worktree list: quote paths

If a worktree path contains newlines or other control characters it messes up the output of "git worktree list". Fix this by using quote_path() to display the worktree path. The output of "git worktree list" is designed for human consumption, scripts should be using the "--porcelain" option so this change should not break them. Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Phillip Wood committed Nov 18, 2025 at 16:07 UTC 08dfa5983572645ae7cc51b49cadfdf216ecfec6
2 files changed +22 -3
builtin/worktree.c
+8 -2
@@ -980,6 +980,7 @@ static void show_worktree_porcelain(struct worktree *wt, int line_terminator)
980 }
981
982 struct worktree_display {
983 + char *path;
984 int width;
985 };
986
@@ -989,7 +990,7 @@ static void show_worktree(struct worktree *wt, struct worktree_display *display,
990 struct strbuf sb = STRBUF_INIT;
991 const char *reason;
992
992 - strbuf_addf(&sb, "%s%*s", wt->path, 1 + path_maxwidth - display->width, "");
993 + strbuf_addf(&sb, "%s%*s", display->path, 1 + path_maxwidth - display->width, "");
994 if (wt->is_bare)
995 strbuf_addstr(&sb, "(bare)");
996 else {
@@ -1028,11 +1029,14 @@ static void measure_widths(struct worktree **wt, int *abbrev,
1029 {
1030 int i, display_alloc = 0;
1031 struct worktree_display *display = NULL;
1032 + struct strbuf buf = STRBUF_INIT;
1033
1034 for (i = 0; wt[i]; i++) {
1035 int sha1_len;
1036 ALLOC_GROW(display, i + 1, display_alloc);
1035 - display[i].width = utf8_strwidth(wt[i]->path);
1037 + quote_path(wt[i]->path, NULL, &buf, 0);
1038 + display[i].width = utf8_strwidth(buf.buf);
1039 + display[i].path = strbuf_detach(&buf, NULL);
1040
1041 if (display[i].width > *maxwidth)
1042 *maxwidth = display[i].width;
@@ -1104,6 +1108,8 @@ static int list(int ac, const char **av, const char *prefix,
1108 show_worktree(worktrees[i],
1109 &display[i], path_maxwidth, abbrev);
1110 }
1111 + for (i = 0; display && worktrees[i]; i++)
1112 + free(display[i].path);
1113 free(display);
1114 free_worktrees(worktrees);
1115 }
t/t2402-worktree-list.sh
+14 -1
@@ -29,7 +29,8 @@ test_expect_success 'rev-parse --git-path objects linked worktree' '
29 test_cmp expect actual
30 '
31
32 -test_expect_success '"list" all worktrees from main' '
32 +test_expect_success '"list" all worktrees from main core.quotepath=false' '
33 + test_config core.quotepath false &&
34 echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
35 test_when_finished "rm -rf áááá out actual expect && git worktree prune" &&
36 git worktree add --detach áááá main &&
@@ -38,7 +39,19 @@ test_expect_success '"list" all worktrees from main' '
39 test_cmp expect actual
40 '
41
42 +test_expect_success '"list" all worktrees from main core.quotepath=true' '
43 + test_config core.quotepath true &&
44 + echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
45 + test_when_finished "rm -rf á out actual expect && git worktree prune" &&
46 + git worktree add --detach á main &&
47 + echo "\"$(git -C á rev-parse --show-toplevel)\" $(git rev-parse --short HEAD) (detached HEAD)" |
48 + sed s/á/\\\\303\\\\241/g >>expect &&
49 + git worktree list >actual &&
50 + test_cmp expect actual
51 +'
52 +
53 test_expect_success '"list" all worktrees from linked' '
54 + test_config core.quotepath false &&
55 echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
56 test_when_finished "rm -rf áááá out actual expect && git worktree prune" &&
57 git worktree add --detach áááá main &&