Skip to content

Commit a46dcfb

Browse files
committed
Merge branch 'mr/worktree-list'
Add the "list" subcommand to "git worktree". * mr/worktree-list: worktree: add 'list' command worktree: add details to the worktree struct worktree: add a function to get worktree details worktree: refactor find_linked_symref function worktree: add top-level worktree.c
2 parents 9c53de7 + bb9c03b commit a46dcfb

File tree

9 files changed

+485
-88
lines changed

9 files changed

+485
-88
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

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,7 @@ LIB_OBJS += version.o
808808
LIB_OBJS += versioncmp.o
809809
LIB_OBJS += walker.o
810810
LIB_OBJS += wildmatch.o
811+
LIB_OBJS += worktree.o
811812
LIB_OBJS += wrapper.o
812813
LIB_OBJS += write_or_die.o
813814
LIB_OBJS += ws.o

branch.c

Lines changed: 1 addition & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "refs.h"
55
#include "remote.h"
66
#include "commit.h"
7+
#include "worktree.h"
78

89
struct tracking {
910
struct refspec spec;
@@ -311,84 +312,6 @@ void remove_branch_state(void)
311312
unlink(git_path_squash_msg());
312313
}
313314

314-
static char *find_linked_symref(const char *symref, const char *branch,
315-
const char *id)
316-
{
317-
struct strbuf sb = STRBUF_INIT;
318-
struct strbuf path = STRBUF_INIT;
319-
struct strbuf gitdir = STRBUF_INIT;
320-
char *existing = NULL;
321-
322-
/*
323-
* $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside
324-
* $GIT_DIR so resolve_ref_unsafe() won't work (it uses
325-
* git_path). Parse the ref ourselves.
326-
*/
327-
if (id)
328-
strbuf_addf(&path, "%s/worktrees/%s/%s", get_git_common_dir(), id, symref);
329-
else
330-
strbuf_addf(&path, "%s/%s", get_git_common_dir(), symref);
331-
332-
if (!strbuf_readlink(&sb, path.buf, 0)) {
333-
if (!starts_with(sb.buf, "refs/") ||
334-
check_refname_format(sb.buf, 0))
335-
goto done;
336-
} else if (strbuf_read_file(&sb, path.buf, 0) >= 0 &&
337-
starts_with(sb.buf, "ref:")) {
338-
strbuf_remove(&sb, 0, strlen("ref:"));
339-
strbuf_trim(&sb);
340-
} else
341-
goto done;
342-
if (strcmp(sb.buf, branch))
343-
goto done;
344-
if (id) {
345-
strbuf_reset(&path);
346-
strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id);
347-
if (strbuf_read_file(&gitdir, path.buf, 0) <= 0)
348-
goto done;
349-
strbuf_rtrim(&gitdir);
350-
} else
351-
strbuf_addstr(&gitdir, get_git_common_dir());
352-
strbuf_strip_suffix(&gitdir, ".git");
353-
354-
existing = strbuf_detach(&gitdir, NULL);
355-
done:
356-
strbuf_release(&path);
357-
strbuf_release(&sb);
358-
strbuf_release(&gitdir);
359-
360-
return existing;
361-
}
362-
363-
char *find_shared_symref(const char *symref, const char *target)
364-
{
365-
struct strbuf path = STRBUF_INIT;
366-
DIR *dir;
367-
struct dirent *d;
368-
char *existing;
369-
370-
if ((existing = find_linked_symref(symref, target, NULL)))
371-
return existing;
372-
373-
strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
374-
dir = opendir(path.buf);
375-
strbuf_release(&path);
376-
if (!dir)
377-
return NULL;
378-
379-
while ((d = readdir(dir)) != NULL) {
380-
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
381-
continue;
382-
existing = find_linked_symref(symref, target, d->d_name);
383-
if (existing)
384-
goto done;
385-
}
386-
done:
387-
closedir(dir);
388-
389-
return existing;
390-
}
391-
392315
void die_if_checked_out(const char *branch)
393316
{
394317
char *existing;

branch.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,4 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);
5959
*/
6060
extern void die_if_checked_out(const char *branch);
6161

62-
/*
63-
* Check if a per-worktree symref points to a ref in the main worktree
64-
* or any linked worktree, and return the path to the exising worktree
65-
* if it is. Returns NULL if there is no existing ref. The caller is
66-
* responsible for freeing the returned path.
67-
*/
68-
extern char *find_shared_symref(const char *symref, const char *target);
69-
7062
#endif

builtin/notes.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
#include "string-list.h"
2020
#include "notes-merge.h"
2121
#include "notes-utils.h"
22-
#include "branch.h"
22+
#include "worktree.h"
2323

2424
static const char * const git_notes_usage[] = {
2525
N_("git notes [--ref <notes-ref>] [list [<object>]]"),

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
}

0 commit comments

Comments
 (0)