aboutsummaryrefslogtreecommitdiff
path: root/internal/engine
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-17 10:15:45 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-17 10:15:45 +0200
commita2cb20851499e9c00bd3bf642680a9ce3148cae8 (patch)
tree09ef80b9662308b758a88ed2f43078c92e525f29 /internal/engine
parent47b6776c2cb9b017ad95acb5aae8e34b8776fc7c (diff)
downloadkrino-a2cb20851499e9c00bd3bf642680a9ce3148cae8.tar.gz
krino-a2cb20851499e9c00bd3bf642680a9ce3148cae8.zip
gui: name the other copy of a duplicate, and offer to keep this one instead
Diffstat (limited to 'internal/engine')
-rw-r--r--internal/engine/facts.go12
-rw-r--r--internal/engine/match.go8
-rw-r--r--internal/engine/match_test.go34
3 files changed, 54 insertions, 0 deletions
diff --git a/internal/engine/facts.go b/internal/engine/facts.go
index 641f1ef..ce05df4 100644
--- a/internal/engine/facts.go
+++ b/internal/engine/facts.go
@@ -158,6 +158,10 @@ type facts struct {
// could not read), so (matched) is unknown while none has matched.
undecided bool
+ // dupOriginal is the file a (duplicate) test last matched against,
+ // absolute, for a front end that offers to act on the other copy.
+ dupOriginal string
+
contentDone bool // extraction was attempted
contentErr error // why it failed
partialErr error // extract.ErrPartial: a keyword not found may be in the unread part
@@ -290,9 +294,17 @@ func (f *facts) Duplicate(dirs []string) (string, bool, error) {
if !isDup {
return "", false, nil
}
+ // The absolute path is kept for a front end that has to act on the
+ // other copy - the window offers to keep this one instead - while the
+ // reason text stays as it reads best (plan 21).
+ f.dupOriginal = orig
return displayOriginal(orig, root), true, nil
}
+// DuplicateOriginal is the file the last (duplicate) test matched against,
+// absolute; "" when none did.
+func (f *facts) DuplicateOriginal() string { return f.dupOriginal }
+
// displayOriginal reports orig relative to root when it lies inside root,
// else home-abbreviated (xdg.Abbrev), as every other user-visible path is.
func displayOriginal(orig, root string) string {
diff --git a/internal/engine/match.go b/internal/engine/match.go
index 59cea42..25c05e5 100644
--- a/internal/engine/match.go
+++ b/internal/engine/match.go
@@ -46,6 +46,13 @@ type FileMatch struct {
// directory's rules use, or that check failed. Only set for a file some
// matching rule would delete.
NoDelete string
+
+ // DuplicateOf is the file a (duplicate) test matched this one against,
+ // absolute; "" when none did. The reason text says the same thing the
+ // way it reads best - relative to the directory when it is inside it -
+ // which leaves a front end no way to act on the other copy, or even to
+ // say where it is (his report, 2026-09-17).
+ DuplicateOf string
}
// Result is everything Match found in one directory.
@@ -190,6 +197,7 @@ func evalFile(run *matchRun, file scan.File) FileMatch {
if len(run.d.DupScopes) > 0 && deletes(fm.Rules) {
fm.NoDelete = noDelete(f, run.d.DupScopes)
}
+ fm.DuplicateOf = f.DuplicateOriginal()
return fm
}
diff --git a/internal/engine/match_test.go b/internal/engine/match_test.go
index 63bb54a..224f881 100644
--- a/internal/engine/match_test.go
+++ b/internal/engine/match_test.go
@@ -460,3 +460,37 @@ func TestRuleDuplicateWarningShortensThePath(t *testing.T) {
t.Errorf("no shortened rule warning for a.txt: %+v %+v", r.Matched, r.Unmatched)
}
}
+
+// TestFileMatchNamesTheDuplicate: a file a (duplicate) test matched comes
+// back with the other copy's absolute path, so a front end can say where it
+// is and act on it - the reason text alone says "duplicate of NAME" for a
+// copy inside the directory, which reads as no place at all (plan 21).
+func TestFileMatchNamesTheDuplicate(t *testing.T) {
+ e, d, _ := fixture(t)
+ res, err := e.Match(context.Background(), d)
+ if err != nil {
+ t.Fatal(err)
+ }
+ found := false
+ for _, fm := range res.Matched {
+ if fm.File.Rel != "report (1).pdf" && fm.File.Rel != "report.pdf" {
+ continue
+ }
+ if fm.DuplicateOf == "" {
+ continue
+ }
+ found = true
+ if !filepath.IsAbs(fm.DuplicateOf) {
+ t.Errorf("%s: DuplicateOf = %q, want an absolute path", fm.File.Rel, fm.DuplicateOf)
+ }
+ if fm.DuplicateOf == fm.File.Path {
+ t.Errorf("%s: reported as a duplicate of itself", fm.File.Rel)
+ }
+ if _, err := os.Stat(fm.DuplicateOf); err != nil {
+ t.Errorf("%s: DuplicateOf does not exist: %v", fm.File.Rel, err)
+ }
+ }
+ if !found {
+ t.Fatal("neither copy came back with the file it duplicates")
+ }
+}