Skip to content

Commit 30a2627

Browse files
committed
Address CodeRabbit review: escape resolved paths
1 parent e97652c commit 30a2627

2 files changed

Lines changed: 74 additions & 8 deletions

File tree

lib/core.sh

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -348,9 +348,59 @@ EOF
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
355405
resolve_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")
424474
EOF
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

@@ -436,9 +486,12 @@ EOF
436486
# Sets: _ctx_is_main, _ctx_worktree_path, _ctx_branch
437487
# Usage: unpack_target "$target_string"
438488
unpack_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

tests/core_resolve_target.bats

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ teardown() {
7676
[ "$_ctx_branch" = "ctx-test" ]
7777
}
7878

79+
@test "resolve_worktree preserves tab in externally registered worktree path" {
80+
local tab_path="${TEST_REPO}-external"$'\t'"tab"
81+
git -C "$TEST_REPO" worktree add "$tab_path" -b resolve-tab-path --quiet
82+
local expected_path
83+
expected_path=$(cd "$tab_path" && pwd -P)
84+
85+
resolve_worktree "resolve-tab-path" "$TEST_REPO" "$TEST_WORKTREES_DIR" ""
86+
87+
[ "$_ctx_is_main" = "0" ]
88+
[ "$_ctx_worktree_path" = "$expected_path" ]
89+
[ "$_ctx_branch" = "resolve-tab-path" ]
90+
}
91+
7992
@test "resolve_worktree returns 1 for unknown branch" {
8093
run resolve_worktree "nope" "$TEST_REPO" "$TEST_WORKTREES_DIR" ""
8194
[ "$status" -eq 1 ]

0 commit comments

Comments
 (0)