feat: add gitignore-scrub to catch tracked files newly matched by .gitignore #172

Merged
rootiest merged 3 commits from feat/gitignore-scrub into main 2026-09-23 20:04:09 +00:00
Owner

Summary

Adds gitignore-scrub, a standalone function that finds files tracked by git but now matched by .gitignore, and offers to untrack them.

  • Default (interactive): prompts once for all tracked-but-ignored files found, git rm --cached on confirmation. A decline is remembered per-path in the repo's local git config (gitignore-scrub.skip) so the same file isn't re-asked next time.
  • -w/--warn: read-only — prints a Warning: line per match, makes no changes. Meant for non-interactive callers such as a personal git hook (declines from interactive runs still suppress the warning).
  • -f/--force: skips the prompt, untracks every pending match immediately.
  • -i/--individual: prompts once per file instead of once for the whole group.
  • -r/--reset: clears the repo's skip list first, so previously declined files are reconsidered. Combines with any of the above.
  • -w, -f, and -i are mutually exclusive (enforced via argparse --exclusive).
  • Skips silently on repos with more than $GITIGNORE_SCRUB_LIMIT tracked files (default 5000), to avoid adding latency to huge repos.

gi now calls gitignore-scrub at the end of any run that touched .gitignore, so a newly-added pattern that matches an already-tracked file gets caught right where it happens.

Also untracks completions/fisher.fish and functions/fisher.fish — both matched an existing "Fisher-managed, do not commit" .gitignore rule but were never scrubbed from the index since the initial commit. Found by running gitignore-scrub for real via gi fish in this repo.

Why

.gitignore patterns added after a file is already committed don't retroactively untrack it — the file just silently stays tracked. This makes that mismatch visible instead of relying on catching it by eye.

Manual Verification Checklist

  • gi python (or any target) in a repo with a tracked file matching a newly-added pattern — confirm the prompt appears and git rm --cached runs on y
  • Decline the prompt (n), then run gi again — confirm the same file is not re-prompted
  • gitignore-scrub --warn in a repo with an unconfirmed tracked-but-ignored file — confirm a Warning: line prints and no changes are made
  • gitignore-scrub --force — confirm it untracks pending matches without prompting
  • gitignore-scrub --individual — confirm each file gets its own prompt
  • gitignore-scrub --reset after declining a file — confirm it's reconsidered
  • gitignore-scrub -f -w — confirm it errors instead of silently picking one
  • gitignore-scrub -h — confirm help text renders
  • tests/run-tests.fish still passes (786/786 locally)
## Summary Adds `gitignore-scrub`, a standalone function that finds files tracked by git but now matched by `.gitignore`, and offers to untrack them. - **Default (interactive):** prompts once for all tracked-but-ignored files found, `git rm --cached` on confirmation. A decline is remembered per-path in the repo's local git config (`gitignore-scrub.skip`) so the same file isn't re-asked next time. - **`-w`/`--warn`:** read-only — prints a `Warning:` line per match, makes no changes. Meant for non-interactive callers such as a personal git hook (declines from interactive runs still suppress the warning). - **`-f`/`--force`:** skips the prompt, untracks every pending match immediately. - **`-i`/`--individual`:** prompts once per file instead of once for the whole group. - **`-r`/`--reset`:** clears the repo's skip list first, so previously declined files are reconsidered. Combines with any of the above. - `-w`, `-f`, and `-i` are mutually exclusive (enforced via `argparse --exclusive`). - Skips silently on repos with more than `$GITIGNORE_SCRUB_LIMIT` tracked files (default 5000), to avoid adding latency to huge repos. `gi` now calls `gitignore-scrub` at the end of any run that touched `.gitignore`, so a newly-added pattern that matches an already-tracked file gets caught right where it happens. Also untracks `completions/fisher.fish` and `functions/fisher.fish` — both matched an existing "Fisher-managed, do not commit" `.gitignore` rule but were never scrubbed from the index since the initial commit. Found by running `gitignore-scrub` for real via `gi fish` in this repo. ## Why `.gitignore` patterns added after a file is already committed don't retroactively untrack it — the file just silently stays tracked. This makes that mismatch visible instead of relying on catching it by eye. ## Manual Verification Checklist - [x] `gi python` (or any target) in a repo with a tracked file matching a newly-added pattern — confirm the prompt appears and `git rm --cached` runs on `y` - [x] Decline the prompt (`n`), then run `gi` again — confirm the same file is not re-prompted - [x] `gitignore-scrub --warn` in a repo with an unconfirmed tracked-but-ignored file — confirm a `Warning:` line prints and no changes are made - [x] `gitignore-scrub --force` — confirm it untracks pending matches without prompting - [x] `gitignore-scrub --individual` — confirm each file gets its own prompt - [x] `gitignore-scrub --reset` after declining a file — confirm it's reconsidered - [x] `gitignore-scrub -f -w` — confirm it errors instead of silently picking one - [x] `gitignore-scrub -h` — confirm help text renders - [x] `tests/run-tests.fish` still passes (786/786 locally)
rootiest added the Kind/FeatureArea/Functions labels 2026-09-23 18:59:32 +00:00
rootiest added 1 commit 2026-09-23 19:00:15 +00:00
feat: add gitignore-scrub to catch tracked files newly matched by .gitignore
CI / github-mirror (pull_request) Skipped
CI / test (pull_request) Successful in 2m26s
CI / build-docs (pull_request) Skipped
355688c134
Standalone function, not gi-private: default mode prompts once for all
tracked-but-ignored files and offers git rm --cached, remembering a
decline per-path in the repo's local git config (gitignore-scrub.skip)
so the same file isn't re-asked. -w/--warn is read-only (prints Warning
lines, no prompt, no mutation) for non-interactive callers like a git
hook. Skips silently above $GITIGNORE_SCRUB_LIMIT tracked files (default
5000) to avoid latency on huge repos.

gi now calls gitignore-scrub at the end of any run that touched
.gitignore.
rootiest force-pushed feat/gitignore-scrub from a9d14177b8 to 355688c134 2026-09-23 19:00:15 +00:00 Compare
rootiest added 1 commit 2026-09-23 19:44:31 +00:00
chore: untrack fisher-managed files left tracked since initial commit
CI / github-mirror (pull_request) Skipped
CI / test (pull_request) Successful in 2m36s
CI / build-docs (pull_request) Skipped
37fea155b3
completions/fisher.fish and functions/fisher.fish match the Fisher-managed
ignore rule added in c77a52a but were never scrubbed from the index. Found
by gitignore-scrub itself (gi fish). Files remain on disk, untracked only.
rootiest added 1 commit 2026-09-23 19:52:12 +00:00
feat: add -r/--reset, -f/--force, -i/--individual to gitignore-scrub
CI / github-mirror (pull_request) Skipped
CI / test (pull_request) Successful in 2m15s
CI / build-docs (pull_request) Skipped
7e3d48ddac
-r clears the repo's skip list first so declined files are reconsidered.
-f untracks every pending match immediately, no prompt. -i prompts once
per file instead of once for the whole group. -w, -f, -i are mutually
exclusive (argparse --exclusive), -r combines with any of them.
rootiest merged commit 6074687a80 into main 2026-09-23 20:04:09 +00:00
rootiest deleted branch feat/gitignore-scrub 2026-09-23 20:04:13 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rootiest/fish-config#172