You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Pointing -p at a bare git repository (repo.git, ~/.cfg) or inside .git still offers branch refs for deletion: #205 skips VCS directories only below the root #207
#192/#198 (fixed by #194/#205) stop the scan from walking into .git, .svn, .hg, .bzr, CVS, _darcs and bare git repositories. Those checks run only in ShouldRecursePredicate (FileDeduplicator/FileScanner.cs:128-129), which is evaluated for subdirectories. Nothing checks the root itself or its ancestors.
Repro, run against main @ 9805334: the root is a bare repository with HEAD, objects/, and refs/heads/main and refs/heads/feature-long pointing at the same commit hash.
Observed: Scan returns one duplicate group, and DryRun/Deduplicate would delete refs/heads/feature-long, which deletes the branch.
Expected: the verb refuses the root (or skips it with a clear message) when it is a VCS metadata directory or lies inside one.
The same applies to -p proj/.git, -p proj/.git/refs and -p ~/.cfg, the dotfiles bare-repo pattern #198 named. Any of these is a plausible target when someone points the tool at "the folder with all my config" or at a mirror directory.
Why it matters
The fix for #198 exists because deleting a ref file silently removes a branch, and loose objects are also byte-identical across packs. Running at the repository instead of above it hits the same data loss. The README describes skipping VCS directories "under the root", but nothing documents a VCS root as intentionally allowed.
Suggested fix
In IsScannableDirectory (or BaseVerb.Run), refuse a root when it or any ancestor:
has a name in VersionControlDirectories, or
passes IsGitDirectory.
Report the refusal and return a non-zero exit, the way a missing directory is handled since #144.
Acceptance criteria
Scan, DryRun, Stats and Deduplicate on a bare-repo root, on .git, and on a folder inside .git report the refusal and delete nothing.
A normal working tree that contains.git behaves as today.
Area: root validation (IsScannableDirectory / BaseVerb.Run). The checks in FileScanner.ShouldRecursePredicate need extending to the root and its ancestors.
Activity
matt-edmondson commentedon Nov 5, 2026
Triage
.gitdirectory, or a folder inside one. The dotfiles bare-repo pattern (~/.cfg) makes this a realistic target. The data loss is the same one Deduplicate still deletes branch refs inside bare git repositories (project.git, mirrors, ~/.cfg): only directories named exactly .git are skipped #198 fixed for subdirectories.IsScannableDirectory/BaseVerb.Run). The checks inFileScanner.ShouldRecursePredicateneed extending to the root and its ancestors.VersionControlDirectoriesandIsGitDirectory, and refuses the root with a non-zero exit as Every verb exits with code 0 even when the directory is missing, deletions fail, or the arguments are invalid #144 does for a missing directory.Generated by Claude Code