Skip to content

Commit

Permalink
worktree: add 'list' command
Browse files Browse the repository at this point in the history
'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 <rappazzo@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
Michael Rappazzo authored and Junio C Hamano committed Oct 8, 2015
1 parent 92718b7 commit bb9c03b
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 1 deletion.
49 changes: 48 additions & 1 deletion Documentation/git-worktree.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ SYNOPSIS
[verse]
'git worktree add' [-f] [--detach] [-b <new-branch>] <path> [<branch>]
'git worktree prune' [-n] [-v] [--expire <expire>]
'git worktree list' [--porcelain]

DESCRIPTION
-----------
Expand Down Expand Up @@ -59,6 +60,13 @@ prune::

Prune working tree information in $GIT_DIR/worktrees.

list::

List details of each worktree. The main worktree is listed first, followed by
each of the linked worktrees. The output details include if the worktree is
bare, the revision currently checked out, and the branch currently checked out
(or 'detached HEAD' if none).

OPTIONS
-------

Expand Down Expand Up @@ -86,6 +94,11 @@ OPTIONS
With `prune`, do not remove anything; just report what it would
remove.

--porcelain::
With `list`, output in an easy-to-parse format for scripts.
This format will remain stable across Git versions and regardless of user
configuration. See below for details.

-v::
--verbose::
With `prune`, report all removals.
Expand Down Expand Up @@ -134,6 +147,41 @@ to `/path/main/.git/worktrees/test-next` then a file named
`test-next` entry from being pruned. See
linkgit:gitrepository-layout[5] for details.

LIST OUTPUT FORMAT
------------------
The worktree list command has two output formats. The default format shows the
details on a single line with columns. For example:

------------
S git worktree list
/path/to/bare-source (bare)
/path/to/linked-worktree abcd1234 [master]
/path/to/other-linked-worktree 1234abc (detached HEAD)
------------

Porcelain Format
~~~~~~~~~~~~~~~~
The porcelain format has a line per attribute. Attributes are listed with a
label and value separated by a single space. Boolean attributes (like 'bare'
and 'detached') are listed as a label only, and are only present if and only
if the value is true. An empty line indicates the end of a worktree. For
example:

------------
S git worktree list --porcelain
worktree /path/to/bare-source
bare

worktree /path/to/linked-worktree
HEAD abcd1234abcd1234abcd1234abcd1234abcd1234
branch refs/heads/master

worktree /path/to/other-linked-worktree
HEAD 1234abc1234abc1234abc1234abc1234abc1234a
detached

------------

EXAMPLES
--------
You are in the middle of a refactoring session and your boss comes in and
Expand Down Expand Up @@ -167,7 +215,6 @@ performed manually, such as:
- `remove` to remove a linked working tree and its administrative files (and
warn if the working tree is dirty)
- `mv` to move or rename a working tree and update its administrative files
- `list` to list linked working trees
- `lock` to prevent automatic pruning of administrative files (for instance,
for a working tree on a portable device)

Expand Down
88 changes: 88 additions & 0 deletions builtin/worktree.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
#include "run-command.h"
#include "sigchain.h"
#include "refs.h"
#include "utf8.h"
#include "worktree.h"

static const char * const worktree_usage[] = {
N_("git worktree add [<options>] <path> <branch>"),
N_("git worktree prune [<options>]"),
N_("git worktree list [<options>]"),
NULL
};

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

static void show_worktree_porcelain(struct worktree *wt)
{
printf("worktree %s\n", wt->path);
if (wt->is_bare)
printf("bare\n");
else {
printf("HEAD %s\n", sha1_to_hex(wt->head_sha1));
if (wt->is_detached)
printf("detached\n");
else
printf("branch %s\n", wt->head_ref);
}
printf("\n");
}

static void show_worktree(struct worktree *wt, int path_maxlen, 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);

strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path);
if (wt->is_bare)
strbuf_addstr(&sb, "(bare)");
else {
strbuf_addf(&sb, "%-*s ", abbrev_len,
find_unique_abbrev(wt->head_sha1, DEFAULT_ABBREV));
if (!wt->is_detached)
strbuf_addf(&sb, "[%s]", shorten_unambiguous_ref(wt->head_ref, 0));
else
strbuf_addstr(&sb, "(detached HEAD)");
}
printf("%s\n", sb.buf);

strbuf_release(&sb);
}

static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen)
{
int i;

for (i = 0; wt[i]; i++) {
int sha1_len;
int path_len = strlen(wt[i]->path);

if (path_len > *maxlen)
*maxlen = path_len;
sha1_len = strlen(find_unique_abbrev(wt[i]->head_sha1, *abbrev));
if (sha1_len > *abbrev)
*abbrev = sha1_len;
}
}

static int list(int ac, const char **av, const char *prefix)
{
int porcelain = 0;

struct option options[] = {
OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
OPT_END()
};

ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
if (ac)
usage_with_options(worktree_usage, options);
else {
struct worktree **worktrees = get_worktrees();
int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i;

if (!porcelain)
measure_widths(worktrees, &abbrev, &path_maxlen);

for (i = 0; worktrees[i]; i++) {
if (porcelain)
show_worktree_porcelain(worktrees[i]);
else
show_worktree(worktrees[i], path_maxlen, abbrev);
}
free_worktrees(worktrees);
}
return 0;
}

int cmd_worktree(int ac, const char **av, const char *prefix)
{
struct option options[] = {
Expand All @@ -371,5 +457,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
return add(ac - 1, av + 1, prefix);
if (!strcmp(av[1], "prune"))
return prune(ac - 1, av + 1, prefix);
if (!strcmp(av[1], "list"))
return list(ac - 1, av + 1, prefix);
usage_with_options(worktree_usage, options);
}
89 changes: 89 additions & 0 deletions t/t2027-worktree-list.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/bin/sh

test_description='test git worktree list'

. ./test-lib.sh

test_expect_success 'setup' '
test_commit init
'

test_expect_success '"list" all worktrees from main' '
echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
test_when_finished "rm -rf here && git worktree prune" &&
git worktree add --detach here master &&
echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
git worktree list | sed "s/ */ /g" >actual &&
test_cmp expect actual
'

test_expect_success '"list" all worktrees from linked' '
echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
test_when_finished "rm -rf here && git worktree prune" &&
git worktree add --detach here master &&
echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
git -C here worktree list | sed "s/ */ /g" >actual &&
test_cmp expect actual
'

test_expect_success '"list" all worktrees --porcelain' '
echo "worktree $(git rev-parse --show-toplevel)" >expect &&
echo "HEAD $(git rev-parse HEAD)" >>expect &&
echo "branch $(git symbolic-ref HEAD)" >>expect &&
echo >>expect &&
test_when_finished "rm -rf here && git worktree prune" &&
git worktree add --detach here master &&
echo "worktree $(git -C here rev-parse --show-toplevel)" >>expect &&
echo "HEAD $(git rev-parse HEAD)" >>expect &&
echo "detached" >>expect &&
echo >>expect &&
git worktree list --porcelain >actual &&
test_cmp expect actual
'

test_expect_success 'bare repo setup' '
git init --bare bare1 &&
echo "data" >file1 &&
git add file1 &&
git commit -m"File1: add data" &&
git push bare1 master &&
git reset --hard HEAD^
'

test_expect_success '"list" all worktrees from bare main' '
test_when_finished "rm -rf there && git -C bare1 worktree prune" &&
git -C bare1 worktree add --detach ../there master &&
echo "$(pwd)/bare1 (bare)" >expect &&
echo "$(git -C there rev-parse --show-toplevel) $(git -C there rev-parse --short HEAD) (detached HEAD)" >>expect &&
git -C bare1 worktree list | sed "s/ */ /g" >actual &&
test_cmp expect actual
'

test_expect_success '"list" all worktrees --porcelain from bare main' '
test_when_finished "rm -rf there && git -C bare1 worktree prune" &&
git -C bare1 worktree add --detach ../there master &&
echo "worktree $(pwd)/bare1" >expect &&
echo "bare" >>expect &&
echo >>expect &&
echo "worktree $(git -C there rev-parse --show-toplevel)" >>expect &&
echo "HEAD $(git -C there rev-parse HEAD)" >>expect &&
echo "detached" >>expect &&
echo >>expect &&
git -C bare1 worktree list --porcelain >actual &&
test_cmp expect actual
'

test_expect_success '"list" all worktrees from linked with a bare main' '
test_when_finished "rm -rf there && git -C bare1 worktree prune" &&
git -C bare1 worktree add --detach ../there master &&
echo "$(pwd)/bare1 (bare)" >expect &&
echo "$(git -C there rev-parse --show-toplevel) $(git -C there rev-parse --short HEAD) (detached HEAD)" >>expect &&
git -C there worktree list | sed "s/ */ /g" >actual &&
test_cmp expect actual
'

test_expect_success 'bare repo cleanup' '
rm -rf bare1
'

test_done

0 comments on commit bb9c03b

Please sign in to comment.