Merge branch 'pw/worktree-list-display-width-fix'

"git worktree list" attempts to show paths to worktrees while
aligning them, but miscounted display columns for the paths when
non-ASCII characters were involved, which has been corrected.

* pw/worktree-list-display-width-fix:
  worktree list: quote paths
  worktree list: fix column spacing
This commit is contained in:
Junio C Hamano
2025-11-26 10:32:42 -08:00
2 changed files with 53 additions and 25 deletions

View File

@@ -975,14 +975,18 @@ static void show_worktree_porcelain(struct worktree *wt, int line_terminator)
fputc(line_terminator, stdout);
}
static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
struct worktree_display {
char *path;
int width;
};
static void show_worktree(struct worktree *wt, struct worktree_display *display,
int path_maxwidth, int abbrev_len)
{
struct strbuf sb = STRBUF_INIT;
int cur_path_len = strlen(wt->path);
int path_adj = cur_path_len - utf8_strwidth(wt->path);
const char *reason;
strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path);
strbuf_addf(&sb, "%s%*s", display->path, 1 + path_maxwidth - display->width, "");
if (wt->is_bare)
strbuf_addstr(&sb, "(bare)");
else {
@@ -1016,20 +1020,27 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
strbuf_release(&sb);
}
static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen)
static void measure_widths(struct worktree **wt, int *abbrev,
struct worktree_display **d, int *maxwidth)
{
int i;
int i, display_alloc = 0;
struct worktree_display *display = NULL;
struct strbuf buf = STRBUF_INIT;
for (i = 0; wt[i]; i++) {
int sha1_len;
int path_len = strlen(wt[i]->path);
ALLOC_GROW(display, i + 1, display_alloc);
quote_path(wt[i]->path, NULL, &buf, 0);
display[i].width = utf8_strwidth(buf.buf);
display[i].path = strbuf_detach(&buf, NULL);
if (path_len > *maxlen)
*maxlen = path_len;
if (display[i].width > *maxwidth)
*maxwidth = display[i].width;
sha1_len = strlen(repo_find_unique_abbrev(the_repository, &wt[i]->head_oid, *abbrev));
if (sha1_len > *abbrev)
*abbrev = sha1_len;
}
*d = display;
}
static int pathcmp(const void *a_, const void *b_)
@@ -1075,21 +1086,27 @@ static int list(int ac, const char **av, const char *prefix,
die(_("the option '%s' requires '%s'"), "-z", "--porcelain");
else {
struct worktree **worktrees = get_worktrees();
int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i;
int path_maxwidth = 0, abbrev = DEFAULT_ABBREV, i;
struct worktree_display *display = NULL;
/* sort worktrees by path but keep main worktree at top */
pathsort(worktrees + 1);
if (!porcelain)
measure_widths(worktrees, &abbrev, &path_maxlen);
measure_widths(worktrees, &abbrev,
&display, &path_maxwidth);
for (i = 0; worktrees[i]; i++) {
if (porcelain)
show_worktree_porcelain(worktrees[i],
line_terminator);
else
show_worktree(worktrees[i], path_maxlen, abbrev);
show_worktree(worktrees[i],
&display[i], path_maxwidth, abbrev);
}
for (i = 0; display && worktrees[i]; i++)
free(display[i].path);
free(display);
free_worktrees(worktrees);
}
return 0;

View File

@@ -29,23 +29,34 @@ test_expect_success 'rev-parse --git-path objects linked worktree' '
test_cmp expect actual
'
test_expect_success '"list" all worktrees from main' '
test_expect_success '"list" all worktrees from main core.quotepath=false' '
test_config core.quotepath false &&
echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
test_when_finished "rm -rf here out actual expect && git worktree prune" &&
git worktree add --detach here main &&
echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
git worktree list >out &&
sed "s/ */ /g" <out >actual &&
test_when_finished "rm -rf áááá out actual expect && git worktree prune" &&
git worktree add --detach áááá main &&
echo "$(git -C áááá rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
git worktree list >actual &&
test_cmp expect actual
'
test_expect_success '"list" all worktrees from main core.quotepath=true' '
test_config core.quotepath true &&
echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
test_when_finished "rm -rf á out actual expect && git worktree prune" &&
git worktree add --detach á main &&
echo "\"$(git -C á rev-parse --show-toplevel)\" $(git rev-parse --short HEAD) (detached HEAD)" |
sed s/á/\\\\303\\\\241/g >>expect &&
git worktree list >actual &&
test_cmp expect actual
'
test_expect_success '"list" all worktrees from linked' '
test_config core.quotepath false &&
echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
test_when_finished "rm -rf here out actual expect && git worktree prune" &&
git worktree add --detach here main &&
echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
git -C here worktree list >out &&
sed "s/ */ /g" <out >actual &&
test_when_finished "rm -rf áááá out actual expect && git worktree prune" &&
git worktree add --detach áááá main &&
echo "$(git -C áááá rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
git -C áááá worktree list >actual &&
test_cmp expect actual
'