Prevent force-updating of the current branch
authorConrad Irwin <conrad.irwin@gmail.com>
Sat, 20 Aug 2011 21:49:48 +0000 (14:49 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 22 Aug 2011 23:00:36 +0000 (16:00 -0700)
"git branch -M <foo> <current-branch>" allows updating the current branch
which HEAD points, without the necessary house-keeping that git reset
normally does to make this operation sensible. It also leaves the reflog
in a confusing state (you would be warned when trying to read it).

"git checkout -B <current branch> <foo>" is also partly vulnerable to this
bug; due to inconsistent pre-flight checks it would perform half of its
task and then abort just before rewriting the branch. Again this
manifested itself as the index file getting out-of-sync with HEAD.

"git branch -f" already guarded against this problem, and aborts with
a fatal error.

Update "git branch -M", "git checkout -B" and "git branch -f" to share the
same check before allowing a branch to be created. These prevent you from
updating the current branch.

We considered suggesting the use of "git reset" in the failure message
but concluded that it was not possible to discern what the user was
actually trying to do.

Signed-off-by: Conrad Irwin <conrad.irwin@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
branch.c
branch.h
builtin/branch.c
builtin/checkout.c
t/t2018-checkout-branch.sh
t/t3200-branch.sh

index c0c865a4b1b0fca038f32b77f2239d7987438ff5..ff84b5b9b70280123343c20db66424f7dee349c6 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -135,6 +135,26 @@ static int setup_tracking(const char *new_ref, const char *orig_ref,
        return 0;
 }
 
+int validate_new_branchname(const char *name, struct strbuf *ref, int force)
+{
+       const char *head;
+       unsigned char sha1[20];
+
+       if (strbuf_check_branch_ref(ref, name))
+               die("'%s' is not a valid branch name.", name);
+
+       if (!ref_exists(ref->buf))
+               return 0;
+       else if (!force)
+               die("A branch named '%s' already exists.", name);
+
+       head = resolve_ref("HEAD", sha1, 0, NULL);
+       if (!is_bare_repository() && head && !strcmp(head, ref->buf))
+               die("Cannot force update the current branch.");
+
+       return 1;
+}
+
 void create_branch(const char *head,
                   const char *name, const char *start_name,
                   int force, int reflog, enum branch_track track)
@@ -151,17 +171,11 @@ void create_branch(const char *head,
        if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
                explicit_tracking = 1;
 
-       if (strbuf_check_branch_ref(&ref, name))
-               die("'%s' is not a valid branch name.", name);
-
-       if (resolve_ref(ref.buf, sha1, 1, NULL)) {
-               if (!force && track == BRANCH_TRACK_OVERRIDE)
+       if (validate_new_branchname(name, &ref, force || track == BRANCH_TRACK_OVERRIDE)) {
+               if (!force)
                        dont_change_ref = 1;
-               else if (!force)
-                       die("A branch named '%s' already exists.", name);
-               else if (!is_bare_repository() && head && !strcmp(head, name))
-                       die("Cannot force update the current branch.");
-               forcing = 1;
+               else
+                       forcing = 1;
        }
 
        real_ref = NULL;
index 4026e3832b265c4cef6e5bc151976771867b3da9..01544e25cbbf77c573da38c73f105a8b62c55f99 100644 (file)
--- a/branch.h
+++ b/branch.h
 void create_branch(const char *head, const char *name, const char *start_name,
                   int force, int reflog, enum branch_track track);
 
+/*
+ * Validates that the requested branch may be created, returning the
+ * interpreted ref in ref, force indicates whether (non-head) branches
+ * may be overwritten. A non-zero return value indicates that the force
+ * parameter was non-zero and the branch already exists.
+ */
+int validate_new_branchname(const char *name, struct strbuf *ref, int force);
+
 /*
  * Remove information about the state of working on the current
  * branch. (E.g., MERGE_HEAD)
index 3142daa57a6fa1c8a7d21095946bf5d26443d0e0..40f885c9fe9598f8e7c25f14c6e5aa01b45d1c82 100644 (file)
@@ -566,11 +566,7 @@ static void rename_branch(const char *oldname, const char *newname, int force)
                        die(_("Invalid branch name: '%s'"), oldname);
        }
 
-       if (strbuf_check_branch_ref(&newref, newname))
-               die(_("Invalid branch name: '%s'"), newname);
-
-       if (resolve_ref(newref.buf, sha1, 1, NULL) && !force)
-               die(_("A branch named '%s' already exists."), newref.buf + 11);
+       validate_new_branchname(newname, &newref, force);
 
        strbuf_addf(&logmsg, "Branch: renamed %s to %s",
                 oldref.buf, newref.buf);
index ca855d716ccab1d1209638193e2f52e901acfba0..ddefec053e563dff0e16cd5adb2f8f6d26c77d87 100644 (file)
@@ -1071,15 +1071,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 
        if (opts.new_branch) {
                struct strbuf buf = STRBUF_INIT;
-               if (strbuf_check_branch_ref(&buf, opts.new_branch))
-                       die(_("git checkout: we do not like '%s' as a branch name."),
-                           opts.new_branch);
-               if (ref_exists(buf.buf)) {
-                       opts.branch_exists = 1;
-                       if (!opts.new_branch_force)
-                               die(_("git checkout: branch %s already exists"),
-                                   opts.new_branch);
-               }
+
+               opts.branch_exists = validate_new_branchname(opts.new_branch, &buf, !!opts.new_branch_force);
+
                strbuf_release(&buf);
        }
 
index a42e03967b1df3001df24089f2c50008c092ac51..b66db2b8818b7b084889ea1b2c7687fc59c12127 100755 (executable)
@@ -180,4 +180,12 @@ test_expect_success 'checkout -b <describe>' '
        test_cmp expect actual
 '
 
+test_expect_success 'checkout -B to the current branch fails before merging' '
+       git checkout branch1 &&
+       setup_dirty_mergeable &&
+       git commit -mfooble &&
+       test_must_fail git checkout -B branch1 initial &&
+       test_must_fail test_dirty_mergeable
+'
+
 test_done
index 9e69c8c926620f06343e64e7b3aa3e4ada5a6b69..cb6458d1c820bd131af4659fe878ff9406fbbcac 100755 (executable)
@@ -98,6 +98,18 @@ test_expect_success 'git branch -m q r/q should fail when r exists' '
        test_must_fail git branch -m q r/q
 '
 
+test_expect_success 'git branch -M foo bar should fail when bar is checked out' '
+       git branch bar &&
+       git checkout -b foo &&
+       test_must_fail git branch -M bar foo
+'
+
+test_expect_success 'git branch -M baz bam should succeed when baz is checked out' '
+       git checkout -b baz &&
+       git branch bam &&
+       git branch -M baz bam
+'
+
 mv .git/config .git/config-saved
 
 test_expect_success 'git branch -m q q2 without config should succeed' '