Skip to main content

gitignore-walk

Evaluate Git-ignore rules, explain matching decisions, and traverse directory trees from Python.

gitignore-walk is a Python 3.10+ library for tools that need predictable, inspectable file discovery. It supports nested .gitignore rules, batch matching, rule provenance, and directory traversal with optional ignore filtering.

Two policies let applications choose whether to use the bundled evaluator or Git itself:

  • GitIgnorePolicy — Portable Python. A bundled Git-ignore parser and matcher with no third-party runtime dependencies. Works without launching Git, even outside a repository.
  • GitStandardPolicy — Git-native. Delegates matching to Git itself, respecting configured global excludes, repository configuration, and Git-specific behavior.

Both work with GitIgnoreWalker; choose the policy that fits the application's configuration needs.

Designed for repository analyzers, code indexers, documentation generators, linters, and build tools.

Quick start

Portable matching

Use the Python-only policy when you want to avoid a Git executable dependency or need to inspect ordinary directory trees.

from gitignore_walk import GitIgnoreWalker

walker = GitIgnoreWalker("/path/to/project")

for path in walker.iter_files():
    print(path)

The walker creates its portable policy automatically. Pass an existing policy when you need to share one or use Git-native matching; the automatically created policy is also available as walker.policy for individual queries and explanations.

The portable policy evaluates applicable rules from:

  • Root and nested .gitignore files.
  • Repository info/exclude, when discoverable.
  • The conventional user-global Git ignore file.
  • Explicit caller-provided initial_excludes.

It discovers an enclosing repository by walking upward, including worktree .git files, but does not require a repository.

The matcher evaluates the patterns itself without launching Git. It intentionally does not interpret Git configuration such as custom core.excludesFile paths or conditional includes.

Git-native matching

When you need Git's actual configured ignore behavior:

from gitignore_walk import GitStandardPolicy, GitIgnoreWalker

policy = GitStandardPolicy("/path/to/checkout")

for decision in policy.explain_many(
    [
        "build/output.txt",
        "docs/guide.md",
    ]
):
    print(
        decision.path,
        decision.ignored,
        decision.source,
        decision.line,
        decision.pattern,
    )

walker = GitIgnoreWalker(policy.root, policy=policy)

for path in walker.iter_files():
    print(path)

This policy uses git check-ignore --no-index and requires an installed Git executable and a non-bare working tree.

It honors Git configuration, including core.excludesFile, linked worktrees, and configuration includes.

Because matching uses --no-index, tracked files can still match ignore rules. Ignore-rule evaluation is not the same as Git status.

Understand why a path is ignored

Consider a repository with two ignore files:

project/
├── .gitignore
├── build/
│   └── output.log
└── src/
    ├── .gitignore
    ├── debug.log
    └── keep.log

The root .gitignore contains:

*.log

And src/.gitignore contains:

!keep.log

The root rule ignores .log files. The nested rule makes src/keep.log eligible for inclusion again.

Using the Git-native policy:

policy = GitStandardPolicy("/path/to/project")

for path in (
    "build/output.log",
    "src/debug.log",
    "src/keep.log",
):
    decision = policy.explain(path)
    print(path, decision.ignored, decision.pattern)

Expected results:

build/output.log True *.log
src/debug.log True *.log
src/keep.log False !keep.log

The detailed explanation also identifies the matching rule's origin:

Path Ignored Source Line
build/output.log Yes .gitignore 1
src/debug.log Yes .gitignore 1
src/keep.log No src/.gitignore 1

GitIgnoreDecision exposes the evaluated path, ignored state, source, matching pattern, and one-based line number.

For overlay diagnostics, use policy.trace(path). It returns the effective decision and the matching rule from each applicable source in precedence order, with effective=True on the rule that determined the result. The portable policy traces its configured layers and parent-directory blocking rules. GitStandardPolicy reports Git's winning rule, but sets complete=False because Git's check-ignore interface does not expose every shadowed matching rule.

This distinction matters when explaining why a file was selected, not just whether it matched an ignore rule.

Choose a matching policy

Capability Portable Python Git-native
Requires Git No Yes
Works outside Git repositories Yes No
Nested .gitignore Yes Yes
Repository info/exclude When discoverable Yes
Conventional global ignores Yes Yes
Custom core.excludesFile No Yes
Git configuration includes No Yes
Batch matching Yes Yes
Match explanations Yes Yes
Filtered traversal Yes Yes

Choose portable when your application needs a self-contained matcher, may run outside repositories, or needs predictable Python-only evaluation.

Choose Git-native when the user's Git configuration is part of the required behavior.

The policies have intentional compatibility differences. GitStandardPolicy never silently falls back to portable matching.

Traverse and filter directory trees

GitIgnoreWalker supports os.walk-style traversal:

walker = GitIgnoreWalker("/path/to/project")

for directory, dir_names, file_names in walker.walk():
    print(directory, file_names)

The selection parameter controls which entries are returned:

Value Behavior
"included" Select unignored entries (default)
"ignored" Select ignored entries
"all" Traverse without ignore filtering

For top-down unfiltered or unignored walks, modify dir_names to prevent descent:

for directory, dir_names, file_names in walker.walk():
    dir_names[:] = [name for name in dir_names if name != "generated"]

walk() yields GitIgnoreWalkEntry named tuples, supporting both named attributes and familiar three-value unpacking.

iter_files() provides a simpler interface when only file paths are needed.

Policies exclude .git files and directories by default, including nested repository metadata. initial_excludes replaces this baseline: use initial_excludes=() to remove it, or include .git alongside any other hard exclusions you supply. Use selection="all" to bypass ignore filtering. Other hidden files follow the active policy.

Ignored-only traversal still visits necessary unignored parents to locate ignored descendants. Its filtered directory list does not control pruning.

Matching and lower-level APIs

Both policies support individual and batch queries:

policy.is_ignored("build/output.log")

policy.is_ignored_many(
    [
        "src/main.py",
        "build/output.log",
        "docs/guide.md",
    ]
)

policy.explain("build/output.log")

Other available interfaces include:

API Purpose
is_gitignored(root, target) Portable one-shot query
parse_gitignore(lines, source=...) Parse ignore rules into an immutable GitIgnoreSpec
matches_gitignore(spec, relative, is_directory=...) Evaluate a parsed specification
GitIgnoreWalker.walk() Directory traversal with optional filtering
GitIgnoreWalker.iter_files() File-only traversal
IgnorePolicy.explain_many() Return a mutable list of detailed decisions
IgnorePolicy.is_ignored_many() Return a mutable list of ignore decisions
IgnorePolicy.iter_explain() Lazily yield detailed decisions
IgnorePolicy.iter_ignored() Lazily yield ignore decisions

Both policies implement IgnorePolicy. Lazy methods consume targets as they run: the portable policy caches each ignore file when first read, and the Git-native policy queries up to batch_size targets per Git process (256 by default). Git can observe changes between batches. Errors are raised when iteration reaches the affected target or batch; neither mode freezes the filesystem.

Important behavior

A few details are worth knowing before integrating the library:

Roots and paths. Relative queries use the policy root. Missing targets are allowed. Targets outside that root are rejected, and unknown nonexistent targets are treated as files.

Baseline exclusions. Both policies treat initial_excludes as hard exclusions. A positive baseline match cannot be overridden by later Git-ignore negation rules.

Symlinks. Discovered symlinks are omitted from traversal. Following symlinks is unsupported. Explicit point-query behavior differs between policies in certain cases.

Filesystem consistency. Operations may cache discovered rules or execute Git in batches. They do not provide an atomic snapshot of a changing directory tree.

Errors. Matching, decoding, configuration, and subprocess failures are reported rather than silently ignored. The Git-native policy does not automatically fall back to portable evaluation.

Platform differences. Pathname interpretation and filesystem behavior can differ between POSIX and Windows, particularly for filenames Windows cannot ordinarily represent.

These boundaries are covered by differential and platform-specific tests. See validation for local test commands and the latest verification results.

The library is not a filesystem security boundary.

Development

Create and activate a Python 3.12 virtual environment from the repository root:

python3.12 -m venv .venv
source .venv/bin/activate

In Windows PowerShell:

py -3.12 -m venv .venv
.venv\Scripts\Activate.ps1

Then install development dependencies into the environment:

python -m pip install -e '.[dev]'

Run the same checks used by CI:

python -m scripts.ci all

Run this from the repository root before pushing. It uses the active Python interpreter and runs the pytest suite, Ruff lint and format checks, strict Pyright, Markdown formatting checks with rumdl, repository Markdown policy checks with mdrepo on Python 3.11+, and package validation (wheel and source archive builds, Twine checks, and an import smoke test against the built wheel). Install the development dependencies first with the command above. A successful run is the local pre-push check; CI also runs tests on other Python versions and operating systems.

GitHub Actions delegates each matrix entry to this same runner: other platform and Python entries run test, while Ubuntu Python 3.12 runs all. That job is the hosted counterpart to the complete local pre-push run.

When editing Markdown, apply safe formatting fixes before the final check:

rumdl check --fix .
python -m mdrepo fix .  # Python 3.11+
python -m scripts.ci all

Or test across supported Python interpreters installed locally:

python -m scripts.ci matrix
Command Purpose
test Unit and differential verification on the active interpreter
quality Ruff, strict Pyright, and Markdown checks
package Wheel/sdist build, Twine validation, isolated wheel import
all All checks for the active interpreter
matrix Test across locally installed supported interpreters
local Windows interpreter matrix and Linux Docker matrix

GitHub Actions covers Python 3.10, 3.12, and 3.15 on Ubuntu, plus Python 3.12 on Windows and macOS.

Tests isolate Git configuration and HOME/XDG settings. Differential fixtures compare portable matching with Git's own evaluator.

See validation for local CI details and verified results.

References

Matching behavior is informed by Git's official documentation:

Metadata

Release files for gitignore-walk 0.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for gitignore-walk 0.0.1
File Size Uploaded Provenance Actions
gitignore_walk-0.0.1.tar.gz 46.1 kB Without provenance Details for gitignore_walk-0.0.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for gitignore-walk 0.0.1
File Interpreter ABI Platform Actions
gitignore_walk-0.0.1-py3-none-any.whl Python 3 none any Details for gitignore_walk-0.0.1-py3-none-any.whl

Total release size: 73.0 kB

Release files / gitignore_walk-0.0.1.tar.gz

Download URL gitignore_walk-0.0.1.tar.gz
Size 46.1 kB
Tags Source
SHA-256 checksum
How to use checksums
3e5e6979e5d3a839c0c6a728f904e484272f7061c9773ab406319cf0810f3f4f
BLAKE2b-256 checksum
How to use checksums
f2aa23b7f621612cf5d3d62ab681660a73509dd4a059051537ce4faba3c2283f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.10

Release files / gitignore_walk-0.0.1-py3-none-any.whl

Download URL gitignore_walk-0.0.1-py3-none-any.whl
Size 26.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f09b26c67ae87d42918e82f88466a6c44723289143971eab9843dc9dd806a309
BLAKE2b-256 checksum
How to use checksums
08facb80cc548f03fb71ecb07cbfe2565257d4aef916225afa3f8880db01e43c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.10

Security for gitignore-walk 0.0.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.0.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page