348348 printf " %s" " $status "
349349}
350350
351+ _tsv_escape_field () {
352+ local value=" $1 "
353+ value=${value// \\ / \\\\ }
354+ value=${value// $' \t ' / \\\\ t}
355+ value=${value// $' \n ' / \\\\ n}
356+ printf " %s" " $value "
357+ }
358+
359+ _tsv_unescape_field () {
360+ local value=" $1 " out=" " char next
361+
362+ while [ -n " $value " ]; do
363+ char=" ${value: 0: 1} "
364+ value=" ${value: 1} "
365+
366+ if [ " $char " = " \\ " ]; then
367+ if [ -z " $value " ]; then
368+ out=" ${out} \\ "
369+ break
370+ fi
371+
372+ next=" ${value: 0: 1} "
373+ value=" ${value: 1} "
374+ case " $next " in
375+ t)
376+ out=" ${out} " $' \t '
377+ ;;
378+ n)
379+ out=" ${out} " $' \n '
380+ ;;
381+ " \\ " )
382+ out=" ${out} \\ "
383+ ;;
384+ * )
385+ out=" ${out} \\ ${next} "
386+ ;;
387+ esac
388+ else
389+ out=" ${out}${char} "
390+ fi
391+ done
392+
393+ printf " %s" " $out "
394+ }
395+
396+ _print_resolved_target () {
397+ local is_main=" $1 " path=" $2 " branch=" $3 "
398+ printf " %s\t%s\t%s\n" " $is_main " " $( _tsv_escape_field " $path " ) " " $( _tsv_escape_field " $branch " ) "
399+ }
400+
351401# Resolve a worktree target from branch name or special ID '1' for main repo
352402# Usage: resolve_target identifier repo_root base_dir prefix
353- # Returns: tab-separated "is_main\tpath\tbranch" on success (is_main: 1 for main repo, 0 for worktrees)
403+ # Returns: tab-separated "is_main\tpath\tbranch" with escaped fields on success (is_main: 1 for main repo, 0 for worktrees)
354404# Exit code: 0 on success, 1 if not found
355405resolve_target () {
356406 local identifier=" $1 "
@@ -363,15 +413,15 @@ resolve_target() {
363413 if [ " $identifier " = " 1" ]; then
364414 path=" $repo_root "
365415 branch=$( get_current_branch " $repo_root " )
366- printf " 1\t%s\t%s\n " " $path " " $branch "
416+ _print_resolved_target " 1" " $path " " $branch "
367417 return 0
368418 fi
369419
370420 # For all other identifiers, treat as branch name
371421 # First check if it's the current branch in repo root (if not ID 1)
372422 branch=$( get_current_branch " $repo_root " )
373423 if [ " $branch " = " $identifier " ]; then
374- printf " 1\t%s\t%s\n " " $repo_root " " $identifier "
424+ _print_resolved_target " 1" " $repo_root " " $identifier "
375425 return 0
376426 fi
377427
@@ -380,7 +430,7 @@ resolve_target() {
380430 path=" $base_dir /${prefix}${sanitized_name} "
381431 if [ -d " $path " ]; then
382432 branch=$( current_branch " $path " )
383- printf " 0\t%s\t%s\n " " $path " " $branch "
433+ _print_resolved_target " 0" " $path " " $branch "
384434 return 0
385435 fi
386436
@@ -390,7 +440,7 @@ resolve_target() {
390440 [ -d " $dir " ] || continue
391441 branch=$( current_branch " $dir " )
392442 if [ " $branch " = " $identifier " ]; then
393- printf " 0\t%s\t%s\n " " $dir " " $branch "
443+ _print_resolved_target " 0" " $dir " " $branch "
394444 return 0
395445 fi
396446 done
@@ -402,7 +452,7 @@ resolve_target() {
402452 case " $line " in
403453 " " )
404454 if [ " $wt_branch " = " $identifier " ]; then
405- printf " %s\t%s\t%s\n " " $is_main " " $wt_path " " $wt_branch "
455+ _print_resolved_target " $is_main " " $wt_path " " $wt_branch "
406456 return 0
407457 fi
408458 is_main=" "
@@ -424,7 +474,7 @@ $(list_worktree_records "$repo_root")
424474EOF
425475
426476 if [ " $wt_branch " = " $identifier " ]; then
427- printf " %s\t%s\t%s\n " " $is_main " " $wt_path " " $wt_branch "
477+ _print_resolved_target " $is_main " " $wt_path " " $wt_branch "
428478 return 0
429479 fi
430480
436486# Sets: _ctx_is_main, _ctx_worktree_path, _ctx_branch
437487# Usage: unpack_target "$target_string"
438488unpack_target () {
489+ local escaped_path escaped_branch
439490 local IFS=$' \t '
440491 # shellcheck disable=SC2162
441- read _ctx_is_main _ctx_worktree_path _ctx_branch <<< " $1"
492+ read _ctx_is_main escaped_path escaped_branch <<< " $1"
493+ _ctx_worktree_path=$( _tsv_unescape_field " $escaped_path " )
494+ _ctx_branch=$( _tsv_unescape_field " $escaped_branch " )
442495}
443496
444497# Resolve an identifier to a worktree and set _ctx_* variables in one step
0 commit comments