Skip to content

Commit

Permalink
Merge branch 'mr/worktree-list'
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Junio C Hamano committed Oct 26, 2015
2 parents 9c53de7 + bb9c03b commit a46dcfb
Show file tree
Hide file tree
Showing 9 changed files with 485 additions and 88 deletions.
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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,7 @@ LIB_OBJS += version.o
LIB_OBJS += versioncmp.o
LIB_OBJS += walker.o
LIB_OBJS += wildmatch.o
LIB_OBJS += worktree.o
LIB_OBJS += wrapper.o
LIB_OBJS += write_or_die.o
LIB_OBJS += ws.o
Expand Down
79 changes: 1 addition & 78 deletions branch.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "refs.h"
#include "remote.h"
#include "commit.h"
#include "worktree.h"

struct tracking {
struct refspec spec;
Expand Down Expand Up @@ -311,84 +312,6 @@ void remove_branch_state(void)
unlink(git_path_squash_msg());
}

static char *find_linked_symref(const char *symref, const char *branch,
const char *id)
{
struct strbuf sb = STRBUF_INIT;
struct strbuf path = STRBUF_INIT;
struct strbuf gitdir = STRBUF_INIT;
char *existing = NULL;

/*
* $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside
* $GIT_DIR so resolve_ref_unsafe() won't work (it uses
* git_path). Parse the ref ourselves.
*/
if (id)
strbuf_addf(&path, "%s/worktrees/%s/%s", get_git_common_dir(), id, symref);
else
strbuf_addf(&path, "%s/%s", get_git_common_dir(), symref);

if (!strbuf_readlink(&sb, path.buf, 0)) {
if (!starts_with(sb.buf, "refs/") ||
check_refname_format(sb.buf, 0))
goto done;
} else if (strbuf_read_file(&sb, path.buf, 0) >= 0 &&
starts_with(sb.buf, "ref:")) {
strbuf_remove(&sb, 0, strlen("ref:"));
strbuf_trim(&sb);
} else
goto done;
if (strcmp(sb.buf, branch))
goto done;
if (id) {
strbuf_reset(&path);
strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id);
if (strbuf_read_file(&gitdir, path.buf, 0) <= 0)
goto done;
strbuf_rtrim(&gitdir);
} else
strbuf_addstr(&gitdir, get_git_common_dir());
strbuf_strip_suffix(&gitdir, ".git");

existing = strbuf_detach(&gitdir, NULL);
done:
strbuf_release(&path);
strbuf_release(&sb);
strbuf_release(&gitdir);

return existing;
}

char *find_shared_symref(const char *symref, const char *target)
{
struct strbuf path = STRBUF_INIT;
DIR *dir;
struct dirent *d;
char *existing;

if ((existing = find_linked_symref(symref, target, NULL)))
return existing;

strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
dir = opendir(path.buf);
strbuf_release(&path);
if (!dir)
return NULL;

while ((d = readdir(dir)) != NULL) {
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
existing = find_linked_symref(symref, target, d->d_name);
if (existing)
goto done;
}
done:
closedir(dir);

return existing;
}

void die_if_checked_out(const char *branch)
{
char *existing;
Expand Down
8 changes: 0 additions & 8 deletions branch.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,4 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);
*/
extern void die_if_checked_out(const char *branch);

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

#endif
2 changes: 1 addition & 1 deletion builtin/notes.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#include "string-list.h"
#include "notes-merge.h"
#include "notes-utils.h"
#include "branch.h"
#include "worktree.h"

static const char * const git_notes_usage[] = {
N_("git notes [--ref <notes-ref>] [list [<object>]]"),
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);
}
Loading

0 comments on commit a46dcfb

Please sign in to comment.