Skip to content

About

Git-compatible .gitignore parser and path filter for Python: library and streaming CLI, include/whitelist mode, shutil.copytree filter, memory-bounded for millions of paths. Ships as a Claude Code plugin too.

Topics

Resources

Contributing

Security policy

Stars

31 stars

Watchers

1 watching

Forks

Latest commit

 

History

170 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

igittigitt

CI CodeQL License: MIT Open in Codespaces PyPI PyPI - Downloads Code Style: Ruff codecov Maintainability security: bandit

igittigitt is a spec-compliant .gitignore parser for Python - a library and a command-line tool. Point it at .gitignore rules (or pass rules as strings) and ask whether a path is ignored, or use it directly as the ignore= callback of shutil.copytree.

Features

  • 100% git-compatible matching. The matching engine reproduces git's algorithm exactly - a single ordered pass with last-matching-pattern-wins, correct negations (!pattern), the rule that a file cannot be re-included when a parent directory is excluded, per-directory .gitignore precedence, anchors, trailing-slash (directory-only), ** (a segment of three or more asterisks such as ***/build counts as ** too, as in git), *, ? and [...] character classes. Compatibility is proven by a differential test suite that compares igittigitt against real git check-ignore on hundreds of generated cases. (This fixes the negation and nested-precedence limitations of older releases.)
  • Include / whitelist mode. IncludeParser inverts the logic: patterns describe what to keep. It is directory-aware (rsync-like), so it works correctly as a copytree filter even for deeply nested includes.
  • Memory-safe for millions of files. Both modes stream per directory and decide each path in O(depth x rules). Memory scales with the number of rules, never with the number of files or directories - so it stays flat on trees with millions of entries.
  • Scriptable CLI with piping. igittigitt check mirrors git check-ignore, and igittigitt filter is a Unix filter that reads paths from stdin and prints the survivors - newline- or NUL-separated (-z), streamed, with clean SIGPIPE handling, so it drops cleanly into find ... | igittigitt filter.
  • Typed library, pure Python. Ships py.typed, no runtime dependency on the git binary.

Install

uv is the recommended installer.

# as a project dependency
uv add igittigitt

# as a standalone CLI tool (isolated environment, added to PATH)
uv tool install igittigitt

# run once without installing
uvx igittigitt --help

# or classic pip / pipx
pip install igittigitt
pipx install igittigitt

The project targets Python 3.10+ and is tested on Linux and Windows across CPython 3.10-3.15, and on macOS with the newest of them. See INSTALL.md for every install method.

Why we recommend uv: supply-chain protection

This repository commits a uv.lock. It pins every package the project needs, transitive dependencies included, to an exact version and to the hashes of that version's files. The exclude-newer setting under [tool.uv] in pyproject.toml adds a cooldown: uv leaves any release younger than that out of the lock. CI installs from the lock, so a hijacked release that gets pulled again within the cooldown never reaches our builds, and a file whose hash does not match is refused.

That protection stays in this repository. The installable package declares only lower bounds (>=) for its dependencies, so a plain pip install takes the newest release of every transitive dependency at the moment you run it. If one of them was hijacked an hour earlier, you get the hijacked version. pipx install and uv tool install without options do the same.

To install the command line tool with a cooldown of your own (uv then skips any release younger than 7 days, transitive ones included), or to run the exact tree our CI tests:

uv tool install --exclude-newer "7 days" igittigitt
git clone https://github.com/bitranox/igittigitt.git && cd igittigitt && uv sync --locked && uv run igittigitt --help

If you depend on igittigitt in your own project, give that project the same cooldown (7 days here, as an example) and commit the uv.lock that uv add igittigitt writes:

[tool.uv]
exclude-newer = "7 days"

Library usage

Ignore mode

import igittigitt

parser = igittigitt.IgnoreParser()

# discover and parse every .gitignore below base_dir (per-directory precedence)
parser.parse_rule_files(base_dir="/opt/project")

