Merge branch 'nd/worktree-various-heads'

The experimental "multiple worktree" feature gains more safety to
forbid operations on a branch that is checked out or being actively
worked on elsewhere, by noticing that e.g. it is being rebased.

* nd/worktree-various-heads:
  branch: do not rename a branch under bisect or rebase
  worktree.c: check whether branch is bisected in another worktree
  wt-status.c: split bisect detection out of wt_status_get_state()
  worktree.c: check whether branch is rebased in another worktree
  worktree.c: avoid referencing to worktrees[i] multiple times
  wt-status.c: make wt_status_check_rebase() work on any worktree
  wt-status.c: split rebase detection out of wt_status_get_state()
  path.c: refactor and add worktree_git_path()
  worktree.c: mark current worktree
  worktree.c: make find_shared_symref() return struct worktree *
  worktree.c: store "id" instead of "git_dir"
  path.c: add git_common_path() and strbuf_git_common_path()
  dir.c: rename str(n)cmp_icase to fspath(n)cmp
maint
Junio C Hamano 2016-05-23 14:54:29 -07:00
commit 352d72a30e
17 changed files with 347 additions and 85 deletions

View File

@ -334,15 +334,16 @@ void remove_branch_state(void)
unlink(git_path_squash_msg()); unlink(git_path_squash_msg());
} }


void die_if_checked_out(const char *branch) void die_if_checked_out(const char *branch, int ignore_current_worktree)
{ {
char *existing; const struct worktree *wt;


existing = find_shared_symref("HEAD", branch); wt = find_shared_symref("HEAD", branch);
if (existing) { if (!wt || (ignore_current_worktree && wt->is_current))
skip_prefix(branch, "refs/heads/", &branch); return;
die(_("'%s' is already checked out at '%s'"), branch, existing); skip_prefix(branch, "refs/heads/", &branch);
} die(_("'%s' is already checked out at '%s'"),
branch, wt->path);
} }


int replace_each_worktree_head_symref(const char *oldref, const char *newref) int replace_each_worktree_head_symref(const char *oldref, const char *newref)
@ -357,7 +358,8 @@ int replace_each_worktree_head_symref(const char *oldref, const char *newref)
if (strcmp(oldref, worktrees[i]->head_ref)) if (strcmp(oldref, worktrees[i]->head_ref))
continue; continue;


if (set_worktree_head_symref(worktrees[i]->git_dir, newref)) { if (set_worktree_head_symref(get_worktree_git_dir(worktrees[i]),
newref)) {
ret = -1; ret = -1;
error(_("HEAD of working tree %s is not updated"), error(_("HEAD of working tree %s is not updated"),
worktrees[i]->path); worktrees[i]->path);

View File

@ -58,7 +58,7 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);
* worktree and die (with a message describing its checkout location) if * worktree and die (with a message describing its checkout location) if
* it is. * it is.
*/ */
extern void die_if_checked_out(const char *branch); extern void die_if_checked_out(const char *branch, int ignore_current_worktree);


/* /*
* Update all per-worktree HEADs pointing at the old ref to point the new ref. * Update all per-worktree HEADs pointing at the old ref to point the new ref.

View File

@ -220,12 +220,12 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
name = mkpathdup(fmt, bname.buf); name = mkpathdup(fmt, bname.buf);


if (kinds == FILTER_REFS_BRANCHES) { if (kinds == FILTER_REFS_BRANCHES) {
char *worktree = find_shared_symref("HEAD", name); const struct worktree *wt =
if (worktree) { find_shared_symref("HEAD", name);
if (wt) {
error(_("Cannot delete branch '%s' " error(_("Cannot delete branch '%s' "
"checked out at '%s'"), "checked out at '%s'"),
bname.buf, worktree); bname.buf, wt->path);
free(worktree);
ret = 1; ret = 1;
continue; continue;
} }
@ -526,6 +526,29 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
ref_array_clear(&array); ref_array_clear(&array);
} }


static void reject_rebase_or_bisect_branch(const char *target)
{
struct worktree **worktrees = get_worktrees();
int i;

for (i = 0; worktrees[i]; i++) {
struct worktree *wt = worktrees[i];

if (!wt->is_detached)
continue;

if (is_worktree_being_rebased(wt, target))
die(_("Branch %s is being rebased at %s"),
target, wt->path);

if (is_worktree_being_bisected(wt, target))
die(_("Branch %s is being bisected at %s"),
target, wt->path);
}

free_worktrees(worktrees);
}

static void rename_branch(const char *oldname, const char *newname, int force) static void rename_branch(const char *oldname, const char *newname, int force)
{ {
struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT; struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
@ -555,6 +578,8 @@ static void rename_branch(const char *oldname, const char *newname, int force)


validate_new_branchname(newname, &newref, force, clobber_head_ok); validate_new_branchname(newname, &newref, force, clobber_head_ok);


reject_rebase_or_bisect_branch(oldref.buf);

strbuf_addf(&logmsg, "Branch: renamed %s to %s", strbuf_addf(&logmsg, "Branch: renamed %s to %s",
oldref.buf, newref.buf); oldref.buf, newref.buf);



View File

@ -1110,7 +1110,7 @@ static int checkout_branch(struct checkout_opts *opts,
char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag); char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
if (head_ref && if (head_ref &&
(!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path))) (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))
die_if_checked_out(new->path); die_if_checked_out(new->path, 1);
free(head_ref); free(head_ref);
} }



View File

@ -847,15 +847,15 @@ static int merge(int argc, const char **argv, const char *prefix)
update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
0, UPDATE_REFS_DIE_ON_ERR); 0, UPDATE_REFS_DIE_ON_ERR);
else { /* Merge has unresolved conflicts */ else { /* Merge has unresolved conflicts */
char *existing; const struct worktree *wt;
/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
0, UPDATE_REFS_DIE_ON_ERR); 0, UPDATE_REFS_DIE_ON_ERR);
/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
existing = find_shared_symref("NOTES_MERGE_REF", default_notes_ref()); wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref());
if (existing) if (wt)
die(_("A notes merge into %s is already in-progress at %s"), die(_("A notes merge into %s is already in-progress at %s"),
default_notes_ref(), existing); default_notes_ref(), wt->path);
if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
die("Failed to store link to current notes ref (%s)", die("Failed to store link to current notes ref (%s)",
default_notes_ref()); default_notes_ref());

