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
This commit is contained in:
commit
a46dcfb840
@ -11,6 +11,7 @@ SYNOPSIS
|
|||||||
[verse]
|
[verse]
|
||||||
'git worktree add' [-f] [--detach] [-b <new-branch>] <path> [<branch>]
|
'git worktree add' [-f] [--detach] [-b <new-branch>] <path> [<branch>]
|
||||||
'git worktree prune' [-n] [-v] [--expire <expire>]
|
'git worktree prune' [-n] [-v] [--expire <expire>]
|
||||||
|
'git worktree list' [--porcelain]
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
@ -59,6 +60,13 @@ prune::
|
|||||||
|
|
||||||
Prune working tree information in $GIT_DIR/worktrees.
|
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
|
OPTIONS
|
||||||
-------
|
-------
|
||||||
|
|
||||||
@ -86,6 +94,11 @@ OPTIONS
|
|||||||
With `prune`, do not remove anything; just report what it would
|
With `prune`, do not remove anything; just report what it would
|
||||||
remove.
|
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::
|
-v::
|
||||||
--verbose::
|
--verbose::
|
||||||
With `prune`, report all removals.
|
With `prune`, report all removals.
|
||||||
@ -134,6 +147,41 @@ to `/path/main/.git/worktrees/test-next` then a file named
|
|||||||
`test-next` entry from being pruned. See
|
`test-next` entry from being pruned. See
|
||||||
linkgit:gitrepository-layout[5] for details.
|
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
|
EXAMPLES
|
||||||
--------
|
--------
|
||||||
You are in the middle of a refactoring session and your boss comes in and
|
You are in the middle of a refactoring session and your boss comes in and
|
||||||
@ -167,7 +215,6 @@ performed manually, such as:
|
|||||||
- `remove` to remove a linked working tree and its administrative files (and
|
- `remove` to remove a linked working tree and its administrative files (and
|
||||||
warn if the working tree is dirty)
|
warn if the working tree is dirty)
|
||||||
- `mv` to move or rename a working tree and update its administrative files
|
- `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,
|
- `lock` to prevent automatic pruning of administrative files (for instance,
|
||||||
for a working tree on a portable device)
|
for a working tree on a portable device)
|
||||||
|
|
||||||
|
1
Makefile
1
Makefile
@ -808,6 +808,7 @@ LIB_OBJS += version.o
|
|||||||
LIB_OBJS += versioncmp.o
|
LIB_OBJS += versioncmp.o
|
||||||
LIB_OBJS += walker.o
|
LIB_OBJS += walker.o
|
||||||
LIB_OBJS += wildmatch.o
|
LIB_OBJS += wildmatch.o
|
||||||
|
LIB_OBJS += worktree.o
|
||||||
LIB_OBJS += wrapper.o
|
LIB_OBJS += wrapper.o
|
||||||
LIB_OBJS += write_or_die.o
|
LIB_OBJS += write_or_die.o
|
||||||
LIB_OBJS += ws.o
|
LIB_OBJS += ws.o
|
||||||
|
79
branch.c
79
branch.c
@ -4,6 +4,7 @@
|
|||||||
#include "refs.h"
|
#include "refs.h"
|
||||||
#include "remote.h"
|
#include "remote.h"
|
||||||
#include "commit.h"
|
#include "commit.h"
|
||||||
|
#include "worktree.h"
|
||||||
|
|
||||||
struct tracking {
|
struct tracking {
|
||||||
struct refspec spec;
|
struct refspec spec;
|
||||||
@ -311,84 +312,6 @@ void remove_branch_state(void)
|
|||||||
unlink(git_path_squash_msg());
|
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)
|
void die_if_checked_out(const char *branch)
|
||||||
{
|
{
|
||||||
char *existing;
|
char *existing;
|
||||||
|
8
branch.h
8
branch.h
@ -59,12 +59,4 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);
|
|||||||
*/
|
*/
|
||||||
extern void die_if_checked_out(const char *branch);
|
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
|
#endif
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
#include "string-list.h"
|
#include "string-list.h"
|
||||||
#include "notes-merge.h"
|
#include "notes-merge.h"
|
||||||
#include "notes-utils.h"
|
#include "notes-utils.h"
|
||||||
#include "branch.h"
|
#include "worktree.h"
|
||||||
|
|
||||||
static const char * const git_notes_usage[] = {
|
static const char * const git_notes_usage[] = {
|
||||||
N_("git notes [--ref <notes-ref>] [list [<object>]]"),
|
N_("git notes [--ref <notes-ref>] [list [<object>]]"),
|
||||||
|
@ -8,10 +8,13 @@
|
|||||||
#include "run-command.h"
|
#include "run-command.h"
|
||||||
#include "sigchain.h"
|
#include "sigchain.h"
|
||||||
#include "refs.h"
|
#include "refs.h"
|
||||||
|
#include "utf8.h"
|
||||||
|
#include "worktree.h"
|
||||||
|
|
||||||
static const char * const worktree_usage[] = {
|
static const char * const worktree_usage[] = {
|
||||||
N_("git worktree add [<options>] <path> <branch>"),
|
N_("git worktree add [<options>] <path> <branch>"),
|
||||||
N_("git worktree prune [<options>]"),
|
N_("git worktree prune [<options>]"),
|
||||||
|
N_("git worktree list [<options>]"),
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -359,6 +362,89 @@ static int add(int ac, const char **av, const char *prefix)
|
|||||||
return add_worktree(path, branch, &opts);
|
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)
|
int cmd_worktree(int ac, const char **av, const char *prefix)
|
||||||
{
|
{
|
||||||
struct option options[] = {
|
struct option options[] = {
|
||||||
@ -371,5 +457,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
|
|||||||
return add(ac - 1, av + 1, prefix);
|
return add(ac - 1, av + 1, prefix);
|
||||||
if (!strcmp(av[1], "prune"))
|
if (!strcmp(av[1], "prune"))
|
||||||
return prune(ac - 1, av + 1, prefix);
|
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);
|
usage_with_options(worktree_usage, options);
|
||||||
}
|
}
|
||||||
|
89
t/t2027-worktree-list.sh
Executable file
89
t/t2027-worktree-list.sh
Executable file
@ -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
|
219
worktree.c
Normal file
219
worktree.c
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
#include "cache.h"
|
||||||
|
#include "refs.h"
|
||||||
|
#include "strbuf.h"
|
||||||
|
#include "worktree.h"
|
||||||
|
|
||||||
|
void free_worktrees(struct worktree **worktrees)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
for (i = 0; worktrees[i]; i++) {
|
||||||
|
free(worktrees[i]->path);
|
||||||
|
free(worktrees[i]->git_dir);
|
||||||
|
free(worktrees[i]->head_ref);
|
||||||
|
free(worktrees[i]);
|
||||||
|
}
|
||||||
|
free (worktrees);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* read 'path_to_ref' into 'ref'. Also if is_detached is not NULL,
|
||||||
|
* set is_detached to 1 (0) if the ref is detatched (is not detached).
|
||||||
|
*
|
||||||
|
* $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside $GIT_DIR so
|
||||||
|
* for linked worktrees, `resolve_ref_unsafe()` won't work (it uses
|
||||||
|
* git_path). Parse the ref ourselves.
|
||||||
|
*
|
||||||
|
* return -1 if the ref is not a proper ref, 0 otherwise (success)
|
||||||
|
*/
|
||||||
|
static int parse_ref(char *path_to_ref, struct strbuf *ref, int *is_detached)
|
||||||
|
{
|
||||||
|
if (is_detached)
|
||||||
|
*is_detached = 0;
|
||||||
|
if (!strbuf_readlink(ref, path_to_ref, 0)) {
|
||||||
|
/* HEAD is symbolic link */
|
||||||
|
if (!starts_with(ref->buf, "refs/") ||
|
||||||
|
check_refname_format(ref->buf, 0))
|
||||||
|
return -1;
|
||||||
|
} else if (strbuf_read_file(ref, path_to_ref, 0) >= 0) {
|
||||||
|
/* textual symref or detached */
|
||||||
|
if (!starts_with(ref->buf, "ref:")) {
|
||||||
|
if (is_detached)
|
||||||
|
*is_detached = 1;
|
||||||
|
} else {
|
||||||
|
strbuf_remove(ref, 0, strlen("ref:"));
|
||||||
|
strbuf_trim(ref);
|
||||||
|
if (check_refname_format(ref->buf, 0))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the head_sha1 and head_ref (if not detached) to the given worktree
|
||||||
|
*/
|
||||||
|
static void add_head_info(struct strbuf *head_ref, struct worktree *worktree)
|
||||||
|
{
|
||||||
|
if (head_ref->len) {
|
||||||
|
if (worktree->is_detached) {
|
||||||
|
get_sha1_hex(head_ref->buf, worktree->head_sha1);
|
||||||
|
} else {
|
||||||
|
resolve_ref_unsafe(head_ref->buf, 0, worktree->head_sha1, NULL);
|
||||||
|
worktree->head_ref = strbuf_detach(head_ref, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the main worktree
|
||||||
|
*/
|
||||||
|
static struct worktree *get_main_worktree(void)
|
||||||
|
{
|
||||||
|
struct worktree *worktree = NULL;
|
||||||
|
struct strbuf path = STRBUF_INIT;
|
||||||
|
struct strbuf worktree_path = STRBUF_INIT;
|
||||||
|
struct strbuf gitdir = STRBUF_INIT;
|
||||||
|
struct strbuf head_ref = STRBUF_INIT;
|
||||||
|
int is_bare = 0;
|
||||||
|
int is_detached = 0;
|
||||||
|
|
||||||
|
strbuf_addf(&gitdir, "%s", absolute_path(get_git_common_dir()));
|
||||||
|
strbuf_addbuf(&worktree_path, &gitdir);
|
||||||
|
is_bare = !strbuf_strip_suffix(&worktree_path, "/.git");
|
||||||
|
if (is_bare)
|
||||||
|
strbuf_strip_suffix(&worktree_path, "/.");
|
||||||
|
|
||||||
|
strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
|
||||||
|
|
||||||
|
if (parse_ref(path.buf, &head_ref, &is_detached) < 0)
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
worktree = xmalloc(sizeof(struct worktree));
|
||||||
|
worktree->path = strbuf_detach(&worktree_path, NULL);
|
||||||
|
worktree->git_dir = strbuf_detach(&gitdir, NULL);
|
||||||
|
worktree->is_bare = is_bare;
|
||||||
|
worktree->head_ref = NULL;
|
||||||
|
worktree->is_detached = is_detached;
|
||||||
|
add_head_info(&head_ref, worktree);
|
||||||
|
|
||||||
|
done:
|
||||||
|
strbuf_release(&path);
|
||||||
|
strbuf_release(&gitdir);
|
||||||
|
strbuf_release(&worktree_path);
|
||||||
|
strbuf_release(&head_ref);
|
||||||
|
return worktree;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct worktree *get_linked_worktree(const char *id)
|
||||||
|
{
|
||||||
|
struct worktree *worktree = NULL;
|
||||||
|
struct strbuf path = STRBUF_INIT;
|
||||||
|
struct strbuf worktree_path = STRBUF_INIT;
|
||||||
|
struct strbuf gitdir = STRBUF_INIT;
|
||||||
|
struct strbuf head_ref = STRBUF_INIT;
|
||||||
|
int is_detached = 0;
|
||||||
|
|
||||||
|
if (!id)
|
||||||
|
die("Missing linked worktree name");
|
||||||
|
|
||||||
|
strbuf_addf(&gitdir, "%s/worktrees/%s",
|
||||||
|
absolute_path(get_git_common_dir()), id);
|
||||||
|
strbuf_addf(&path, "%s/gitdir", gitdir.buf);
|
||||||
|
if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
|
||||||
|
/* invalid gitdir file */
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
strbuf_rtrim(&worktree_path);
|
||||||
|
if (!strbuf_strip_suffix(&worktree_path, "/.git")) {
|
||||||
|
strbuf_reset(&worktree_path);
|
||||||
|
strbuf_addstr(&worktree_path, absolute_path("."));
|
||||||
|
strbuf_strip_suffix(&worktree_path, "/.");
|
||||||
|
}
|
||||||
|
|
||||||
|
strbuf_reset(&path);
|
||||||
|
strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
|
||||||
|
|
||||||
|
if (parse_ref(path.buf, &head_ref, &is_detached) < 0)
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
worktree = xmalloc(sizeof(struct worktree));
|
||||||
|
worktree->path = strbuf_detach(&worktree_path, NULL);
|
||||||
|
worktree->git_dir = strbuf_detach(&gitdir, NULL);
|
||||||
|
worktree->is_bare = 0;
|
||||||
|
worktree->head_ref = NULL;
|
||||||
|
worktree->is_detached = is_detached;
|
||||||
|
add_head_info(&head_ref, worktree);
|
||||||
|
|
||||||
|
done:
|
||||||
|
strbuf_release(&path);
|
||||||
|
strbuf_release(&gitdir);
|
||||||
|
strbuf_release(&worktree_path);
|
||||||
|
strbuf_release(&head_ref);
|
||||||
|
return worktree;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct worktree **get_worktrees(void)
|
||||||
|
{
|
||||||
|
struct worktree **list = NULL;
|
||||||
|
struct strbuf path = STRBUF_INIT;
|
||||||
|
DIR *dir;
|
||||||
|
struct dirent *d;
|
||||||
|
int counter = 0, alloc = 2;
|
||||||
|
|
||||||
|
list = xmalloc(alloc * sizeof(struct worktree *));
|
||||||
|
|
||||||
|
if ((list[counter] = get_main_worktree()))
|
||||||
|
counter++;
|
||||||
|
|
||||||
|
strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
|
||||||
|
dir = opendir(path.buf);
|
||||||
|
strbuf_release(&path);
|
||||||
|
if (dir) {
|
||||||
|
while ((d = readdir(dir)) != NULL) {
|
||||||
|
struct worktree *linked = NULL;
|
||||||
|
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((linked = get_linked_worktree(d->d_name))) {
|
||||||
|
ALLOC_GROW(list, counter + 1, alloc);
|
||||||
|
list[counter++] = linked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
ALLOC_GROW(list, counter + 1, alloc);
|
||||||
|
list[counter] = NULL;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *find_shared_symref(const char *symref, const char *target)
|
||||||
|
{
|
||||||
|
char *existing = NULL;
|
||||||
|
struct strbuf path = STRBUF_INIT;
|
||||||
|
struct strbuf sb = STRBUF_INIT;
|
||||||
|
struct worktree **worktrees = get_worktrees();
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
for (i = 0; worktrees[i]; i++) {
|
||||||
|
strbuf_reset(&path);
|
||||||
|
strbuf_reset(&sb);
|
||||||
|
strbuf_addf(&path, "%s/%s", worktrees[i]->git_dir, symref);
|
||||||
|
|
||||||
|
if (parse_ref(path.buf, &sb, NULL)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(sb.buf, target)) {
|
||||||
|
existing = xstrdup(worktrees[i]->path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strbuf_release(&path);
|
||||||
|
strbuf_release(&sb);
|
||||||
|
free_worktrees(worktrees);
|
||||||
|
|
||||||
|
return existing;
|
||||||
|
}
|
38
worktree.h
Normal file
38
worktree.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#ifndef WORKTREE_H
|
||||||
|
#define WORKTREE_H
|
||||||
|
|
||||||
|
struct worktree {
|
||||||
|
char *path;
|
||||||
|
char *git_dir;
|
||||||
|
char *head_ref;
|
||||||
|
unsigned char head_sha1[20];
|
||||||
|
int is_detached;
|
||||||
|
int is_bare;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Functions for acting on the information about worktrees. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the worktrees. The primary worktree will always be the first returned,
|
||||||
|
* and linked worktrees will be pointed to by 'next' in each subsequent
|
||||||
|
* worktree. No specific ordering is done on the linked worktrees.
|
||||||
|
*
|
||||||
|
* The caller is responsible for freeing the memory from the returned
|
||||||
|
* worktree(s).
|
||||||
|
*/
|
||||||
|
extern struct worktree **get_worktrees(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free up the memory for worktree(s)
|
||||||
|
*/
|
||||||
|
extern void free_worktrees(struct worktree **);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
Loading…
Reference in New Issue
Block a user