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]
|
||||
'git worktree add' [-f] [--detach] [-b <new-branch>] <path> [<branch>]
|
||||
'git worktree prune' [-n] [-v] [--expire <expire>]
|
||||
'git worktree list' [--porcelain]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
@ -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
|
||||
-------
|
||||
|
||||
@ -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.
|
||||
@ -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
|
||||
@ -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)
|
||||
|
||||
|
1
Makefile
1
Makefile
@ -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
|
||||
|
79
branch.c
79
branch.c
@ -4,6 +4,7 @@
|
||||
#include "refs.h"
|
||||
#include "remote.h"
|
||||
#include "commit.h"
|
||||
#include "worktree.h"
|
||||
|
||||
struct tracking {
|
||||
struct refspec spec;
|
||||
@ -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;
|
||||
|
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);
|
||||
|
||||
/*
|
||||
* 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
|
||||
|
@ -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>]]"),
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
@ -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[] = {
|
||||
@ -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
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