Skip to content

feat: respect nested .gitignore files + --exclude flag#18

Merged
lesnik512 merged 7 commits into
mainfrom
feat/nested-gitignore
Jun 26, 2026
Merged

feat: respect nested .gitignore files + --exclude flag#18
lesnik512 merged 7 commits into
mainfrom
feat/nested-gitignore

Conversation

@lesnik512

Copy link
Copy Markdown
Member

What & why

eof-fixer previously read only the root .gitignore and applied it tree-wide, so files ignored by a .gitignore in a subdirectory were still rewritten. This makes the walk honor the nested gitignore convention — per-directory .gitignore files with correct anchoring, precedence, negation, and ignored-directory pruning — and adds a repeatable --exclude flag.

Scope is nested .gitignore only, pure-filesystem: no .git/info/exclude, no global core.excludesFile, no shelling out to git. The tool still works on any directory, repo or not. (Built on the existing pathspec dependency — no new deps; rationale in the decision record below.)

How it works

New module eof_fixer/discovery.py exposes iter_text_files(root, extra_excludes): a top-down DFS carrying a stack of per-directory GitIgnoreSpecs. Each entry is classified by evaluating specs deepest→shallowest, first definitive check_file().include wins — exactly git's "closest file wins, negation re-includes." Ignored directories are pruned (git-correct: a file under an excluded parent can't be re-included), .git is hard-skipped by name, and symlinks are skipped. main() shrinks to consume the iterator; the EOF core (_is_binary/_detect_trailing/_fix_file) is untouched.

CLI

--exclude DIR (repeatable) augments the always-skipped .git and the default .cache/.uv-cache:

  • eof-fixer . → unchanged behavior (backward compatible)
  • eof-fixer . --exclude node_modules --exclude dist → also skips those

Planning artifacts

This was built via the repo's planning convention (Full lane):

  • Spec + plan: planning/changes/2026-06-26.01-nested-gitignore/
  • Decision (build on pathspec vs. a dedicated library): planning/decisions/2026-06-26-build-on-pathspec-for-nested-gitignore.md
  • Architecture promotion: architecture/file-discovery.md, architecture/cli.md

Tests

8 new unit tests in tests/test_discovery.py (nested subtree, deeper-overrides-shallower, negation, directory pruning, .git skip, symlink skip, extra-excludes) plus a --exclude integration test. Full suite 24 passed, 100% coverage; just lint-ci clean. Nested precedence/negation/pruning cross-checked against real git check-ignore.

🤖 Generated with Claude Code

lesnik512 and others added 7 commits June 26, 2026 12:35
Design for honoring per-directory .gitignore files (built on pathspec,
pure-filesystem) plus a repeatable --exclude flag. Full-lane change
bundle 2026-06-26.01-nested-gitignore.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three-task TDD plan: discovery.py walk, main() wiring + --exclude flag,
architecture promotion + decision record.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a one-line comment in `_is_ignored` making explicit that `rel` is
always within the anchor's subtree because specs are pushed/popped around
their directory's walk in `_walk`. Add the root-anchored qualifier to the
architecture prose describing the baseline spec.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Drop redundant list() copy in iter_text_files; from_lines takes any iterable.
- Express the anchor-relative slice as removeprefix(anchor + "/") for clarity
  and safe degradation if the prefix invariant is ever broken.

No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lesnik512 lesnik512 merged commit d1fe9ed into main Jun 26, 2026
6 checks passed
@lesnik512 lesnik512 deleted the feat/nested-gitignore branch June 26, 2026 10:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant