Skip to content

Commit bb9c03b

Browse files
rappazzogitster
authored andcommitted
worktree: add 'list' command
'git worktree list' iterates through the worktree list, and outputs details of the worktree including the path to the worktree, the currently checked out revision and branch, and if the work tree is bare. There is also porcelain format option available. Signed-off-by: Michael Rappazzo <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 92718b7 commit bb9c03b

File tree

3 files changed

+225
-1
lines changed

3 files changed

+225
-1
lines changed

Documentation/git-worktree.txt

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ SYNOPSIS
1111
[verse]
1212
'git worktree add' [-f] [--detach] [-b <new-branch>] <path> [<branch>]
1313
'git worktree prune' [-n] [-v] [--expire <expire>]
14+
'git worktree list' [--porcelain]
1415

1516
DESCRIPTION
1617
-----------
@@ -59,6 +60,13 @@ prune::
5960

6061
Prune working tree information in $GIT_DIR/worktrees.
6162

63+
list::
64+
65+
List details of each worktree. The main worktree is listed first, followed by
66+
each of the linked worktrees. The output details include if the worktree is
67+
bare, the revision currently checked out, and the branch currently checked out
68+
(or 'detached HEAD' if none).
69+
6270
OPTIONS
6371
-------
6472

@@ -86,6 +94,11 @@ OPTIONS
8694
With `prune`, do not remove anything; just report what it would
8795
remove.
8896

97+
--porcelain::
98+
With `list`, output in an easy-to-parse format for scripts.
99+
This format will remain stable across Git versions and regardless of user
100+
configuration. See below for details.
101+
89102
-v::
90103
--verbose::
91104
With `prune`, report all removals.
@@ -134,6 +147,41 @@ to `/path/main/.git/worktrees/test-next` then a file named
134147
`test-next` entry from being pruned. See
135148
linkgit:gitrepository-layout[5] for details.
136149

150+
LIST OUTPUT FORMAT
151+
------------------
152+
The worktree list command has two output formats. The default format shows the
153+
details on a single line with columns. For example:
154+
155+
------------
156+
S git worktree list
157+
/path/to/bare-source (bare)
158+
/path/to/linked-worktree abcd1234 [master]
159+
/path/to/other-linked-worktree 1234abc (detached HEAD)
160+
------------
161+
162+
Porcelain Format
163+
~~~~~~~~~~~~~~~~
164+
The porcelain format has a line per attribute. Attributes are listed with a
165+
label and value separated by a single space. Boolean attributes (like 'bare'
166+
and 'detached') are listed as a label only, and are only present if and only
167+
if the value is true. An empty line indicates the end of a worktree. For
168+
example:
169+
170+
------------
171+
S git worktree list --porcelain
172+
worktree /path/to/bare-source
173+
bare
174+
175+
worktree /path/to/linked-worktree
176+
HEAD abcd1234abcd1234abcd1234abcd1234abcd1234
177+
branch refs/heads/master
178+
179+
worktree /path/to/other-linked-worktree
180+
HEAD 1234abc1234abc1234abc1234abc1234abc1234a
181+
detached
182+
183+
------------
184+
137185
EXAMPLES
138186
--------
139187
You are in the middle of a refactoring session and your boss comes in and
@@ -167,7 +215,6 @@ performed manually, such as:
167215
- `remove` to remove a linked working tree and its administrative files (and
168216
warn if the working tree is dirty)
169217
- `mv` to move or rename a working tree and update its administrative files
170-
- `list` to list linked working trees
171218
- `lock` to prevent automatic pruning of administrative files (for instance,
172219
for a working tree on a portable device)
173220

builtin/worktree.c

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88
#include "run-command.h"
99
#include "sigchain.h"
1010
#include "refs.h"
11+
#include "utf8.h"
12+
#include "worktree.h"
1113

1214
static const char * const worktree_usage[] = {
1315
N_("git worktree add [<options>] <path> <branch>"),
1416
N_("git worktree prune [<options>]"),
17+
N_("git worktree list [<options>]"),
1518
NULL
1619
};
1720

@@ -359,6 +362,89 @@ static int add(int ac, const char **av, const char *prefix)
359362
return add_worktree(path, branch, &opts);
360363
}
361364

