Skip to content

feat: detect git worktrees (opt-in toggle, #24)#25

Open
steven-pribilinskiy wants to merge 3 commits into
Bharath-code:mainfrom
steven-pribilinskiy:feat/worktrees-support
Open

feat: detect git worktrees (opt-in toggle, #24)#25
steven-pribilinskiy wants to merge 3 commits into
Bharath-code:mainfrom
steven-pribilinskiy:feat/worktrees-support

Conversation

@steven-pribilinskiy
Copy link
Copy Markdown

@steven-pribilinskiy steven-pribilinskiy commented May 10, 2026

Closes #24.

image

Summary

Adds opt-in support for linked git worktrees. Today the scanner only matches .git directories, so worktrees (where .git is a file containing a gitdir: pointer) are silently skipped — including any uncommitted changes in them.

A single keybinding controls everything: visibility, the 📁 N repos count, and the dirty/clean totals all flip together so the table and stats bar stay consistent.

Changes

  • Config (config.yml): new includeWorktrees: false (default).
  • CLI: --worktrees flag for one-shot enable. Overrides the config value when set.
  • TUI: uppercase W toggles live (lowercase w already does workspace switch). Toggle re-runs the scan and updates totals atomically.
  • Model (internal/model.Repo): new IsWorktree bool field (omitempty in JSON for back-compat with downstream git-scope scan consumers).
  • Scanner (internal/scan):
    • New ScanRootsWithOptions(roots, ignore, includeWorktrees). The old ScanRoots is kept as a thin wrapper.
    • When the toggle is on, .git files are read; only entries whose gitdir: path contains /worktrees/ are accepted. Submodules (gitdir: under .git/modules/) are filtered out — including them would inflate the dashboard with content already nested under a parent repo.
  • TUI rendering: worktree rows get a marker in the Repository column. Stats bar shows 📁 N repos (M wt) when the toggle is on.
  • Cache (internal/cache): cache validity now considers the toggle, so flipping it forces a fresh scan instead of serving the stale opposite set.
  • Docs: README keybindings table + config example, CHANGELOG Unreleased entry.

Why opt-in

  • Default behaviour is unchanged — no surprise count changes for current users.
  • Worktree-heavy and worktree-light workflows are both common; a toggle (config + CLI flag + TUI key) lets each user pick a default and override per-session.

Test plan

Tested against ~/projects/cloudbeds/tools which contains an automation-hub.worktrees/ directory with 21 worktrees alongside 10 regular repos.

$ git-scope scan ~/projects/cloudbeds/tools | jq length
10

$ git-scope --worktrees scan ~/projects/cloudbeds/tools \
    | jq '[length, (map(select(.is_worktree)) | length)]'
[31, 21]

Each worktree's branch field is correctly resolved by git status --porcelain=v2 from inside the worktree path — no changes needed in internal/gitstatus.

Manual TUI checks (run before/after):

  • Launch with default config → no worktrees, totals match scan output.
  • Press W → status line shows "Including worktrees — rescanning…", table grows, 📁 badge shows (N wt), dirty/clean counts include worktrees.
  • Press W again → reverts cleanly. Cache check catches the toggle change so the rescan is real, not a cached return.
  • --worktrees flag and includeWorktrees: true in config both produce the same starting state; W still flips at runtime.

Notes

  • Scope is intentionally narrow: detection + display + toggle. No changes to actions (open in editor, rescan, etc.) — they already work on any path that git status understands, and they do.
  • The marker is one rune wide and reuses the existing 18-char Repository column; existing layout is preserved.

Linked git worktrees were previously skipped because the scanner only
matched on `.git` directories — a worktree's `.git` is a regular file
containing a `gitdir:` pointer.

This change adds opt-in support so worktree-heavy workflows can see them
in the dashboard alongside regular repos.

- config: `includeWorktrees: false` (default) in config.yml
- CLI: `--worktrees` flag for one-shot enable
- TUI: uppercase `W` toggles live; flips visibility AND totals together
- model: new `is_worktree` field on `Repo` (omitempty)
- TUI: worktrees render with a `⎇` marker in the Repository column,
  and the stats bar shows `📁 N repos (M wt)` when the toggle is on
- scan: filters submodules out (gitdir under `.git/modules/`); only
  `gitdir:` paths under `.git/worktrees/` are accepted
- cache: include the toggle in the cache key so flipping it forces a
  fresh scan instead of returning the wrong set
@kilo-code-bot
Copy link
Copy Markdown

kilo-code-bot Bot commented May 10, 2026

Kilo Code Review could not run — your account is out of credits.

Add credits or switch to a free model to enable reviews on this change.

Two changes that together make the W toggle feel snappy.

1) Parallel git status during scan.

The scanner walked roots in parallel but ran `git status --porcelain=v2 -b`
and `git log -1` serially within each root. On a 1058-repo tree that's
~2,100 subprocess forks on a sequential path — measured at 3.6s on a
typical config.

Split scan into two phases:
- Walk: discover repos in parallel by root (cheap; just dir traversal).
- Resolve: fan out gitstatus.Status across runtime.NumCPU()*2 workers.

Same tree now scans in ~1.1s — ~3x faster, bounded by fork/IO contention
rather than serialisation. No correctness change; results are unordered
but the TUI sorts separately.

2) Instant in-memory worktree toggle.

Previously every `W` press triggered a full rescan, paying the cold-scan
cost (~1s now, was ~3.6s) every time — even when toggling OFF, where the
data we already have is a strict superset of what we want to display.

Track whether the last scan included worktrees on the model. On toggle:
- Fast path (in-memory filter): we already have the data we need —
  either we're hiding worktrees we already scanned, or we don't need
  worktrees at all. Instant.
- Slow path (rescan): only when we need worktrees and the current scan
  doesn't have them. Once paid, all subsequent toggles in this session
  are instant.

`applyFilter` and `renderStats` now treat the worktree toggle as part of
the base set, so totals + dirty/clean counts stay consistent with the
table when you flip it.
The W toggle was forgotten on relaunch — startup always read
cfg.IncludeWorktrees from config.yml, so any in-session toggle was lost.

Add a small JSON state file at ~/.config/git-scope/state.json that holds
the runtime toggle. Resolution order, low → high precedence:

  1. Config file (`includeWorktrees:` in config.yml)
  2. State file (last W toggle from previous session)
  3. CLI flag (`--worktrees` for one-shot override)

Kept separate from config.yml on purpose — yaml.Marshal would clobber
the user's comments on every toggle. State writes are best-effort; a
permission failure doesn't break the in-session toggle.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: detect git worktrees (opt-in toggle)

1 participant