worktree.c: check whether branch is rebased in another worktree
authorNguyễn Thái Ngọc Duy <pclouds@gmail.com>
Fri, 22 Apr 2016 13:01:33 +0000 (20:01 +0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 22 Apr 2016 21:09:38 +0000 (14:09 -0700)
This function find_shared_symref() is used in a couple places:

1) in builtin/branch.c: it's used to detect if a branch is checked out
elsewhere and refuse to delete the branch.

2) in builtin/notes.c: it's used to detect if a note is being merged in
another worktree

3) in branch.c, the function die_if_checked_out() is actually used by
"git checkout" and "git worktree add" to see if a branch is already
checked out elsewhere and refuse the operation.

In cases 1 and 3, if a rebase is ongoing, "HEAD" will be in detached
mode, find_shared_symref() fails to detect it and declares "no branch is
checked out here", which is not really what we want.

This patch tightens the test. If the given symref is "HEAD", we try to
detect if rebase is ongoing. If so return the branch being rebased. This
makes checkout and branch delete operations safer because you can't
checkout a branch being rebased in another place, or delete it.

Special case for checkout. If the current branch is being rebased,
git-rebase.sh may use "git checkout" to abort and return back to the
original branch. The updated test in find_shared_symref() will prevent
that and "git rebase --abort" will fail as a result.
find_shared_symref() and die_if_checked_out() have to learn a new
option ignore_current_worktree to loosen the test a bit.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
branch.c
branch.h
builtin/checkout.c
builtin/worktree.c
t/t2025-worktree-add.sh
worktree.c
index 1f1fbf528c638fc3a0dc9ae1b0f711870cd39af6..a5a8dcbd0ed929d09a73674361ee1dd81dd5b88c 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -334,12 +334,12 @@ void remove_branch_state(void)
        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)
 {
        const struct worktree *wt;
 
        wt = find_shared_symref("HEAD", branch);
-       if (!wt)
+       if (!wt || (ignore_current_worktree && wt->is_current))
                return;
        skip_prefix(branch, "refs/heads/", &branch);
        die(_("'%s' is already checked out at '%s'"),
index d69163daf793f92f3dab92e2fb9bc5217b2dfd6b..b2f964933270e1306eff1f0589c58efd22d6c76b 100644 (file)
--- a/branch.h
+++ b/branch.h
@@ -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
  * 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.
index efcbd8f6b5e0b7d551c88c02b6d7d3069a47944d..60417187838ef2e93fcf997dd4b311bcf54c0a15 100644 (file)
@@ -1111,7 +1111,7 @@ static int checkout_branch(struct checkout_opts *opts,
                char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag);
                if (head_ref &&
                    (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)))
-                       die_if_checked_out(new->path);
+                       die_if_checked_out(new->path, 1);
                free(head_ref);
        }
 
index d8e3795dc44a0dc047cac77af92282361227111a..12c0af723e69b87c07cc152824fb2cff6930ee66 100644 (file)
@@ -205,7 +205,7 @@ static int add_worktree(const char *path, const char *refname,
        if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
                 ref_exists(symref.buf)) { /* it's a branch */
                if (!opts->force)
-                       die_if_checked_out(symref.buf);
+                       die_if_checked_out(symref.buf, 0);
        } else { /* must be a commit */
                commit = lookup_commit_reference_by_name(refname);
                if (!commit)
@@ -349,7 +349,7 @@ static int add(int ac, const char **av, const char *prefix)
                if (!opts.force &&
                    !strbuf_check_branch_ref(&symref, opts.new_branch) &&
                    ref_exists(symref.buf))
-                       die_if_checked_out(symref.buf);
+                       die_if_checked_out(symref.buf, 0);
                strbuf_release(&symref);
        }
 
index 3acb9926f2ff7fbf2b367a089b294a4a88e2ea31..da54327f5da14d4778ad8603a34b22cf96f96e5c 100755 (executable)
@@ -4,6 +4,8 @@ test_description='test git worktree add'
 
 . ./test-lib.sh
 
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
 test_expect_success 'setup' '
        test_commit init
 '
@@ -225,4 +227,40 @@ test_expect_success '"add" worktree with --checkout' '
        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 'check out from current worktree branch ok' '
+       (
+               cd under-rebase &&
+               git checkout under-rebase &&
+               git checkout - &&
+               git rebase --abort
+       )
+'
+
 test_done
index 927905b7a1b9f067aa4b3c34f70a0826ff4467e7..5043756663eb993db5a93fe1da7149caaca8ac5c 100644 (file)
@@ -3,6 +3,7 @@
 #include "strbuf.h"
 #include "worktree.h"
 #include "dir.h"
+#include "wt-status.h"
 
 void free_worktrees(struct worktree **worktrees)
 {
@@ -215,6 +216,30 @@ const char *get_worktree_git_dir(const struct worktree *wt)
                return git_common_path("worktrees/%s", wt->id);
 }
 
+static 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;
+}
+
+/*
+ * 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)
 {
@@ -231,6 +256,13 @@ const struct worktree *find_shared_symref(const char *symref,
        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;
+                       }
+               }
+
                strbuf_reset(&path);
                strbuf_reset(&sb);
                strbuf_addf(&path, "%s/%s",