365+
static void show_worktree_porcelain(struct worktree *wt)
366+
{
367+
printf("worktree %s\n", wt->path);
368+
if (wt->is_bare)
369+
printf("bare\n");
370+
else {
371+
printf("HEAD %s\n", sha1_to_hex(wt->head_sha1));
372+
if (wt->is_detached)
373+
printf("detached\n");
374+
else
375+
printf("branch %s\n", wt->head_ref);
376+
}
377+
printf("\n");
378+
}
379+
380+
static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
381+
{
382+
struct strbuf sb = STRBUF_INIT;
383+
int cur_path_len = strlen(wt->path);
384+
int path_adj = cur_path_len - utf8_strwidth(wt->path);
385+
386+
strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path);
387+
if (wt->is_bare)
388+
strbuf_addstr(&sb, "(bare)");
389+
else {
390+
strbuf_addf(&sb, "%-*s ", abbrev_len,
391+
find_unique_abbrev(wt->head_sha1, DEFAULT_ABBREV));
392+
if (!wt->is_detached)
393+
strbuf_addf(&sb, "[%s]", shorten_unambiguous_ref(wt->head_ref, 0));
394+
else
395+
strbuf_addstr(&sb, "(detached HEAD)");
396+
}
397+
printf("%s\n", sb.buf);
398+
399+
strbuf_release(&sb);
400+
}
401+
402+
static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen)
403+
{
404+
int i;
405+
406+
for (i = 0; wt[i]; i++) {
407+
int sha1_len;
408+
int path_len = strlen(wt[i]->path);
409+
410+
if (path_len > *maxlen)
411+
*maxlen = path_len;
412+
sha1_len = strlen(find_unique_abbrev(wt[i]->head_sha1, *abbrev));
413+
if (sha1_len > *abbrev)
414+
*abbrev = sha1_len;
415+
}
416+
}
417+
418+
static int list(int ac, const char **av, const char *prefix)
419+
{
420+
int porcelain = 0;
421+
422+
struct option options[] = {
423+
OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
424+
OPT_END()
425+
};
426+
427+
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
428+
if (ac)
429+
usage_with_options(worktree_usage, options);
430+
else {
431+
struct worktree **worktrees = get_worktrees();
432+
int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i;
433+
434+
if (!porcelain)
435+
measure_widths(worktrees, &abbrev, &path_maxlen);
436+
437+
for (i = 0; worktrees[i]; i++) {
438+
if (porcelain)
439+
show_worktree_porcelain(worktrees[i]);
440+
else
441+
show_worktree(worktrees[i], path_maxlen, abbrev);
442+
}
443+
free_worktrees(worktrees);
444+
}
445+
return 0;
446+
}
447+
362448
int cmd_worktree(int ac, const char **av, const char *prefix)
363449
{
364450
struct option options[] = {
@@ -371,5 +457,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
371457
return add(ac - 1, av + 1, prefix);
372458
if (!strcmp(av[1], "prune"))
373459
return prune(ac - 1, av + 1, prefix);
460+
if (!strcmp(av[1], "list"))
461+
return list(ac - 1, av + 1, prefix);
374462
usage_with_options(worktree_usage, options);
375463
}

t/t2027-worktree-list.sh

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/bin/sh
2+
3+
test_description='test git worktree list'
4+
5+
. ./test-lib.sh
6+
7+
test_expect_success 'setup' '
8+
test_commit init
9+
'
10+
11+
test_expect_success '"list" all worktrees from main' '
12+
echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
13+
test_when_finished "rm -rf here && git worktree prune" &&
14+
git worktree add --detach here master &&
15+
echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
16+
git worktree list | sed "s/ */ /g" >actual &&
17+
test_cmp expect actual
18+
'
19+
20+
test_expect_success '"list" all worktrees from linked' '
21+
echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
22+
test_when_finished "rm -rf here && git worktree prune" &&
23+
git worktree add --detach here master &&
24+
echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
25+
git -C here worktree list | sed "s/ */ /g" >actual &&
26+
test_cmp expect actual
27+
'
28+
29+
test_expect_success '"list" all worktrees --porcelain' '
30+
echo "worktree $(git rev-parse --show-toplevel)" >expect &&
31+
echo "HEAD $(git rev-parse HEAD)" >>expect &&
32+
echo "branch $(git symbolic-ref HEAD)" >>expect &&
33+
echo >>expect &&
34+
test_when_finished "rm -rf here && git worktree prune" &&
35+
git worktree add --detach here master &&
36+
echo "worktree $(git -C here rev-parse --show-toplevel)" >>expect &&
37+
echo "HEAD $(git rev-parse HEAD)" >>expect &&
38+
echo "detached" >>expect &&
39+
echo >>expect &&
40+
git worktree list --porcelain >actual &&
41+
test_cmp expect actual
42+
'
43+
44+
test_expect_success 'bare repo setup' '
45+
git init --bare bare1 &&
46+
echo "data" >file1 &&
47+
git add file1 &&
48+
git commit -m"File1: add data" &&
49+
git push bare1 master &&
50+
git reset --hard HEAD^
51+
'
52+
53+
test_expect_success '"list" all worktrees from bare main' '
54+
test_when_finished "rm -rf there && git -C bare1 worktree prune" &&
55+
git -C bare1 worktree add --detach ../there master &&
56+
echo "$(pwd)/bare1 (bare)" >expect &&
57+
echo "$(git -C there rev-parse --show-toplevel) $(git -C there rev-parse --short HEAD) (detached HEAD)" >>expect &&
58+
git -C bare1 worktree list | sed "s/ */ /g" >actual &&
59+
test_cmp expect actual
60+
'
61+
62+
test_expect_success '"list" all worktrees --porcelain from bare main' '
63+
test_when_finished "rm -rf there && git -C bare1 worktree prune" &&
64+
git -C bare1 worktree add --detach ../there master &&
65+
echo "worktree $(pwd)/bare1" >expect &&
66+
echo "bare" >>expect &&
67+
echo >>expect &&
68+
echo "worktree $(git -C there rev-parse --show-toplevel)" >>expect &&
69+
echo "HEAD $(git -C there rev-parse HEAD)" >>expect &&
70+
echo "detached" >>expect &&
71+
echo >>expect &&
72+
git -C bare1 worktree list --porcelain >actual &&
73+
test_cmp expect actual
74+
'
75+
76+
test_expect_success '"list" all worktrees from linked with a bare main' '
77+
test_when_finished "rm -rf there && git -C bare1 worktree prune" &&
78+
git -C bare1 worktree add --detach ../there master &&
79+
echo "$(pwd)/bare1 (bare)" >expect &&
80+
echo "$(git -C there rev-parse --show-toplevel) $(git -C there rev-parse --short HEAD) (detached HEAD)" >>expect &&
81+
git -C there worktree list | sed "s/ */ /g" >actual &&
82+
test_cmp expect actual
83+
'
84+
85+
test_expect_success 'bare repo cleanup' '
86+
rm -rf bare1
87+
'
88+
89+
test_done

0 commit comments

Comments
 (0)