How Dirty works
Dirty’s performance comes from three key design decisions:Dirty uses the rayon crate to inspect repositories in parallel. After finding all repository paths, it processes them concurrently across multiple CPU cores.
// From main.rs:119-123
let infos: Vec<_> = repos
.par_iter()
.filter_map(|p| inspect_repo(p, args.include_unpushed))
.filter(|i| (!args.dirty || i.dirty) && (!args.local || i.local_only))
.collect();
// From main.rs:89-96
let repo = Repository::open(path).ok()?;
let mut opts = StatusOptions::new();
opts.include_untracked(true)
.recurse_untracked_dirs(false)
.exclude_submodules(true);
let dirty = !repo.statuses(Some(&mut opts)).ok()?.is_empty();
let local_only = repo.remotes().ok().is_none_or(|r| r.is_empty());
Default depth of 3
The default depth of 3 is optimized for common project layouts:Depth is measured from the starting directory. A repo at depth 3 means there are 3 directory levels between the start path and the repository.
Why depth 3?
- Fast scanning: Limits filesystem traversal
- Typical coverage: Most developers organize code 1-3 levels deep
- Predictable performance: Prevents accidentally scanning entire home directories
Adjusting depth with -L flag
Use the-L flag to control how deep Dirty searches:
Finding the right depth
If you see “No git repos found”, try increasing the depth:Performance note: —include-unpushed flag
The--include-unpushed flag shows how many commits ahead of upstream each repository is, but it’s significantly slower:
Why is it slower?
Checking unpushed commits requires:- Resolving the upstream tracking branch
- Computing graph distance between HEAD and upstream
- Handling edge cases (detached HEAD, no upstream, etc.)
Handling large monorepo directories
Dirty is optimized for scanning multiple repositories, not for inspecting large individual repositories.Stopping at .git directories
Dirty stops descending into subdirectories once it finds a.git folder:
- Large repos don’t slow down scanning: Dirty won’t traverse a monorepo’s entire file tree
- Nested repos are skipped: Only the top-level repository is detected
If you have intentionally nested git repositories (not submodules), only the outermost repository will be detected.
Symbolic links are skipped
Dirty does not follow symbolic links to avoid infinite loops and duplicate scanning:Tips for optimal performance
# Same speed: filtering is efficient
dirty --dirty ~/code
dirty --local ~/code
dirty --dirty --local ~/code
# Fast: general status check
dirty ~/code
# Slow: includes upstream comparisons
dirty --include-unpushed ~/code
Performance characteristics
Time complexity
- Directory traversal: O(d × n) where d is depth and n is average directories per level
- Repository inspection: O(r) where r is number of repositories (parallelized)
- With —include-unpushed: O(r × b) where b is branch resolution time
Memory usage
- Low memory footprint: Dirty collects repository paths first, then processes them
- No file content loading: Only git metadata is read
- Sorted output: Repository paths are sorted before inspection