This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Branch per feature, no direct pushes to main. Past sessions sometimes committed user-visible changes directly to main; from now on:
- For any user-visible change (new flag, new behavior, perf improvement, output redesign), create a branch named
feat/<short-slug>,fix/<short-slug>, orperf/<short-slug>. Trivial doc fixes or CI tweaks can still go straight tomainif the user asks. - Land it via a PR (
gh pr create) so there's an audit trail. Squash or rebase merge — the project favors a linear history. - After merge, add a one- or two-line entry to
CHANGELOG.mdunder## [Unreleased]using theAdded / Changed / Fixed / Removedbuckets. Skip entries that don't affect users (internal refactors, test additions, doc polish). - On release: move the
[Unreleased]items under a new## [X.Y.Z] — YYYY-MM-DDheading, bump the version inCargo.toml, tagvX.Y.Z, push. The release workflow (.github/workflows/release.yml) builds the cross-platform archives automatically.
The current state of the changelog is the source of truth for what's shipped vs. what's queued for the next release — read it before claiming a feature exists.
gitree is a Rust CLI that walks a directory tree, finds every git repository under it, and prints a colored tree with each repo's current branch and short HEAD hash. Distributed as a single binary; libgit2 is statically linked via the git2 crate (so no system git is shelled out).
cargo build --release # optimized binary at target/release/gitree
cargo test --release # all unit tests (tempfile-backed integration of git_info)
cargo test --release branch_parser # run a single test by name substring
cargo install --path . --locked # install into ~/.cargo/bin/gitree
cargo clippy --release -- -D warnings # lintRunning locally during dev:
target/release/gitree ~/some/dir # normal scan
target/release/gitree ~ -j 1 # force serial scan (compare against parallel)
target/release/gitree --no-color --no-emoji ~/some/dir # ASCII / pipe-safe outputReleases are tagged v* and .github/workflows/release.yml builds cross-platform archives. Do not bump the version manually unless cutting a release.
The pipeline is scan → render → print, with git_info reads happening lazily during render, not during scan. Two background concerns — terminal feedback and platform-aware parallelism — wrap that core.
main.rs Args parsing, picks thread count, owns the Spinner, drives the pipeline.
├─ scan.rs Recursive directory walk. Returns Option<Node> (a pruned tree where
│ every leaf is a repo). Uses rayon::par_iter when parallel=true,
│ std iter otherwise — no rayon overhead in serial mode.
├─ git_info.rs Per-repo branch / short hash / state via libgit2. Also calls
│ platform::detect for the icon. Called from render, not scan.
├─ platform.rs Best-guess Rust/Go/Node/Next/Flutter/… from a single read_dir()
│ on the repo root. No file contents are read.
├─ render.rs Two-line layout per repo (icon+name line, then status line).
│ Holds the Theme (color/emoji flags) and all paint_* helpers.
└─ progress.rs stderr spinner with a Mutex<String> live status. No-op when
stderr isn't a TTY. Caller updates status via a callback the
scanner invokes on every dir it descends into.
-
Parallelism gating:
main::pick_thread_countdecides serial vs parallel. Default is rayon-default (all cores), except on Linux where a rotational disk is detected via/sys/dev/block/<major>:<minor>/.../queue/rotational— then it falls back to 1. The-j Nflag overrides. Theparallel: boolis threaded throughscan::walkso the serial path can staystd::iter(zero rayon cost). -
Reporter callback is
&(dyn Fn(&Path) + Sync): the scanner calls it on every directory entry, and in parallel mode multiple threads call it concurrently. The Spinner'sMutex<String>already handles that. -
State markers, not dots, for non-normal repos:
Theme::state_markerreturnsNonefor on-branch (normal) and only emits⚠/○/✕for detached / empty / unreadable. The two-line render shows a└─continuation under each repo's name;build_statusdecides whether to append· <hash>(only OnBranch/Detached have meaningful hashes). -
Icon slot is 2 cells:
render::icon_slotpads narrow glyphs like▲(Next.js) so they occupy the same width as wide emoji (🦀, 🐳). Otherwise names misalign across rows. -
scan.rsshould_skipprunes.dotdirsand a fixedDEFAULT_SKIPlist (node_modules,target,dist, …) unless--allis passed. New skips go in that constant. -
git_info::readreturns aRepoInfoeven on error:Unreadable(open failed),Empty(no commits),Detached, orOnBranch. Render must handle all four — don'tunwrapon the state. -
Header / tree color writes go to stdout; spinner writes go to stderr. Don't swap them: piping to a file should produce clean output with no ANSI control sequences from the spinner.
src/git_info.rs has a #[cfg(test)] mod tests that builds real repos in a TempDir via git2 and exercises all four RepoState branches. Tests use Repository::init_opts(initial_head: "main") so they don't depend on the host's init.defaultBranch. Add new tests there using the same commit_empty / init_with_branch helpers.
- Add a variant to
Platforminsrc/platform.rs. - Map it in both
icon_emojiandicon_ascii. - Insert a detection branch in
detect()— order matters (more specific markers above more generic ones; e.g.next.config.*beforepackage.json). - Document it in the README's "Platform icons" tables (English + Korean).
PLAN.md records the UI redesign decisions (two-line layout, branch-before-hash, · separator, no ● for normal state, narrow-glyph padding). Read it before making layout changes — many of those choices come from real tradeoffs (outlier widths, scan-time outliers like (no commits)).