Skip to content

Use registered worktree inventory#178

Merged
helizaga merged 7 commits intomainfrom
codex/worktree-inventory-list
May 5, 2026
Merged

Use registered worktree inventory#178
helizaga merged 7 commits intomainfrom
codex/worktree-inventory-list

Conversation

@helizaga
Copy link
Copy Markdown
Collaborator

@helizaga helizaga commented May 4, 2026

Summary

  • add a shared git worktree list --porcelain inventory helper for registered worktrees
  • make git gtr list consume that inventory so nested worktrees are shown by their real path and fake parent rows disappear
  • use the same inventory for branch enumeration, resolve fallback, and clean --merged iteration

Fixes #177.

Validation

  • git diff --check
  • shellcheck bin/gtr bin/git-gtr lib/*.sh lib/commands/*.sh adapters/editor/*.sh adapters/ai/*.sh
  • ./scripts/generate-completions.sh --check
  • bats tests/cmd_list.bats tests/core_resolve_target.bats tests/integration_lifecycle.bats tests/cmd_copy.bats tests/cmd_clean.bats
  • bats tests/

Summary by CodeRabbit

  • New Features

    • More robust registry-based detection of worktrees, including nested/external paths; branch listings now omit detached entries and preserve TSV-escaped fields.
  • Bug Fixes

    • Deterministic sorting and clearer status reporting (ok/locked/prunable/detached). Safer removal of merged worktrees while protecting main/active worktrees and honoring skip/force/yes flows, including paths with tabs/newlines.
  • Tests

    • Expanded unit and integration tests for listing, cleaning, copying, resolution, and branch listing with nested and externally-created worktrees.

@helizaga helizaga requested a review from NatoBoram as a code owner May 4, 2026 21:23
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 7db3a956-2f71-47e7-ac28-e9430893c29b

📥 Commits

Reviewing files that changed from the base of the PR and between f3c1b37 and b230dd7.

📒 Files selected for processing (4)
  • lib/commands/clean.sh
  • lib/commands/list.sh
  • lib/core.sh
  • tests/core_resolve_target.bats
✅ Files skipped from review due to trivial changes (1)
  • lib/commands/clean.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/core_resolve_target.bats
  • lib/commands/list.sh

Walkthrough

Replaces ad-hoc worktree discovery with a structured registry produced by list_worktree_records(repo_root) and updates core helpers (worktree_status, resolve_target, unpack_target, list_worktree_branches) and commands (list, clean, copy) to consume that registry; tests added/updated for nested, escaped, detached, and locked worktrees.

Changes

Worktree Records & Command Consumers

Layer / File(s) Summary
Data shape / primitives
lib/core.sh
Adds _worktree_record_status() and _emit_worktree_record(); implements list_worktree_records(repo_root) that parses git worktree list --porcelain into blank-line-delimited TSV records with is_main, path, branch, and status (`ok
Core behavior / Consumers
lib/core.sh
Rewrites worktree_status() to canonicalize target paths and find matching entries by scanning list_worktree_records; refactors resolve_target() to route successful resolutions through _print_resolved_target() and to fallback by scanning records; changes unpack_target() to read escaped TSV into locals then unescape into _ctx_*; list_worktree_branches(base_dir,prefix,[repo_root]) now derives branches from records (excludes main and (detached)).
Command wiring — list
lib/commands/list.sh
cmd_list now consumes list_worktree_records for both --porcelain and human output: emits is_main=1 rows immediately, accumulates non-main rows, sorts deterministically (LC_ALL=C sort) and prints sorted TSV or formatted table.
Command wiring — clean
lib/commands/clean.sh
_clean_merged replaces glob-based enumeration with list_worktree_records "$repo_root" read-loop (while IFS=$'\t' read ...) parsing is_main,path,branch; retains merge-check, _clean_should_skip, hooks, dry-run/prompt/--yes removal flow; adds no-op : "$base_dir" "$prefix" reference to satisfy helper contract.
Command wiring — copy
lib/commands/copy.sh
cmd_copy --all now calls list_worktree_branches "$base_dir" "$prefix" "$repo_root" (passes repo_root through) for branch discovery.
Tests / Integration
tests/*.bats, tests/integration_lifecycle.bats
Adds/updates tests exercising nested externally-created worktrees, TSV-escaping (tabs/newlines/backslashes), detached/locked/prunable states, cmd_list porcelain/human outputs, cmd_clean --merged removal of nested registered worktrees, cmd_copy --all nested-target copying, and list_worktree_branches integration cases.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User as User (gtr)
    participant Cmd as Command (list/clean/copy)
    participant Core as Core helper (list_worktree_records)
    participant Git as Git CLI
    participant FS as Filesystem

    User->>Cmd: invoke "gtr list / clean --merged / copy --all"
    Cmd->>Core: request worktree records (repo_root)
    Core->>Git: git worktree list --porcelain
    Git-->>Core: porcelain output
    Core->>Core: parse porcelain -> emit TSV records (is_main,path,branch,status)
    Core-->>Cmd: records stream (tab/blank-delimited)
    Cmd->>Cmd: filter, sort, present or decide removal
    Cmd->>Git: (on remove) git worktree remove / git branch delete
    Cmd->>FS: remove worktree path (preRemove/postRemove hooks)
    Cmd-->>User: printed table / dry-run / confirmation / result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through porcelain, chased each hidden trail,
Nested branches surfaced where plain paths would fail.
Tabs and newlines kept safe under my paw,
Hooks thump and pruners tidy up what they saw.
A carrot for every record well-trailed.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Use registered worktree inventory' directly and clearly describes the main architectural change: implementing a shared inventory system for parsing registered worktrees.
Linked Issues check ✅ Passed The PR fully addresses issue #177 by implementing a worktree inventory system that surfaces nested worktrees individually with their real paths and branches, eliminating the collapsed (detached) parent directory behavior.
Out of Scope Changes check ✅ Passed All code changes are scoped to the worktree inventory system: new list_worktree_records function, updates to list/clean/copy commands, and corresponding test coverage—nothing extraneous.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/worktree-inventory-list

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/core.sh`:
- Around line 225-228: list_worktree_records currently emits raw tab-delimited
fields so a path containing a literal TAB corrupts parsing; change
list_worktree_records to output a safe, NUL-delimited format (emit each field
with printf '%s\0' per record) instead of raw TSV, and update all callers that
parse its output with IFS=$'\t' to read NUL-delimited fields (use read -r -d ''
or while IFS= read -r -d '' ...) so paths with spaces/special chars/tabs are
preserved; reference the list_worktree_records function and any callers that use
IFS=$'\t' when making the changes.
- Around line 297-299: The loop compares raw 'path' against normalized
'target_path' but never canonicalizes 'path', causing mismatches for symlinked
or differently-typed paths; inside the while-read loop (the block that reads
is_main, path, branch, record_status from list_worktree_records) produce a
canonical form of path (e.g., path_canonical via realpath/readlink -f or an
existing canonicalize helper) and then compare that canonicalized value against
target_path_canonical (and/or target_path) instead of the raw 'path' so
equivalent paths match correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 1235dc7d-8ffa-4e83-8549-903190f0734a

📥 Commits

Reviewing files that changed from the base of the PR and between 32dc340 and c88943f.

📒 Files selected for processing (8)
  • lib/commands/clean.sh
  • lib/commands/list.sh
  • lib/core.sh
  • tests/cmd_clean.bats
  • tests/cmd_copy.bats
  • tests/cmd_list.bats
  • tests/core_resolve_target.bats
  • tests/integration_lifecycle.bats

Comment thread lib/core.sh Outdated
Comment thread lib/core.sh Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/core.sh`:
- Around line 668-675: The helper list_worktree_branches currently calls
_resolve_main_repo_root and ignores the caller-supplied base_dir/prefix, so
change list_worktree_branches to accept a repo_root parameter (e.g., repo_root)
instead of resolving it internally, remove the internal call to
_resolve_main_repo_root, and use the passed repo_root for worktree discovery;
then update all callers that already have repo_root (or can compute it once via
_resolve_main_repo_root) to pass that value into list_worktree_branches so the
function no longer depends on the current working directory.
- Around line 217-220: Canonicalize wt_path before comparing to repo_root so
symlinked or non-canonical paths match the main worktree: compute a canonical
form of wt_path (e.g., via realpath/readlink -f or cd "$wt_path"; pwd -P) into a
temporary variable and use that when setting is_main=1 (replace the direct
comparison [ "$wt_path" = "$repo_root" ] with a comparison against the
canonicalized path); leave _worktree_record_status and other variables
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8ecfb560-9718-4af6-b381-e025670d62a5

📥 Commits

Reviewing files that changed from the base of the PR and between c88943f and 1f9a0f7.

📒 Files selected for processing (4)
  • lib/commands/clean.sh
  • lib/commands/list.sh
  • lib/core.sh
  • tests/core_resolve_target.bats
✅ Files skipped from review due to trivial changes (1)
  • tests/core_resolve_target.bats
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/commands/clean.sh
  • lib/commands/list.sh

Comment thread lib/core.sh Outdated
Comment thread lib/core.sh Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
lib/core.sh (1)

399-429: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t pass raw worktree paths back through TSV here.

This fallback now parses tab-safe records, but it returns wt_path inside a tab-delimited triple again. Any registered worktree whose path contains a tab will be parsed correctly by list_worktree_records() and then broken here when unpack_target() splits on IFS=$'\t', corrupting _ctx_worktree_path and _ctx_branch for every resolve_worktree() caller. Use the same structured handoff as the inventory code, or escape/unescape the path before crossing the resolve_target/unpack_target boundary. As per coding guidelines, "**/*.sh: Always quote paths to handle spaces and special characters; avoid unguarded globbing".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/core.sh` around lines 399 - 429, The code returns raw wt_path in a
tab-delimited triple which breaks unpack_target() (and thus resolve_worktree())
when paths contain tabs; before printing the triple from this block (and from
list_worktree_records handoff), escape any literal tab characters in wt_path
(e.g. replace $'\t' with a safe escape like \\t) and ensure you quote variables
in the printf, and then update unpack_target() to unescape \\t back to a real
tab when parsing into _ctx_worktree_path/_ctx_branch; alternatively adopt the
same structured handoff used by the inventory code (escape/unescape or a
different delimiter) so that list_worktree_records, resolve_worktree, and
unpack_target all agree on the encoding for wt_path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@lib/core.sh`:
- Around line 399-429: The code returns raw wt_path in a tab-delimited triple
which breaks unpack_target() (and thus resolve_worktree()) when paths contain
tabs; before printing the triple from this block (and from list_worktree_records
handoff), escape any literal tab characters in wt_path (e.g. replace $'\t' with
a safe escape like \\t) and ensure you quote variables in the printf, and then
update unpack_target() to unescape \\t back to a real tab when parsing into
_ctx_worktree_path/_ctx_branch; alternatively adopt the same structured handoff
used by the inventory code (escape/unescape or a different delimiter) so that
list_worktree_records, resolve_worktree, and unpack_target all agree on the
encoding for wt_path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 0cc6af4f-7537-4007-b2c5-e14cdb52280e

📥 Commits

Reviewing files that changed from the base of the PR and between 8a3fb79 and e97652c.

📒 Files selected for processing (3)
  • lib/commands/copy.sh
  • lib/core.sh
  • tests/integration_lifecycle.bats
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/integration_lifecycle.bats

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/core.sh`:
- Around line 351-357: The escape routine _tsv_escape_field is producing
double-escaped sequences for tabs/newlines (\\\\t/\\\\n) so unescape yields
backslash+letter instead of actual control chars; change the replacements to
produce single backslash escapes that the unescape logic expects: keep the
backslash-first replace (value=${value//\\/\\\\}) but replace tabs with $'\\t'
(value=${value//$'\t'/$'\\t'}) and newlines with $'\\n'
(value=${value//$'\n'/$'\\n'}) so the produced sequences are "\t" and "\n" and
roundtrip correctly with the corresponding unescape implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: fc33283a-e1f7-491b-9cb3-8a05755a1537

📥 Commits

Reviewing files that changed from the base of the PR and between e97652c and 30a2627.

📒 Files selected for processing (2)
  • lib/core.sh
  • tests/core_resolve_target.bats

Comment thread lib/core.sh
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
lib/core.sh (1)

351-357: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

TSV escape produces double-escaped sequences, breaking roundtrip.

The escape replacements use \\\\t/\\\\n which in bash produces \\t/\\n (two backslashes + letter). The unescape function expects single-escaped \t/\n. This causes tab characters to become literal \t after roundtrip, corrupting paths containing tabs.

Proposed fix
 _tsv_escape_field() {
   local value="$1"
   value=${value//\\/\\\\}
-  value=${value//$'\t'/\\\\t}
-  value=${value//$'\n'/\\\\n}
+  value=${value//$'\t'/\\t}
+  value=${value//$'\n'/\\n}
   printf "%s" "$value"
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/core.sh` around lines 351 - 357, _tsF function _tsv_escape_field
currently replaces tabs/newlines with four-backslash sequences (\\\\t/\\\\n)
producing literal backslash-backslash-t in output; change the replacements to
insert a single backslash plus letter by using ANSI-C quoted replacements:
replace value=${value//$'\t'/\\\\t} with value=${value//$'\t'/$'\\t'} and
value=${value//$'\n'/\\\\n} with value=${value//$'\n'/$'\\n'} so
_tsv_escape_field produces \t and \n (single-escaped) that match the unescape
logic; keep the backslash escaping value=${value//\\/\\\\} as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@lib/core.sh`:
- Around line 351-357: _tsF function _tsv_escape_field currently replaces
tabs/newlines with four-backslash sequences (\\\\t/\\\\n) producing literal
backslash-backslash-t in output; change the replacements to insert a single
backslash plus letter by using ANSI-C quoted replacements: replace
value=${value//$'\t'/\\\\t} with value=${value//$'\t'/$'\\t'} and
value=${value//$'\n'/\\\\n} with value=${value//$'\n'/$'\\n'} so
_tsv_escape_field produces \t and \n (single-escaped) that match the unescape
logic; keep the backslash escaping value=${value//\\/\\\\} as-is.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8c8b716e-788a-41b9-baf0-15a713571abb

📥 Commits

Reviewing files that changed from the base of the PR and between e97652c and 30a2627.

📒 Files selected for processing (2)
  • lib/core.sh
  • tests/core_resolve_target.bats
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/core_resolve_target.bats

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/core.sh`:
- Around line 225-228: The printed worktree inventory currently emits the raw
wt_path (printf "path %s\n" "$wt_path") which breaks parsing if the path
contains newlines; update the exporter (function/list_worktree_records or the
block producing "path ...") to escape the path using the existing TSV escaper
(e.g. call _tsv_escape_field on wt_path) before printing, ensure all
printf/printf-like calls quote the variable, and then update consumers/parsers
to unescape the parsed path (use _tsv_unescape_field on the value extracted via
${line#path } in list, clean, resolve_target, and worktree_status) so the
transport is safe end-to-end.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9c639e7d-ddba-48f1-b12c-d783f54874c8

📥 Commits

Reviewing files that changed from the base of the PR and between 30a2627 and f3c1b37.

📒 Files selected for processing (2)
  • lib/core.sh
  • tests/core_resolve_target.bats
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/core_resolve_target.bats

Comment thread lib/core.sh
@helizaga helizaga merged commit 7903bf4 into main May 5, 2026
4 checks passed
@helizaga helizaga deleted the codex/worktree-inventory-list branch May 5, 2026 02:52
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.

gtr ls does not show worktrees in nested subdirectories created outside of gtr

1 participant