How `.tuicrignore` File Patterns Work for Diff File Filtering: A Complete Guide
.tuicrignore uses Git-compatible ignore patterns to filter files from diff views, with the added ability to override .gitignore rules using un-ignore (!) patterns.
The tuicr terminal UI for code reviews combines repository ignore rules with project-specific overrides to control which files appear in your diff. Understanding how .tuicrignore patterns work helps you tailor exactly what gets reviewed, from hiding generated artifacts to resurrecting specific files that .gitignore would otherwise suppress. According to the agavra/tuicr source code, the filtering system builds on the ignore crate's Gitignore matcher with a layered approach that puts .tuicrignore patterns last—and therefore authoritative.
How the Ignore Matcher Is Built
When tuicr loads a diff, it calls load_matcher in src/tuicrignore.rs to construct a composite matcher from your repository root. The function follows a strict order:
- Load
.gitignore— if present, all patterns are added first - Load
.tuicrignore— if present, patterns are appended after
This ordering is critical because the Gitignore matcher resolves conflicts by favoring later patterns. By placing .tuicrignore second, its rules automatically override any matching .gitignore exclusions.
// From src/tuicrignore.rs, lines 42-60
fn load_matcher(repo_root: &Path) -> Option<Gitignore> {
let mut builder = GitignoreBuilder::new(repo_root);
// .gitignore first
if let Ok(gitignore) = Gitignore::new(repo_root.join(".gitignore")) {
builder.add(gitignore);
}
// .tuicrignore second (can un-ignore with !patterns)
if let Ok(tuicrignore) = Gitignore::new(repo_root.join(".tuicrignore")) {
builder.add(tuicrignore);
}
builder.build().ok()
}
If neither file exists, load_matcher returns None and no filtering occurs. The helper function has_ignore_rules (lines 37-41) performs a quick existence check to avoid unnecessary builder construction.
Filtering Diff Files with Pattern Matching
Once built, the matcher drives filter_diff_files (lines 8-21), which processes the raw Vec<DiffFile> from the VCS backend. For each file, tuicr determines which path to match:
- Added or modified files — use the new path
- Deleted files — use the old path
The matched_path_or_any_parents method from the ignore crate checks both the exact path and its parent directories against accumulated patterns.
// From src/tuicrignore.rs, lines 8-21
pub fn filter_diff_files(repo_root: &Path, files: Vec<DiffFile>) -> Vec<DiffFile> {
let matcher = match load_matcher(repo_root) {
Some(m) => m,
None => return files, // No filtering needed
};
files.into_iter()
.filter(|f| {
let path = f.display_path(); // new_path or old_path for deletions
!matcher.matched_path_or_any_parents(path, false).is_ignore()
})
.collect()
}
Files where is_ignore() returns true are silently dropped. The remaining filtered list proceeds to the TUI for display.
.tuicrignore Pattern Syntax and Semantics
The matcher imported from the ignore crate implements standard Git ignore semantics. These are the essential pattern types applicable to .tuicrignore:
| Pattern form | Meaning | Example match |
|---|---|---|
name/ |
Directory and all contents | target/ matches target/debug/app and target/release/lib.o |
*.ext |
Any file with extension | *.lock matches Cargo.lock, package-lock.json |
path/to/file |
Specific file or directory | src/generated/mod.rs |
!pattern |
Un-ignore: negate previous rule | !Cargo.lock keeps the lockfile visible |
Order-Dependent Rule Resolution
Because patterns accumulate in .gitignore → .tuicrignore sequence, your .tuicrignore file can selectively resurrect files without modifying version-controlled .gitignore:
# .gitignore (repository-wide)
*.lock
generated/
docs/build/
# .tuicrignore (local overrides for review)
# Locks are security-critical, show them
!Cargo.lock
!package-lock.json
# One generated file needs manual review
!generated/api/types.rs
The tests in src/tuicrignore.rs (lines 30-48) verify this behavior: generated/keep.rs surfaces in the diff while sibling files under generated/ remain hidden.
Fast-Path Filtering with filter_paths
The same matcher powers filter_paths (lines 23-35), a lightweight check used by the "cheap status probe" optimization. Before invoking expensive diff generation, tuicr can quickly eliminate paths known to be ignored:
// From src/tuicrignore.rs, lines 23-35
pub fn filter_paths(repo_root: &Path, paths: &[PathBuf]) -> Vec<PathBuf> {
let matcher = match load_matcher(repo_root) {
Some(m) => m,
None => return paths.to_vec(),
};
paths.iter()
.filter(|p| !matcher.matched_path_or_any_parents(p, false).is_ignore())
.cloned()
.collect()
}
This avoids unnecessary VCS calls when the user has already marked substantial portions of the tree as ignored.
Practical .tuicrignore Configuration
Place .tuicrignore in your repository root alongside .gitignore. Here is a typical configuration for a Rust project:
# Build artifacts not worth reviewing
target/
*.rlib
*.rmeta
# Generated bindings (mostly)
src/bindings/
# But keep the hand-edited exceptions
!src/bindings/manual_extensions.rs
# Lockfiles are security relevant, always show
!*.lock
To verify your patterns, invoke tuicr on a diff containing both ignored and un-ignored paths. The filtered file count in the TUI header confirms matcher operation.
Summary
.tuicrignoreprovides Git-compatible ignore patterns with override capability via!un-ignore rules- Pattern precedence follows load order:
.gitignorefirst, then.tuicrignore, with later rules winning conflicts - Core functions in
src/tuicrignore.rs—load_matcher,filter_diff_files,filter_paths— implement the filtering pipeline - The matcher applies to display paths (new path for additions/modifications, old path for deletions)
- Fallback behavior preserves all files when no ignore files exist, ensuring
tuicrremains usable without configuration
Frequently Asked Questions
How do I un-ignore a file that .gitignore hides?
Add a !pattern line in .tuicrignore that matches the file path. Because .tuicrignore loads after .gitignore, its un-ignore rules take precedence. For example, !src/main.rs in .tuicrignore restores that specific file even if src/*.rs appears in .gitignore.
Can I use .tuicrignore without a .gitignore?
Yes. load_matcher functions correctly with only .tuicrignore present. The system treats missing files as empty pattern sets, so you can rely exclusively on .tuicrignore if your project has no standard .gitignore or you prefer keeping review-specific rules separate.
Why do my .tuicrignore patterns not affect working directory status?
The matcher currently filters diff files and paths only. The patterns do not modify git status output or filesystem operations—they strictly control what tuicr presents in its review interface. The underlying Gitignore matcher from the ignore crate could theoretically extend to other operations, but the current implementation focuses on diff presentation.
Do pattern negations work within a single .tuicrignore file?
Yes. Standard Git semantics apply: later patterns override earlier ones in the same file, and ! patterns negate preceding matches regardless of which file they originated from. You can intermix ignore and un-ignore rules in .tuicrignore to construct precise filters without touching .gitignore at all.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →