The walker and git
Every run begins by walking your repository once into a sorted in-memory index, and every rule reads that index, never the raw filesystem and never git directly. What lands in the index is the working tree minus everything .gitignore excludes, so a rule’s idea of “what is here” is the un-ignored working tree. That is almost always what you want; the two places it diverges from git’s own index are the source of nearly every “why didn’t my rule fire” question.
What the walker sees
Section titled “What the walker sees”Starting at the path you pass to alint check (or the current directory), the walker yields every regular file under that root, except paths matched by any of: the repo’s .gitignore files (root, per-directory, and any in directories above the walk root), .git/info/exclude, your global gitignore (core.excludesFile), .ignore files (the same syntax, honored by the ignore crate that powers ripgrep and the walker), the .git/ directory itself, and anything in the config’s ignore: list.
Hidden files are included: alint walks .github/, .editorconfig, and .cargo/ by default. In-tree symlinks are followed, but a symlink whose target escapes the repo root, or that dangles, is pruned from the walk. No git repo is required, and the ignore rules do not need one either: a directory that is not a git repo still honors any .gitignore and .ignore files it contains, so filtering happens with or without a .git/. Only a directory with no ignore files at all shows literally every file.
Two config fields shape the filtering. respect_gitignore (default true) toggles every gitignore source at once; the CLI’s --no-gitignore forces it off for one run. ignore: adds gitignore-style patterns on top, and applies regardless of respect_gitignore, for exclusions that are an alint concern rather than a git one:
version: 1ignore: - "vendor/**" - "**/*.snapshot.json"Setting respect_gitignore: false is rarely useful during development: absence-style rules (dir_absent, file_absent) begin firing on every locally-built target/, node_modules/, and __pycache__/. It fits one-off audits of a build tree, or linting a directory that is not a git repo.
Walked tree versus git’s index
Section titled “Walked tree versus git’s index”Because the walker filters by .gitignore, “the walked tree” is a close but imperfect stand-in for “what git would commit.” alint never reads .git/index or shells out to git ls-files for the walk, so the approximation drifts in two directions:
- A gitignored-but-tracked file (added to git first and gitignored later, or forced in with
git add -f) stays in git’s index on every commit, yet the walker filters it out. Absence rules never see it and content rules never inspect it. - An un-ignored untracked file (a scratch file no
.gitignorepattern covers) is in the walked tree but not git’s index, so a rule fires on a file git is not tracking.
In a healthy repo neither case is common, and git ls-files <path> is the authoritative answer when you suspect one. When a rule must key off git’s index rather than the walked tree, set git_tracked_only: true on it. The rule then fires only for paths git actually tracks, independent of .gitignore state:
- id: target-not-tracked kind: dir_absent paths: "**/target" git_tracked_only: true level: errortarget/ state | default | git_tracked_only |
|---|---|---|
| Gitignored, never built | silent | silent |
| Gitignored, built locally | silent | silent |
| Not gitignored, on disk | fires | silent (not in index) |
| Committed (not gitignored) | fires | fires |
| Gitignored but force-added | silent (walker prunes it) | fires |
| Not a git repo | fires | silent (no index) |
git_tracked_only applies to the existence kinds file_exists, file_absent, dir_exists, and dir_absent; any other kind rejects it at load rather than ignoring it. Outside a git repo (or with git off PATH) the tracked set is empty, so absence rules with the flag become silent no-ops (there is nothing to commit) and existence rules with it fail conservatively (no file qualifies). The git-hygiene family also ships git_commit_message, git_no_denied_paths, and other git-aware kinds; see the rule reference for the full set.
In practice
Section titled “In practice”The canonical “do not let target/ be committed” rule keys off the index, not the walked tree, so a locally-built target/ stays quiet while a force-added one is caught:
version: 1rules: - id: target-not-tracked kind: dir_absent paths: "**/target" git_tracked_only: true level: error message: "target/ must not be committed"On a repo where someone ran git add -f target/debug/build.log:
error target-not-tracked target/ must not be committedThe same rule is silent for every developer who merely built locally, because that target/ is gitignored and untracked.
Going deeper
Section titled “Going deeper”- Configuration is the field reference for
ignore:,respect_gitignore, and every rule field. - Scoping is how a rule narrows within the index, with
paths:,when:, andscope_filter:. - Changed mode restricts a run to the files in a diff, layered on top of the walk.
- The interactive architecture diagrams include the walker view (
walkerFlow) as an explorable model.