Skip to content

Commit a6238ee

Browse files
phillipwoodgitster
authored andcommitted
worktree list: fix column spacing
The output of "git worktree list" displays a table containing the worktree path, HEAD OID and branch name for each worktree. The code aligns the columns by measuring the visual width of the worktree path when it is printed. Unfortunately it fails to use the visual width when calculating the width of the column so, if any of the paths contain a multibyte character, we can end up with excess padding between columns. The simplest fix would be to replace strlen() with utf8_strwidth() in measure_widths(). However that leaves us measuring the visual width twice and the byte length once. By caching the visual width and printing the padding separately to the worktree path, we only need to calculate the visual width once and do not need the byte length at all. The visual widths are stored in an arrays of structs rather than an array of ints as the next commit will add more struct members. Even if there are no multibyte characters in any of the paths we still print an extra space between the path and the object id as the field width is calculated as one plus the length of the path and we print an explicit space as well. This is fixed by not printing the extra space. The tests are updated to include multibyte characters in one of the worktree paths and to check the spacing of the columns. Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent bb5c624 commit a6238ee

File tree

2 files changed

+33
-24
lines changed

2 files changed

+33
-24
lines changed

builtin/worktree.c

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -979,14 +979,17 @@ static void show_worktree_porcelain(struct worktree *wt, int line_terminator)
979979
fputc(line_terminator, stdout);
980980
}
981981

982-
static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
982+
struct worktree_display {
983+
int width;
984+
};
985+
986+
static void show_worktree(struct worktree *wt, struct worktree_display *display,
987+
int path_maxwidth, int abbrev_len)
983988
{
984989
struct strbuf sb = STRBUF_INIT;
985-
int cur_path_len = strlen(wt->path);
986-
int path_adj = cur_path_len - utf8_strwidth(wt->path);
987990
const char *reason;
988991

989-
strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path);
992+
strbuf_addf(&sb, "%s%*s", wt->path, 1 + path_maxwidth - display->width, "");
990993
if (wt->is_bare)
991994
strbuf_addstr(&sb, "(bare)");
992995
else {
@@ -1020,20 +1023,24 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
10201023
strbuf_release(&sb);
10211024
}
10221025

1023-
static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen)
1026+
static void measure_widths(struct worktree **wt, int *abbrev,
1027+
struct worktree_display **d, int *maxwidth)
10241028
{
1025-
int i;
1029+
int i, display_alloc = 0;
1030+
struct worktree_display *display = NULL;
10261031

10271032
for (i = 0; wt[i]; i++) {
10281033
int sha1_len;
1029-
int path_len = strlen(wt[i]->path);
1034+
ALLOC_GROW(display, i + 1, display_alloc);
1035+
display[i].width = utf8_strwidth(wt[i]->path);
10301036

1031-
if (path_len > *maxlen)
1032-
*maxlen = path_len;
1037+
if (display[i].width > *maxwidth)
1038+
*maxwidth = display[i].width;
10331039
sha1_len = strlen(repo_find_unique_abbrev(the_repository, &wt[i]->head_oid, *abbrev));
10341040
if (sha1_len > *abbrev)
10351041
*abbrev = sha1_len;
10361042
}
1043+
*d = display;
10371044
}
10381045

10391046
static int pathcmp(const void *a_, const void *b_)
@@ -1079,21 +1086,25 @@ static int list(int ac, const char **av, const char *prefix,
10791086
die(_("the option '%s' requires '%s'"), "-z", "--porcelain");
10801087
else {
10811088
struct worktree **worktrees = get_worktrees();
1082-
int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i;
1089+
int path_maxwidth = 0, abbrev = DEFAULT_ABBREV, i;
1090+
struct worktree_display *display = NULL;
10831091

10841092
/* sort worktrees by path but keep main worktree at top */
10851093
pathsort(worktrees + 1);
10861094

10871095
if (!porcelain)
1088-
measure_widths(worktrees, &abbrev, &path_maxlen);
1096+
measure_widths(worktrees, &abbrev,
1097+
&display, &path_maxwidth);
10891098

10901099
for (i = 0; worktrees[i]; i++) {
10911100
if (porcelain)
10921101
show_worktree_porcelain(worktrees[i],
10931102
line_terminator);
10941103
else
1095-
show_worktree(worktrees[i], path_maxlen, abbrev);
1104+
show_worktree(worktrees[i],
1105+
&display[i], path_maxwidth, abbrev);
10961106
}
1107+
free(display);
10971108
free_worktrees(worktrees);
10981109
}
10991110
return 0;

t/t2402-worktree-list.sh

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,20 @@ test_expect_success 'rev-parse --git-path objects linked worktree' '
3030
'
3131

3232
test_expect_success '"list" all worktrees from main' '
33-
echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
34-
test_when_finished "rm -rf here out actual expect && git worktree prune" &&
35-
git worktree add --detach here main &&
36-
echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
37-
git worktree list >out &&
38-
sed "s/ */ /g" <out >actual &&
33+
echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
34+
test_when_finished "rm -rf áááá out actual expect && git worktree prune" &&
35+
git worktree add --detach áááá main &&
36+
echo "$(git -C áááá rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
37+
git worktree list >actual &&
3938
test_cmp expect actual
4039
'
4140

4241
test_expect_success '"list" all worktrees from linked' '
43-
echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
44-
test_when_finished "rm -rf here out actual expect && git worktree prune" &&
45-
git worktree add --detach here main &&
46-
echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
47-
git -C here worktree list >out &&
48-
sed "s/ */ /g" <out >actual &&
42+
echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
43+
test_when_finished "rm -rf áááá out actual expect && git worktree prune" &&
44+
git worktree add --detach áááá main &&
45+
echo "$(git -C áááá rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
46+
git -C áááá worktree list >actual &&
4947
test_cmp expect actual
5048
'
5149

0 commit comments

Comments
 (0)