View File

@ -205,7 +205,7 @@ static int add_worktree(const char *path, const char *refname,
if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) && if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
ref_exists(symref.buf)) { /* it's a branch */ ref_exists(symref.buf)) { /* it's a branch */
if (!opts->force) if (!opts->force)
die_if_checked_out(symref.buf); die_if_checked_out(symref.buf, 0);
} else { /* must be a commit */ } else { /* must be a commit */
commit = lookup_commit_reference_by_name(refname); commit = lookup_commit_reference_by_name(refname);
if (!commit) if (!commit)
@ -349,7 +349,7 @@ static int add(int ac, const char **av, const char *prefix)
if (!opts.force && if (!opts.force &&
!strbuf_check_branch_ref(&symref, opts.new_branch) && !strbuf_check_branch_ref(&symref, opts.new_branch) &&
ref_exists(symref.buf)) ref_exists(symref.buf))
die_if_checked_out(symref.buf); die_if_checked_out(symref.buf, 0);
strbuf_release(&symref); strbuf_release(&symref);
} }



View File

@ -808,11 +808,14 @@ extern void check_repository_format(void);
*/ */
extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2))); extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2))); extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));


extern char *mksnpath(char *buf, size_t n, const char *fmt, ...) extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
__attribute__((format (printf, 3, 4))); __attribute__((format (printf, 3, 4)));
extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...) extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
__attribute__((format (printf, 2, 3))); __attribute__((format (printf, 2, 3)));
extern void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)
__attribute__((format (printf, 2, 3)));
extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...) extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
__attribute__((format (printf, 2, 3))); __attribute__((format (printf, 2, 3)));
extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path, extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path,

13
dir.c
View File

@ -53,13 +53,12 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
int check_only, const struct path_simplify *simplify); int check_only, const struct path_simplify *simplify);
static int get_dtype(struct dirent *de, const char *path, int len); static int get_dtype(struct dirent *de, const char *path, int len);


/* helper string functions with support for the ignore_case flag */ int fspathcmp(const char *a, const char *b)
int strcmp_icase(const char *a, const char *b)
{ {
return ignore_case ? strcasecmp(a, b) : strcmp(a, b); return ignore_case ? strcasecmp(a, b) : strcmp(a, b);
} }


