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
This commit is contained in:
Junio C Hamano
2016-05-23 14:54:29 -07:00
17 changed files with 347 additions and 85 deletions

View File

@ -2,6 +2,8 @@
#include "refs.h"
#include "strbuf.h"
#include "worktree.h"
#include "dir.h"
#include "wt-status.h"
void free_worktrees(struct worktree **worktrees)
{
@ -9,7 +11,7 @@ void free_worktrees(struct worktree **worktrees)
for (i = 0; worktrees[i]; i++) {
free(worktrees[i]->path);
free(worktrees[i]->git_dir);
free(worktrees[i]->id);
free(worktrees[i]->head_ref);
free(worktrees[i]);
}
@ -74,13 +76,11 @@ 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);
strbuf_addstr(&worktree_path, absolute_path(get_git_common_dir()));
is_bare = !strbuf_strip_suffix(&worktree_path, "/.git");
if (is_bare)
strbuf_strip_suffix(&worktree_path, "/.");
@ -92,15 +92,15 @@ static struct worktree *get_main_worktree(void)
worktree = xmalloc(sizeof(struct worktree));
worktree->path = strbuf_detach(&worktree_path, NULL);
worktree->git_dir = strbuf_detach(&gitdir, NULL);
worktree->id = NULL;
worktree->is_bare = is_bare;
worktree->head_ref = NULL;
worktree->is_detached = is_detached;
worktree->is_current = 0;
add_head_info(&head_ref, worktree);
done:
strbuf_release(&path);
strbuf_release(&gitdir);
strbuf_release(&worktree_path);
strbuf_release(&head_ref);
return worktree;
@ -111,16 +111,13 @@ 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);
strbuf_git_common_path(&path, "worktrees/%s/gitdir", id);
if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
/* invalid gitdir file */
goto done;
@ -140,20 +137,39 @@ static struct worktree *get_linked_worktree(const char *id)
worktree = xmalloc(sizeof(struct worktree));
worktree->path = strbuf_detach(&worktree_path, NULL);
worktree->git_dir = strbuf_detach(&gitdir, NULL);
worktree->id = xstrdup(id);
worktree->is_bare = 0;
worktree->head_ref = NULL;
worktree->is_detached = is_detached;
worktree->is_current = 0;
add_head_info(&head_ref, worktree);
done:
strbuf_release(&path);
strbuf_release(&gitdir);
strbuf_release(&worktree_path);
strbuf_release(&head_ref);
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 **list = NULL;
@ -185,35 +201,105 @@ struct worktree **get_worktrees(void)
}
ALLOC_GROW(list, counter + 1, alloc);
list[counter] = NULL;
mark_current_worktree(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 sb = STRBUF_INIT;
struct worktree **worktrees = get_worktrees();
static struct worktree **worktrees;
int i = 0;
if (worktrees)
free_worktrees(worktrees);
worktrees = get_worktrees();
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(&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)) {
continue;
}
if (!strcmp(sb.buf, target)) {
existing = xstrdup(worktrees[i]->path);
existing = wt;
break;
}
}
strbuf_release(&path);
strbuf_release(&sb);
free_worktrees(worktrees);
return existing;
}