diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-14 10:56:21 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-14 10:56:21 +0200 |
| commit | 1506c7dcd6032c04c1df6f5785b6dd3dfe4511cc (patch) | |
| tree | fb6d573ca8726c1b1d91ba273a284ba666cde3f6 /internal/engine/match.go | |
| parent | ebdd7bb254a0f19f815a644d26ce232de7be0adb (diff) | |
| download | krino-1506c7dcd6032c04c1df6f5785b6dd3dfe4511cc.tar.gz krino-1506c7dcd6032c04c1df6f5785b6dd3dfe4511cc.zip | |
krino: duplicates are found, never deleted
A rule combining (duplicate) with a delete action is refused at load, and a
file that is a duplicate under any duplicate scope its directory uses gets no
delete step from any rule: the plan shows it skipped and the chain continues.
A failed duplicate check blocks the delete too. Tests cover (matched), another
rule's own condition, a test evaluation skipped, two scopes and a failed
check, each checking every copy is still on disk.
Diffstat (limited to 'internal/engine/match.go')
| -rw-r--r-- | internal/engine/match.go | 42 |
1 files changed, 42 insertions, 0 deletions
diff --git a/internal/engine/match.go b/internal/engine/match.go index e68a6e8..ffee7f0 100644 --- a/internal/engine/match.go +++ b/internal/engine/match.go @@ -33,6 +33,12 @@ type FileMatch struct { File scan.File Rules []RuleMatch // matching rules in order, ending at the first with (stop) Warnings []string // "<rule>: <warning>", e.g. "acme: content unreadable: needs pdftotext, not installed" + + // NoDelete is non-empty when no delete step may run for this file (spec + // §5.5 rule 2), and says why: the file is a duplicate under a scope its + // directory's rules use, or that check failed. Only set for a file some + // matching rule would delete. + NoDelete string } // Result is everything Match found in one directory. @@ -127,9 +133,45 @@ func evalFile(run *matchRun, file scan.File) FileMatch { break } } + if len(run.d.DupScopes) > 0 && deletes(fm.Rules) { + fm.NoDelete = noDelete(f, run.d.DupScopes) + } return fm } +// NeverDeleted is the reason a duplicate's delete step is skipped (spec +// §5.5). +const NeverDeleted = "a duplicate is never deleted" + +// deletes reports whether any of rules has a delete action. +func deletes(rules []RuleMatch) bool { + for _, rm := range rules { + for _, a := range rm.Rule.Conf.Actions { + if a.Kind == config.Delete || a.Kind == config.DeletePermanent { + return true + } + } + } + return false +} + +// noDelete looks f up under every scope, whether or not evaluating the +// rules reached that test (spec §5.5 rule 2), and returns why f must not be +// deleted, or "" when it may be. A failed lookup blocks the delete too: +// krino cannot show the file is not a duplicate, so it keeps it. +func noDelete(f *facts, scopes [][]string) string { + for _, dirs := range scopes { + _, dup, err := f.Duplicate(dirs) + if err != nil { + return "duplicate check failed, so not deleted: " + err.Error() + } + if dup { + return NeverDeleted + } + } + return "" +} + // RuleTrace is one rule's outcome in an Explain call. type RuleTrace struct { Rule *Rule |