int strncmp_icase(const char *a, const char *b, size_t count) int fspathncmp(const char *a, const char *b, size_t count)
{ {
return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count); return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
} }
@ -795,12 +794,12 @@ int match_basename(const char *basename, int basenamelen,
{ {
if (prefix == patternlen) { if (prefix == patternlen) {
if (patternlen == basenamelen && if (patternlen == basenamelen &&
!strncmp_icase(pattern, basename, basenamelen)) !fspathncmp(pattern, basename, basenamelen))
return 1; return 1;
} else if (flags & EXC_FLAG_ENDSWITH) { } else if (flags & EXC_FLAG_ENDSWITH) {
/* "*literal" matching against "fooliteral" */ /* "*literal" matching against "fooliteral" */
if (patternlen - 1 <= basenamelen && if (patternlen - 1 <= basenamelen &&
!strncmp_icase(pattern + 1, !fspathncmp(pattern + 1,
basename + basenamelen - (patternlen - 1), basename + basenamelen - (patternlen - 1),
patternlen - 1)) patternlen - 1))
return 1; return 1;
@ -837,7 +836,7 @@ int match_pathname(const char *pathname, int pathlen,
*/ */
if (pathlen < baselen + 1 || if (pathlen < baselen + 1 ||
(baselen && pathname[baselen] != '/') || (baselen && pathname[baselen] != '/') ||
strncmp_icase(pathname, base, baselen)) fspathncmp(pathname, base, baselen))
return 0; return 0;


namelen = baselen ? pathlen - baselen - 1 : pathlen; namelen = baselen ? pathlen - baselen - 1 : pathlen;
@ -851,7 +850,7 @@ int match_pathname(const char *pathname, int pathlen,
if (prefix > namelen) if (prefix > namelen)
return 0; return 0;


if (strncmp_icase(pattern, name, prefix)) if (fspathncmp(pattern, name, prefix))
return 0; return 0;
pattern += prefix; pattern += prefix;
patternlen -= prefix; patternlen -= prefix;

4
dir.h
View File

@ -270,8 +270,8 @@ extern int remove_dir_recursively(struct strbuf *path, int flag);
/* tries to remove the path with empty directories along it, ignores ENOENT */ /* tries to remove the path with empty directories along it, ignores ENOENT */
extern int remove_path(const char *path); extern int remove_path(const char *path);


extern int strcmp_icase(const char *a, const char *b); extern int fspathcmp(const char *a, const char *b);
extern int strncmp_icase(const char *a, const char *b, size_t count); extern int fspathncmp(const char *a, const char *b, size_t count);


/* /*
* The prefix part of pattern must not contains wildcards. * The prefix part of pattern must not contains wildcards.

View File

@ -1512,7 +1512,7 @@ static int tree_content_set(
t = root->tree; t = root->tree;
for (i = 0; i < t->entry_count; i++) { for (i = 0; i < t->entry_count; i++) {
e = t->entries[i]; e = t->entries[i];
if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) { if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {
if (!*slash1) { if (!*slash1) {
if (!S_ISDIR(mode) if (!S_ISDIR(mode)
&& e->versions[1].mode == mode && e->versions[1].mode == mode
@ -1602,7 +1602,7 @@ static int tree_content_remove(
t = root->tree; t = root->tree;
for (i = 0; i < t->entry_count; i++) { for (i = 0; i < t->entry_count; i++) {
e = t->entries[i]; e = t->entries[i];
if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) { if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {
if (*slash1 && !S_ISDIR(e->versions[1].mode)) if (*slash1 && !S_ISDIR(e->versions[1].mode))
/* /*
* If p names a file in some subdirectory, and a * If p names a file in some subdirectory, and a
@ -1669,7 +1669,7 @@ static int tree_content_get(
t = root->tree; t = root->tree;
for (i = 0; i < t->entry_count; i++) { for (i = 0; i < t->entry_count; i++) {
e = t->entries[i]; e = t->entries[i];
if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) { if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) {
if (!*slash1) if (!*slash1)
goto found_entry; goto found_entry;
if (!S_ISDIR(e->versions[1].mode)) if (!S_ISDIR(e->versions[1].mode))

53
path.c
View File

@ -5,6 +5,7 @@
#include "strbuf.h" #include "strbuf.h"
#include "string-list.h" #include "string-list.h"
#include "dir.h" #include "dir.h"
#include "worktree.h"


static int get_st_mode_bits(const char *path, int *mode) static int get_st_mode_bits(const char *path, int *mode)
{ {
@ -383,10 +384,11 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len)
update_common_dir(buf, git_dir_len, NULL); update_common_dir(buf, git_dir_len, NULL);
} }


static void do_git_path(struct strbuf *buf, const char *fmt, va_list args) static void do_git_path(const struct worktree *wt, struct strbuf *buf,
const char *fmt, va_list args)
{ {
int gitdir_len; int gitdir_len;
strbuf_addstr(buf, get_git_dir()); strbuf_addstr(buf, get_worktree_git_dir(wt));
if (buf->len && !is_dir_sep(buf->buf[buf->len - 1])) if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
strbuf_addch(buf, '/'); strbuf_addch(buf, '/');
gitdir_len = buf->len; gitdir_len = buf->len;
@ -400,7 +402,7 @@ char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
va_list args; va_list args;
strbuf_reset(buf); strbuf_reset(buf);
va_start(args, fmt); va_start(args, fmt);
do_git_path(buf, fmt, args); do_git_path(NULL, buf, fmt, args);
va_end(args); va_end(args);
return buf->buf; return buf->buf;
} }
@ -409,7 +411,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
{ {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
do_git_path(sb, fmt, args); do_git_path(NULL, sb, fmt, args);
va_end(args); va_end(args);
} }


@ -418,7 +420,7 @@ const char *git_path(const char *fmt, ...)
struct strbuf *pathname = get_pathname(); struct strbuf *pathname = get_pathname();
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
do_git_path(pathname, fmt, args); do_git_path(NULL, pathname, fmt, args);
va_end(args); va_end(args);
return pathname->buf; return pathname->buf;
} }
@ -428,7 +430,7 @@ char *git_pathdup(const char *fmt, ...)
struct strbuf path = STRBUF_INIT; struct strbuf path = STRBUF_INIT;
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
do_git_path(&path, fmt, args); do_git_path(NULL, &path, fmt, args);
va_end(args); va_end(args);
return strbuf_detach(&path, NULL); return strbuf_detach(&path, NULL);
} }
@ -454,6 +456,16 @@ const char *mkpath(const char *fmt, ...)
return cleanup_path(pathname->buf); return cleanup_path(pathname->buf);
} }


const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...)
{
struct strbuf *pathname = get_pathname();
va_list args;
va_start(args, fmt);
do_git_path(wt, pathname, fmt, args);
va_end(args);
return pathname->buf;
}

static void do_submodule_path(struct strbuf *buf, const char *path, static void do_submodule_path(struct strbuf *buf, const char *path,
const char *fmt, va_list args) const char *fmt, va_list args)
{ {
@ -503,6 +515,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path,
va_end(args); va_end(args);
} }


static void do_git_common_path(struct strbuf *buf,
const char *fmt,
va_list args)
{
strbuf_addstr(buf, get_git_common_dir());
if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
strbuf_addch(buf, '/');
strbuf_vaddf(buf, fmt, args);
strbuf_cleanup_path(buf);
}

const char *git_common_path(const char *fmt, ...)
{
struct strbuf *pathname = get_pathname();
va_list args;
va_start(args, fmt);
do_git_common_path(pathname, fmt, args);
va_end(args);
return pathname->buf;
}

void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
do_git_common_path(sb, fmt, args);
va_end(args);
}

int validate_headref(const char *path) int validate_headref(const char *path)
{ {
struct stat st; struct stat st;

View File

@ -301,7 +301,7 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base,
return -1; return -1;
} }
} }
if (!strcmp_icase(ent->base, normalized_objdir)) { if (!fspathcmp(ent->base, normalized_objdir)) {
free(ent); free(ent);
return -1; return -1;
} }

View File

@ -4,6 +4,8 @@ test_description='test git worktree add'


. ./test-lib.sh . ./test-lib.sh


. "$TEST_DIRECTORY"/lib-rebase.sh

test_expect_success 'setup' ' test_expect_success 'setup' '
test_commit init test_commit init
' '
@ -225,4 +227,61 @@ test_expect_success '"add" worktree with --checkout' '
test_cmp init.t swamp2/init.t test_cmp init.t swamp2/init.t
' '


test_expect_success 'put a worktree under rebase' '
git worktree add under-rebase &&
(
cd under-rebase &&
set_fake_editor &&
FAKE_LINES="edit 1" git rebase -i HEAD^ &&
git worktree list | grep "under-rebase.*detached HEAD"
)
'

test_expect_success 'add a worktree, checking out a rebased branch' '
test_must_fail git worktree add new-rebase under-rebase &&
! test -d new-rebase
'

test_expect_success 'checking out a rebased branch from another worktree' '
git worktree add new-place &&
test_must_fail git -C new-place checkout under-rebase
'

test_expect_success 'not allow to delete a branch under rebase' '
(
cd under-rebase &&
test_must_fail git branch -D under-rebase
)
'

test_expect_success 'rename a branch under rebase not allowed' '
test_must_fail git branch -M under-rebase rebase-with-new-name
'

test_expect_success 'check out from current worktree branch ok' '
(
cd under-rebase &&
git checkout under-rebase &&
git checkout - &&
git rebase --abort
)
'

test_expect_success 'checkout a branch under bisect' '
git worktree add under-bisect &&
(
cd under-bisect &&
git bisect start &&
git bisect bad &&
git bisect good HEAD~2 &&
git worktree list | grep "under-bisect.*detached HEAD" &&
test_must_fail git worktree add new-bisect under-bisect &&
! test -d new-bisect
)
'

test_expect_success 'rename a branch under bisect not allowed' '
test_must_fail git branch -M under-bisect bisect-with-new-name
'

test_done test_done

View File

@ -2,6 +2,8 @@
#include "refs.h" #include "refs.h"
#include "strbuf.h" #include "strbuf.h"
#include "worktree.h" #include "worktree.h"
#include "dir.h"
#include "wt-status.h"


void free_worktrees(struct worktree **worktrees) void free_worktrees(struct worktree **worktrees)
{ {
@ -9,7 +11,7 @@ void free_worktrees(struct worktree **worktrees)


for (i = 0; worktrees[i]; i++) { for (i = 0; worktrees[i]; i++) {
free(worktrees[i]->path); free(worktrees[i]->path);
free(worktrees[i]->git_dir); free(worktrees[i]->id);
free(worktrees[i]->head_ref); free(worktrees[i]->head_ref);
free(worktrees[i]); free(worktrees[i]);
} }
@ -74,13 +76,11 @@ static struct worktree *get_main_worktree(void)
struct worktree *worktree = NULL; struct worktree *worktree = NULL;
struct strbuf path = STRBUF_INIT; struct strbuf path = STRBUF_INIT;
struct strbuf worktree_path = STRBUF_INIT; struct strbuf worktree_path = STRBUF_INIT;
struct strbuf gitdir = STRBUF_INIT;
struct strbuf head_ref = STRBUF_INIT; struct strbuf head_ref = STRBUF_INIT;
int is_bare = 0; int is_bare = 0;
int is_detached = 0; int is_detached = 0;


strbuf_addf(&gitdir, "%s", absolute_path(get_git_common_dir())); strbuf_addstr(&worktree_path, absolute_path(get_git_common_dir()));
strbuf_addbuf(&worktree_path, &gitdir);
is_bare = !strbuf_strip_suffix(&worktree_path, "/.git"); is_bare = !strbuf_strip_suffix(&worktree_path, "/.git");
if (is_bare) if (is_bare)
strbuf_strip_suffix(&worktree_path, "/."); strbuf_strip_suffix(&worktree_path, "/.");
@ -92,15 +92,15 @@ static struct worktree *get_main_worktree(void)


worktree = xmalloc(sizeof(struct worktree)); worktree = xmalloc(sizeof(struct worktree));
worktree->path = strbuf_detach(&worktree_path, NULL); worktree->path = strbuf_detach(&worktree_path, NULL);
worktree->git_dir = strbuf_detach(&gitdir, NULL); worktree->id = NULL;
worktree->is_bare = is_bare; worktree->is_bare = is_bare;
worktree->head_ref = NULL; worktree->head_ref = NULL;
worktree->is_detached = is_detached; worktree->is_detached = is_detached;
worktree->is_current = 0;
add_head_info(&head_ref, worktree); add_head_info(&head_ref, worktree);


done: done:
strbuf_release(&path); strbuf_release(&path);
strbuf_release(&gitdir);
strbuf_release(&worktree_path); strbuf_release(&worktree_path);
strbuf_release(&head_ref); strbuf_release(&head_ref);
return worktree; return worktree;
@ -111,16 +111,13 @@ static struct worktree *get_linked_worktree(const char *id)
struct worktree *worktree = NULL; struct worktree *worktree = NULL;
struct strbuf path = STRBUF_INIT; struct strbuf path = STRBUF_INIT;
struct strbuf worktree_path = STRBUF_INIT; struct strbuf worktree_path = STRBUF_INIT;
struct strbuf gitdir = STRBUF_INIT;
struct strbuf head_ref = STRBUF_INIT; struct strbuf head_ref = STRBUF_INIT;
int is_detached = 0; int is_detached = 0;


if (!id) if (!id)
die("Missing linked worktree name"); die("Missing linked worktree name");


strbuf_addf(&gitdir, "%s/worktrees/%s", strbuf_git_common_path(&path, "worktrees/%s/gitdir", id);
absolute_path(get_git_common_dir()), id);
strbuf_addf(&path, "%s/gitdir", gitdir.buf);
if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0) if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
/* invalid gitdir file */ /* invalid gitdir file */
goto done; goto done;
@ -140,20 +137,39 @@ static struct worktree *get_linked_worktree(const char *id)


worktree = xmalloc(sizeof(struct worktree)); worktree = xmalloc(sizeof(struct worktree));
worktree->path = strbuf_detach(&worktree_path, NULL); worktree->path = strbuf_detach(&worktree_path, NULL);
worktree->git_dir = strbuf_detach(&gitdir, NULL); worktree->id = xstrdup(id);
worktree->is_bare = 0; worktree->is_bare = 0;
worktree->head_ref = NULL; worktree->head_ref = NULL;
worktree->is_detached = is_detached; worktree->is_detached = is_detached;
worktree->is_current = 0;
add_head_info(&head_ref, worktree); add_head_info(&head_ref, worktree);


done: done:
strbuf_release(&path); strbuf_release(&path);
strbuf_release(&gitdir);
strbuf_release(&worktree_path); strbuf_release(&worktree_path);
strbuf_release(&head_ref); strbuf_release(&head_ref);
return worktree; return worktree;
} }


static void mark_current_worktree(struct worktree **worktrees)
{
struct strbuf git_dir = STRBUF_INIT;
struct strbuf path = STRBUF_INIT;
int i;

strbuf_addstr(&git_dir, absolute_path(get_git_dir()));
for (i = 0; worktrees[i]; i++) {
struct worktree *wt = worktrees[i];
strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));
wt->is_current = !fspathcmp(git_dir.buf, path.buf);
strbuf_reset(&path);
if (wt->is_current)
break;
}
strbuf_release(&git_dir);
strbuf_release(&path);
}

struct worktree **get_worktrees(void) struct worktree **get_worktrees(void)
{ {
struct worktree **list = NULL; struct worktree **list = NULL;
@ -185,35 +201,105 @@ struct worktree **get_worktrees(void)
} }
ALLOC_GROW(list, counter + 1, alloc); ALLOC_GROW(list, counter + 1, alloc);
list[counter] = NULL; list[counter] = NULL;

mark_current_worktree(list);
return list; return list;
} }


char *find_shared_symref(const char *symref, const char *target) const char *get_worktree_git_dir(const struct worktree *wt)
{ {
char *existing = NULL; if (!wt)
return get_git_dir();
else if (!wt->id)
return get_git_common_dir();
else
return git_common_path("worktrees/%s", wt->id);
}

int is_worktree_being_rebased(const struct worktree *wt,
const char *target)
{
struct wt_status_state state;
int found_rebase;

memset(&state, 0, sizeof(state));
found_rebase = wt_status_check_rebase(wt, &state) &&
((state.rebase_in_progress ||
state.rebase_interactive_in_progress) &&
state.branch &&
starts_with(target, "refs/heads/") &&
!strcmp(state.branch, target + strlen("refs/heads/")));
free(state.branch);
free(state.onto);
return found_rebase;
}

int is_worktree_being_bisected(const struct worktree *wt,
const char *target)
{
struct wt_status_state state;
int found_rebase;

memset(&state, 0, sizeof(state));
found_rebase = wt_status_check_bisect(wt, &state) &&
state.branch &&
starts_with(target, "refs/heads/") &&
!strcmp(state.branch, target + strlen("refs/heads/"));
free(state.branch);
return found_rebase;
}

/*
* note: this function should be able to detect shared symref even if
* HEAD is temporarily detached (e.g. in the middle of rebase or
* bisect). New commands that do similar things should update this
* function as well.
*/
const struct worktree *find_shared_symref(const char *symref,
const char *target)
{
const struct worktree *existing = NULL;
struct strbuf path = STRBUF_INIT; struct strbuf path = STRBUF_INIT;
struct strbuf sb = STRBUF_INIT; struct strbuf sb = STRBUF_INIT;
struct worktree **worktrees = get_worktrees(); static struct worktree **worktrees;
int i = 0; int i = 0;


if (worktrees)
free_worktrees(worktrees);
worktrees = get_worktrees();

for (i = 0; worktrees[i]; i++) { for (i = 0; worktrees[i]; i++) {
struct worktree *wt = worktrees[i];

if (wt->is_detached && !strcmp(symref, "HEAD")) {
if (is_worktree_being_rebased(wt, target)) {
existing = wt;
break;
}
if (is_worktree_being_bisected(wt, target)) {
existing = wt;
break;
}
}

strbuf_reset(&path); strbuf_reset(&path);
strbuf_reset(&sb); strbuf_reset(&sb);
strbuf_addf(&path, "%s/%s", worktrees[i]->git_dir, symref); strbuf_addf(&path, "%s/%s",
get_worktree_git_dir(wt),
symref);


if (parse_ref(path.buf, &sb, NULL)) { if (parse_ref(path.buf, &sb, NULL)) {
continue; continue;
} }


if (!strcmp(sb.buf, target)) { if (!strcmp(sb.buf, target)) {
existing = xstrdup(worktrees[i]->path); existing = wt;
break; break;
} }
} }


strbuf_release(&path); strbuf_release(&path);
strbuf_release(&sb); strbuf_release(&sb);
free_worktrees(worktrees);


return existing; return existing;
} }

View File

@ -3,11 +3,12 @@


struct worktree { struct worktree {
char *path; char *path;
char *git_dir; char *id;
char *head_ref; char *head_ref;
unsigned char head_sha1[20]; unsigned char head_sha1[20];
int is_detached; int is_detached;
int is_bare; int is_bare;
int is_current;
}; };


/* Functions for acting on the information about worktrees. */ /* Functions for acting on the information about worktrees. */
@ -22,6 +23,12 @@ struct worktree {
*/ */
extern struct worktree **get_worktrees(void); extern struct worktree **get_worktrees(void);


/*
* Return git dir of the worktree. Note that the path may be relative.
* If wt is NULL, git dir of current worktree is returned.
*/
extern const char *get_worktree_git_dir(const struct worktree *wt);

/* /*
* Free up the memory for worktree(s) * Free up the memory for worktree(s)
*/ */
@ -29,10 +36,21 @@ extern void free_worktrees(struct worktree **);


/* /*
* Check if a per-worktree symref points to a ref in the main 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 * or any linked worktree, and return the worktree that holds the ref,
* if it is. Returns NULL if there is no existing ref. The caller is * or NULL otherwise. The result may be destroyed by the next call.
* responsible for freeing the returned path.
*/ */
extern char *find_shared_symref(const char *symref, const char *target); extern const struct worktree *find_shared_symref(const char *symref,
const char *target);

int is_worktree_being_rebased(const struct worktree *wt, const char *target);
int is_worktree_being_bisected(const struct worktree *wt, const char *target);

/*
* Similar to git_path() but can produce paths for a specified
* worktree instead of current one
*/
extern const char *worktree_git_path(const struct worktree *wt,
const char *fmt, ...)
__attribute__((format (printf, 2, 3)));


#endif #endif

View File

@ -15,6 +15,7 @@
#include "column.h" #include "column.h"
#include "strbuf.h" #include "strbuf.h"
#include "utf8.h" #include "utf8.h"
#include "worktree.h"


static const char cut_line[] = static const char cut_line[] =
"------------------------ >8 ------------------------\n"; "------------------------ >8 ------------------------\n";
@ -1263,13 +1264,13 @@ static void show_bisect_in_progress(struct wt_status *s,
/* /*
* Extract branch information from rebase/bisect * Extract branch information from rebase/bisect
*/ */
static char *read_and_strip_branch(const char *path) static char *get_branch(const struct worktree *wt, const char *path)
{ {
struct strbuf sb = STRBUF_INIT; struct strbuf sb = STRBUF_INIT;
unsigned char sha1[20]; unsigned char sha1[20];
const char *branch_name; const char *branch_name;


if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0) if (strbuf_read_file(&sb, worktree_git_path(wt, "%s", path), 0) <= 0)
goto got_nothing; goto got_nothing;


while (sb.len && sb.buf[sb.len - 1] == '\n') while (sb.len && sb.buf[sb.len - 1] == '\n')
@ -1361,6 +1362,46 @@ static void wt_status_get_detached_from(struct wt_status_state *state)
strbuf_release(&cb.buf); strbuf_release(&cb.buf);
} }


int wt_status_check_rebase(const struct worktree *wt,
struct wt_status_state *state)
{
struct stat st;

if (!stat(worktree_git_path(wt, "rebase-apply"), &st)) {
if (!stat(worktree_git_path(wt, "rebase-apply/applying"), &st)) {
state->am_in_progress = 1;
if (!stat(worktree_git_path(wt, "rebase-apply/patch"), &st) && !st.st_size)
state->am_empty_patch = 1;
} else {
state->rebase_in_progress = 1;
state->branch = get_branch(wt, "rebase-apply/head-name");
state->onto = get_branch(wt, "rebase-apply/onto");
}
} else if (!stat(worktree_git_path(wt, "rebase-merge"), &st)) {
if (!stat(worktree_git_path(wt, "rebase-merge/interactive"), &st))
state->rebase_interactive_in_progress = 1;
else
state->rebase_in_progress = 1;
state->branch = get_branch(wt, "rebase-merge/head-name");
state->onto = get_branch(wt, "rebase-merge/onto");
} else
return 0;
return 1;
}

int wt_status_check_bisect(const struct worktree *wt,
struct wt_status_state *state)
{
struct stat st;

if (!stat(worktree_git_path(wt, "BISECT_LOG"), &st)) {
state->bisect_in_progress = 1;
state->branch = get_branch(wt, "BISECT_START");
return 1;
}
return 0;
}

void wt_status_get_state(struct wt_status_state *state, void wt_status_get_state(struct wt_status_state *state,
int get_detached_from) int get_detached_from)
{ {
@ -1369,32 +1410,14 @@ void wt_status_get_state(struct wt_status_state *state,


if (!stat(git_path_merge_head(), &st)) { if (!stat(git_path_merge_head(), &st)) {
state->merge_in_progress = 1; state->merge_in_progress = 1;
} else if (!stat(git_path("rebase-apply"), &st)) { } else if (wt_status_check_rebase(NULL, state)) {
if (!stat(git_path("rebase-apply/applying"), &st)) { ; /* all set */
state->am_in_progress = 1;
if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
state->am_empty_patch = 1;
} else {
state->rebase_in_progress = 1;
state->branch = read_and_strip_branch("rebase-apply/head-name");
state->onto = read_and_strip_branch("rebase-apply/onto");
}
} else if (!stat(git_path("rebase-merge"), &st)) {
if (!stat(git_path("rebase-merge/interactive"), &st))
state->rebase_interactive_in_progress = 1;
else
state->rebase_in_progress = 1;
state->branch = read_and_strip_branch("rebase-merge/head-name");
state->onto = read_and_strip_branch("rebase-merge/onto");
} else if (!stat(git_path_cherry_pick_head(), &st) && } else if (!stat(git_path_cherry_pick_head(), &st) &&
!get_sha1("CHERRY_PICK_HEAD", sha1)) { !get_sha1("CHERRY_PICK_HEAD", sha1)) {
state->cherry_pick_in_progress = 1; state->cherry_pick_in_progress = 1;
hashcpy(state->cherry_pick_head_sha1, sha1); hashcpy(state->cherry_pick_head_sha1, sha1);
} }
if (!stat(git_path("BISECT_LOG"), &st)) { wt_status_check_bisect(NULL, state);
state->bisect_in_progress = 1;
state->branch = read_and_strip_branch("BISECT_START");
}
if (!stat(git_path_revert_head(), &st) && if (!stat(git_path_revert_head(), &st) &&
!get_sha1("REVERT_HEAD", sha1)) { !get_sha1("REVERT_HEAD", sha1)) {
state->revert_in_progress = 1; state->revert_in_progress = 1;

View File

@ -6,6 +6,8 @@
#include "color.h" #include "color.h"
#include "pathspec.h" #include "pathspec.h"


struct worktree;

enum color_wt_status { enum color_wt_status {
WT_STATUS_HEADER = 0, WT_STATUS_HEADER = 0,
WT_STATUS_UPDATED, WT_STATUS_UPDATED,
@ -100,6 +102,10 @@ void wt_status_prepare(struct wt_status *s);
void wt_status_print(struct wt_status *s); void wt_status_print(struct wt_status *s);
void wt_status_collect(struct wt_status *s); void wt_status_collect(struct wt_status *s);
void wt_status_get_state(struct wt_status_state *state, int get_detached_from); void wt_status_get_state(struct wt_status_state *state, int get_detached_from);
int wt_status_check_rebase(const struct worktree *wt,
struct wt_status_state *state);
int wt_status_check_bisect(const struct worktree *wt,
struct wt_status_state *state);


void wt_shortstatus_print(struct wt_status *s); void wt_shortstatus_print(struct wt_status *s);
void wt_porcelain_print(struct wt_status *s); void wt_porcelain_print(struct wt_status *s);