Skip to content

Commit 55c4a67

Browse files
ConradIrwingitster
authored andcommitted
Prevent force-updating of the current branch
"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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 1be9d84 commit 55c4a67

File tree

6 files changed

+56
-24
lines changed

6 files changed

+56
-24
lines changed

branch.c

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,26 @@ static int setup_tracking(const char *new_ref, const char *orig_ref,
135135
return 0;
136136
}
137137

138+
int validate_new_branchname(const char *name, struct strbuf *ref, int force)
139+
{
140+
const char *head;
141+
unsigned char sha1[20];
142+
143+
if (strbuf_check_branch_ref(ref, name))
144+
die("'%s' is not a valid branch name.", name);
145+
146+
if (!ref_exists(ref->buf))
147+
return 0;
148+
else if (!force)
149+
die("A branch named '%s' already exists.", name);
150+
151+
head = resolve_ref("HEAD", sha1, 0, NULL);
152+
if (!is_bare_repository() && head && !strcmp(head, ref->buf))
153+
die("Cannot force update the current branch.");
154+
155+
return 1;
156+
}
157+
138158
void create_branch(const char *head,
139159
const char *name, const char *start_name,
140160
int force, int reflog, enum branch_track track)
@@ -151,17 +171,11 @@ void create_branch(const char *head,
151171
if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
152172
explicit_tracking = 1;
153173

154-
if (strbuf_check_branch_ref(&ref, name))
155-
die("'%s' is not a valid branch name.", name);
156-
157-
if (resolve_ref(ref.buf, sha1, 1, NULL)) {
158-
if (!force && track == BRANCH_TRACK_OVERRIDE)
174+
if (validate_new_branchname(name, &ref, force || track == BRANCH_TRACK_OVERRIDE)) {
175+
if (!force)
159176
dont_change_ref = 1;
160-
else if (!force)
161-
die("A branch named '%s' already exists.", name);
162-
else if (!is_bare_repository() && head && !strcmp(head, name))
163-
die("Cannot force update the current branch.");
164-
forcing = 1;
177+
else
178+
forcing = 1;
165179
}
166180

167181
real_ref = NULL;

branch.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@
1515
void create_branch(const char *head, const char *name, const char *start_name,
1616
int force, int reflog, enum branch_track track);
1717

18+
/*
19+
* Validates that the requested branch may be created, returning the
20+
* interpreted ref in ref, force indicates whether (non-head) branches
21+
* may be overwritten. A non-zero return value indicates that the force
22+
* parameter was non-zero and the branch already exists.
23+
*/
24+
int validate_new_branchname(const char *name, struct strbuf *ref, int force);
25+
1826
/*
1927
* Remove information about the state of working on the current
2028
* branch. (E.g., MERGE_HEAD)

builtin/branch.c

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -566,11 +566,7 @@ static void rename_branch(const char *oldname, const char *newname, int force)
566566
die(_("Invalid branch name: '%s'"), oldname);
567567
}
568568

569-
if (strbuf_check_branch_ref(&newref, newname))
570-
die(_("Invalid branch name: '%s'"), newname);
571-
572-
if (resolve_ref(newref.buf, sha1, 1, NULL) && !force)
573-
die(_("A branch named '%s' already exists."), newref.buf + 11);
569+
validate_new_branchname(newname, &newref, force);
574570

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

builtin/checkout.c

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,15 +1071,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
10711071

10721072
if (opts.new_branch) {
10731073
struct strbuf buf = STRBUF_INIT;
1074-
if (strbuf_check_branch_ref(&buf, opts.new_branch))
1075-
die(_("git checkout: we do not like '%s' as a branch name."),
1076-
opts.new_branch);
1077-
if (ref_exists(buf.buf)) {
1078-
opts.branch_exists = 1;
1079-
if (!opts.new_branch_force)
1080-
die(_("git checkout: branch %s already exists"),
1081-
opts.new_branch);
1082-
}
1074+
1075+
opts.branch_exists = validate_new_branchname(opts.new_branch, &buf, !!opts.new_branch_force);
1076+
10831077
strbuf_release(&buf);
10841078
}
10851079

t/t2018-checkout-branch.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,12 @@ test_expect_success 'checkout -b <describe>' '
180180
test_cmp expect actual
181181
'
182182

183+
test_expect_success 'checkout -B to the current branch fails before merging' '
184+
git checkout branch1 &&
185+
setup_dirty_mergeable &&
186+
git commit -mfooble &&
187+
test_must_fail git checkout -B branch1 initial &&
188+
test_must_fail test_dirty_mergeable
189+
'
190+
183191
test_done

t/t3200-branch.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,18 @@ test_expect_success 'git branch -m q r/q should fail when r exists' '
9898
test_must_fail git branch -m q r/q
9999
'
100100

101+
test_expect_success 'git branch -M foo bar should fail when bar is checked out' '
102+
git branch bar &&
103+
git checkout -b foo &&
104+
test_must_fail git branch -M bar foo
105+
'
106+
107+
test_expect_success 'git branch -M baz bam should succeed when baz is checked out' '
108+
git checkout -b baz &&
109+
git branch bam &&
110+
git branch -M baz bam
111+
'
112+
101113
mv .git/config .git/config-saved
102114

103115
test_expect_success 'git branch -m q q2 without config should succeed' '

0 commit comments

Comments
 (0)