# How `.tuicrignore` File Patterns Work for Diff File Filtering: A Complete Guide

> Learn how tuicrignore file patterns filter diffs using Git compatible rules. Override .gitignore with un-ignore patterns for precise control.

- Repository: [Almog Gavra/tuicr](https://github.com/agavra/tuicr)
- Tags: how-to-guide
- Published: 2026-08-02

---

**`.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`](https://github.com/agavra/tuicr/blob/main/src/tuicrignore.rs) to construct a composite matcher from your repository root. The function follows a strict order:

1. **Load `.gitignore`** — if present, all patterns are added first
2. **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.

```rust
// 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.

```rust
// 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`](https://github.com/agavra/tuicr/blob/main/package-lock.json) |
| `path/to/file` | Specific file or directory | [`src/generated/mod.rs`](https://github.com/agavra/tuicr/blob/main/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`:

```text

# .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`](https://github.com/agavra/tuicr/blob/main/src/tuicrignore.rs) (lines 30-48) verify this behavior: [`generated/keep.rs`](https://github.com/agavra/tuicr/blob/main/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:

```rust
// 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:

```text

# 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

- **`.tuicrignore`** provides Git-compatible ignore patterns with override capability via `!` un-ignore rules
- Pattern precedence follows load order: `.gitignore` first, then `.tuicrignore`, with later rules winning conflicts
- **Core functions** in [`src/tuicrignore.rs`](https://github.com/agavra/tuicr/blob/main/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 `tuicr` remains 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.