# ... or add rules explicitly
parser.add_rule("*.py[cod]", base_path="/opt/project")
parser.add_rule("!keep.pyc", base_path="/opt/project")

parser.match("/opt/project/main.pyc")  # True  (ignored)
parser.match("/opt/project/keep.pyc")  # False (re-included by the negation)

ignored, rule = parser.match_with_rule("/opt/project/main.pyc")
rule.pattern_original  # '*.py[cod]' - the deciding rule, also returned for a negation

match_with_rule returns (ignored, rule); the rule (or None when nothing matched) carries pattern_original, is_negation_rule, source_file and source_line_number (both None for a rule added with add_rule).

parse_rule_files also loads git's user-level ignore file ($XDG_CONFIG_HOME/git/ignore, or ~/.config/git/ignore), as git does: pass add_default_patterns=False to skip it, set igittigitt.conf_igittigitt.add_default_patterns = False to change that default for later calls, or call load_default_patterns(base_dir) to add it to a parser on its own. IgnoreParser(dir_cache_max=N) sets the directory-decision cache for one parser (see [performance] below).

Use it as a shutil.copytree filter to copy a tree without the ignored files:

import shutil, igittigitt

parser = igittigitt.IgnoreParser()
parser.parse_rule_files(base_dir="src_tree")
shutil.copytree("src_tree", "dst_tree", ignore=parser.shutil_ignore)

Whitelist / include mode

IncludeParser keeps only what matches; everything else is dropped. Including a directory keeps its whole subtree, and unanchored patterns are found at any depth:

import shutil, igittigitt

inc = igittigitt.IncludeParser()
inc.add_rule("*.py", base_path="src_tree")  # keep only python files...
inc.add_rule("docs/", base_path="src_tree")  # ...and the whole docs/ subtree

# copy only the kept files (parent directories are descended into automatically)
shutil.copytree("src_tree", "dst_tree", ignore=inc.shutil_include)

inc.match("src_tree/pkg/deep.py")  # True  (kept)

IncludeParser reads rule files the same way (parse_rule_file, parse_rule_files) and takes the same dir_cache_max argument.


CLI usage

# which of these paths are ignored? (like `git check-ignore`)
igittigitt check -C /path/to/repo a.log src/main.py
#   exit 0 if at least one path is ignored, 1 if none

# show the deciding rule (source:line:pattern), like `git check-ignore -v`: as in git, a
# path a negation re-includes is listed too and that makes the exit 0 (without -v it is
# not ignored: exit 1); the rule file is named relative to -C
igittigitt check -C /path/to/repo -v a.log

# Unix filter: drop ignored paths from a stream
find . -type f | igittigitt filter

# keep only what matches the include patterns
find . -type f | igittigitt filter --include -r '*.py'

# paths can also be given as arguments
igittigitt filter -C /path/to/repo a.log src/main.py

# NUL-separated I/O for paths with spaces/newlines
find . -print0 | igittigitt filter -z | xargs -0 ...

# inline rules and explicit ignore files
igittigitt check -r '*.tmp' -f .gitignore --stdin < paths.txt

Global options

Option Description
--version Print the version and exit.
--traceback / --no-traceback Show a full Python traceback on errors (default: off).
--profile NAME Load configuration from a named profile.
--set SECTION.KEY=VALUE Override one config value (repeatable), e.g. --set performance.dir_cache_max=16384.
--env-file PATH Use an explicit .env file (skips the upward search).
-h, --help Show help.

Commands

Command What it does
info Print resolved package metadata.
check [PATHS...] Print the paths that are ignored (mirrors git check-ignore, -v included). Reads stdin with --stdin, -, or when no path is given. Exit 0 if any matched, 1 if none.
filter [PATHS...] Unix filter: read paths from stdin (or the arguments) and print the survivors. --include switches to whitelist mode. Exit 0.
config Print the merged, layered configuration (with provenance).
config-deploy Write the default config files into the app/host/user config directories.
config-generate-examples Write example config files you can copy and edit.
logdemo Emit sample log records; --theme classic|dark|neon|pastel previews a console theme.

Shared check / filter options: -C/--base-dir, -f/--gitignore FILE (repeatable), -r/--rule PATTERN (repeatable), --scan/--no-scan, --default-patterns, -z/--zero (NUL I/O), check only: -v/--verbose and --stdin, filter only: -i/--include.

Two defaults differ from what you might expect:

  • --default-patterns is off on the command line, while parse_rule_files loads git's user-level ignore file by default. git check-ignore reads that file too, so pass --default-patterns when check must answer exactly as git does on your machine.
  • --include keeps only what the -r/-f rules match; it never scans for .gitignore files, so with neither option it prints nothing. --scan and --default-patterns apply to ignore mode.

With -v -z, check writes each record as four NUL-terminated fields (source, line, pattern, path), as git check-ignore -v -z does.

check and filter stream their input and write results immediately, with clean SIGPIPE handling - so ... | igittigitt filter | head works without a traceback; the command then exits 141.


Configuration

igittigitt uses lib_layered_config: settings are merged from, lowest to highest precedence,

bundled defaults -> app -> host -> user -> .env file -> environment variables -> --set

Inspect the effective configuration with igittigitt config, and deploy editable copies with igittigitt config-deploy. Override a single value per run with --set, in a .env file as SECTION__KEY=value, or via an environment variable as IGITTIGITT___SECTION__KEY=value. When a configuration file cannot be loaded, config, check and filter exit 78 with one line naming it, while info, config-deploy (which replaces the file), config-generate-examples, logdemo and --help still run; a malformed --set or an invalid --profile name exits 2. See CONFIG.md.

[performance] - engine and CLI tuning

These knobs only affect speed and cache memory, never which paths match. Defaults were chosen by measurement; all keep memory bounded.

Key Default Meaning
dir_cache_max 8192 Capacity of the per-parser directory-decision LRU cache (0 disables it). The main speed-up on tree-shaped workloads; memory is O(this), not O(#files).
pattern_cache_max 4096 Capacity of the process-wide compiled-regex cache (keyed by distinct pattern).
stdin_chunk_bytes 65536 Read granularity for streaming paths from stdin.
max_token_bytes 1048576 Per-path safety bound; a separator-less token larger than this is rejected instead of buffered unbounded.

Example: igittigitt --set performance.dir_cache_max=32768 filter -C repo.

[lib_log_rich] - logging

Logging is provided by lib_log_rich. The [lib_log_rich] section configures console level/theme, journald/eventlog/Graylog backends, queueing, scrubbing and payload limits. Every key is documented inline in the deployed config.d/90-logging.toml; for example set the console level with igittigitt --set lib_log_rich.console_level=DEBUG info or IGITTIGITT___LIB_LOG_RICH__CONSOLE_LEVEL=DEBUG.

The full, commented settings live in the bundled config files (see config-generate-examples) and in CONFIG.md.


Claude Code skill

This repo is itself a Claude Code plugin/marketplace and ships a skill (skills/python-gitignore/) that teaches Claude how to install, configure and use igittigitt (library, CLI, and bash piping). Install it in any project:

/plugin marketplace add bitranox/igittigitt
/plugin install igittigitt

The same skill is also mirrored in the central bitranox marketplace (https://github.com/bitranox/bitranox-skills) as coding-python-gitignore.


AI transparency

This project was built by a human with AI assistance, used as a tool under human direction.

  • ai-stance.md - why we work this way and how we think about AI in software.
  • ai-disclosure.md - exactly where AI was and was not used in this project, and what has and has not been tested.

Further Documentation

About

Git-compatible .gitignore parser and path filter for Python: library and streaming CLI, include/whitelist mode, shutil.copytree filter, memory-bounded for millions of paths. Ships as a Claude Code plugin too.

Topics

Resources

Contributing

Security policy

Stars

31 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages