@@ -190,49 +190,118 @@ current_branch() {
190190 printf " %s" " $branch "
191191}
192192
193+ _worktree_record_status () {
194+ local detached=" $1 " locked=" $2 " prunable=" $3 "
195+
196+ if [ " $locked " -eq 1 ]; then
197+ printf " locked"
198+ elif [ " $prunable " -eq 1 ]; then
199+ printf " prunable"
200+ elif [ " $detached " -eq 1 ]; then
201+ printf " detached"
202+ else
203+ printf " ok"
204+ fi
205+ }
206+
207+ _emit_worktree_record () {
208+ local repo_root=" $1 "
209+ local wt_path=" $2 "
210+ local wt_branch=" $3 "
211+ local wt_detached=" $4 "
212+ local wt_locked=" $5 "
213+ local wt_prunable=" $6 "
214+
215+ [ -z " $wt_path " ] && return 0
216+
217+ local is_main=0 branch=" $wt_branch " status
218+ [ " $wt_path " = " $repo_root " ] && is_main=1
219+ [ -z " $branch " ] && branch=" (detached)"
220+ status=$( _worktree_record_status " $wt_detached " " $wt_locked " " $wt_prunable " )
221+
222+ printf " %s\t%s\t%s\t%s\n" " $is_main " " $wt_path " " $branch " " $status "
223+ }
224+
225+ # List registered git worktrees for a repository.
226+ # Usage: list_worktree_records repo_root
227+ # Output: is_main<TAB>path<TAB>branch<TAB>status
228+ list_worktree_records () {
229+ local repo_root=" $1 "
230+ local repo_root_canonical
231+ repo_root_canonical=$( canonicalize_path " $repo_root " || printf " %s" " $repo_root " )
232+
233+ local porcelain_output
234+
235+ porcelain_output=$( git -C " $repo_root " worktree list --porcelain 2> /dev/null) || return 0
236+
237+ local wt_path=" " wt_branch=" " wt_detached=0 wt_locked=0 wt_prunable=0
238+
239+ local line
240+ while IFS= read -r line; do
241+ case " $line " in
242+ " " )
243+ _emit_worktree_record " $repo_root_canonical " " $wt_path " " $wt_branch " " $wt_detached " " $wt_locked " " $wt_prunable "
244+ wt_path=" "
245+ wt_branch=" "
246+ wt_detached=0
247+ wt_locked=0
248+ wt_prunable=0
249+ ;;
250+ " worktree " * )
251+ if [ -n " $wt_path " ]; then
252+ _emit_worktree_record " $repo_root_canonical " " $wt_path " " $wt_branch " " $wt_detached " " $wt_locked " " $wt_prunable "
253+ wt_branch=" "
254+ wt_detached=0
255+ wt_locked=0
256+ wt_prunable=0
257+ fi
258+ wt_path=" ${line# worktree } "
259+ ;;
260+ " branch refs/heads/" * )
261+ wt_branch=" ${line# branch refs/ heads/ } "
262+ ;;
263+ " branch " * )
264+ wt_branch=" ${line# branch } "
265+ ;;
266+ detached)
267+ wt_detached=1
268+ ;;
269+ locked* )
270+ wt_locked=1
271+ ;;
272+ prunable* )
273+ wt_prunable=1
274+ ;;
275+ esac
276+ done << EOF
277+ $porcelain_output
278+ EOF
279+
280+ _emit_worktree_record " $repo_root_canonical " " $wt_path " " $wt_branch " " $wt_detached " " $wt_locked " " $wt_prunable "
281+ }
282+
193283# Get the status of a worktree from git
194284# Usage: worktree_status worktree_path
195285# Returns: status (ok, detached, locked, prunable, or missing)
196286worktree_status () {
197287 local target_path=" $1 "
198- local porcelain_output
199- local in_section=0
288+ local target_path_canonical
289+ target_path_canonical=$( canonicalize_path " $target_path " || printf " %s" " $target_path " )
290+
200291 local status=" ok"
201292 local found=0
293+ local repo_root
294+ repo_root=$( _resolve_main_repo_root) || return 1
202295
203- # Parse git worktree list --porcelain line by line
204- porcelain_output=$( git worktree list --porcelain 2> /dev/null)
205-
206- while IFS= read -r line; do
207- # Check if this is the start of our target worktree
208- if [ " $line " = " worktree $target_path " ]; then
209- in_section=1
296+ local is_main path branch record_status
297+ while IFS=$' \t ' read -r is_main path branch record_status; do
298+ if [ " $path " = " $target_path " ] || [ " $path " = " $target_path_canonical " ]; then
210299 found=1
211- continue
212- fi
213-
214- # If we're in the target section, check for status lines
215- if [ " $in_section " -eq 1 ]; then
216- # Empty line marks end of section
217- if [ -z " $line " ]; then
218- break
219- fi
220-
221- # Check for status indicators (priority: locked > prunable > detached)
222- case " $line " in
223- locked* )
224- status=" locked"
225- ;;
226- prunable* )
227- [ " $status " = " ok" ] && status=" prunable"
228- ;;
229- detached)
230- [ " $status " = " ok" ] && status=" detached"
231- ;;
232- esac
300+ status=" $record_status "
301+ break
233302 fi
234303 done << EOF
235- $porcelain_output
304+ $( list_worktree_records " $repo_root " )
236305EOF
237306
238307 # If worktree not found in git's list
@@ -292,22 +361,15 @@ resolve_target() {
292361 fi
293362
294363 # Last resort: ask git for all worktrees (catches non-gtr-managed worktrees)
295- local wt_path wt_branch
296- while IFS= read -r line; do
297- case " $line " in
298- " worktree " * ) wt_path=" ${line# worktree } " ;;
299- " branch " * )
300- wt_branch=" ${line# branch refs/ heads/ } "
301- if [ " $wt_branch " = " $identifier " ]; then
302- local is_main=0
303- [ " $wt_path " = " $repo_root " ] && is_main=1
304- printf " %s\t%s\t%s\n" " $is_main " " $wt_path " " $wt_branch "
305- return 0
306- fi
307- ;;
308- " " ) wt_path=" " ; wt_branch=" " ;;
309- esac
310- done < <( git -C " $repo_root " worktree list --porcelain 2> /dev/null)
364+ local is_main wt_path wt_branch _wt_status
365+ while IFS=$' \t ' read -r is_main wt_path wt_branch _wt_status; do
366+ if [ " $wt_branch " = " $identifier " ]; then
367+ printf " %s\t%s\t%s\n" " $is_main " " $wt_path " " $wt_branch "
368+ return 0
369+ fi
370+ done << EOF
371+ $( list_worktree_records " $repo_root " )
372+ EOF
311373
312374 log_error " Worktree not found for branch: $identifier "
313375 return 1
@@ -549,13 +611,24 @@ resolve_repo_context() {
549611list_worktree_branches () {
550612 local base_dir=" $1 "
551613 local prefix=" $2 "
552-
553- [ ! -d " $base_dir " ] && return 0
554-
555- for dir in " $base_dir /${prefix} " * ; do
556- [ -d " $dir " ] || continue
557- local branch
558- branch=$( current_branch " $dir " )
559- [ -n " $branch " ] && echo " $branch "
560- done
614+ local repo_root
615+ repo_root=$( _resolve_main_repo_root) || return 0
616+
617+ # base_dir and prefix are kept for the public helper contract. Worktree
618+ # discovery itself comes from Git's registry so nested registered worktrees
619+ # are included and arbitrary parent directories are ignored.
620+ : " $base_dir " " $prefix "
621+
622+ local records
623+ records=$( list_worktree_records " $repo_root " )
624+
625+ local is_main path branch status
626+ while IFS=$' \t ' read -r is_main path branch status; do
627+ [ " $is_main " = " 1" ] && continue
628+ [ -z " $branch " ] && continue
629+ [ " $branch " = " (detached)" ] && continue
630+ printf " %s\n" " $branch "
631+ done << EOF
632+ $records
633+ EOF
561634}
0 commit comments