Skip to content

Commit cbdffe4

Browse files
committed
check_ref_format(): tighten refname rules
This changes the rules for refnames to forbid: (1) a refname that contains "@{" in it. Some people and foreign SCM converter may have named their branches as frotz@24 and we still want to keep supporting it. However, "git branch frotz@{24}" is a disaster. It cannot even checked out because "git checkout frotz@{24}" will interpret it as "detach the HEAD at twenty-fourth reflog entry of the frotz branch". (2) a refname that ends with a dot. We already reject a path component that begins with a dot, primarily to avoid ambiguous range interpretation. If we allowed ".B" as a valid ref, it is unclear if "A...B" means "in dot-B but not in A" or "either in A or B but not in both". But for this to be complete, we need also to forbid "A." to avoid "in B but not in A-dot". This was not a problem in the original range notation, but we should have added this restriction when three-dot notation was introduced. Unlike "no dot at the beginning of any path component" rule, this rule does not have to be "no dot at the end of any path component", because you cannot abbreviate the tail end away, similar to you can say "dot-B" to mean "refs/heads/dot-B". For these reasons, it is not likely people created branches with these names on purpose, but we have allowed such names to be used for quite some time, and it is possible that people created such branches by mistake or by accident. To help people with branches with such unfortunate names to recover, we still allow "branch -d 'bad.'" to delete such branches, and also allow "branch -m bad. good" to rename them. Signed-off-by: Junio C Hamano <[email protected]>
1 parent a2fab53 commit cbdffe4

File tree

3 files changed

+28
-7
lines changed

3 files changed

+28
-7
lines changed

Documentation/git-check-ref-format.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ imposes the following rules on how refs are named:
3232
caret `{caret}`, colon `:`, question-mark `?`, asterisk `*`,
3333
or open bracket `[` anywhere;
3434

35-
. It cannot end with a slash `/`.
35+
. They cannot end with a slash `/` nor a dot `.`.
36+
37+
. They cannot contain a sequence `@{`.
3638

3739
These rules makes it easy for shell script based tools to parse
3840
refnames, pathname expansion by the shell when a refname is used
@@ -51,6 +53,8 @@ refname expressions (see linkgit:git-rev-parse[1]). Namely:
5153
It may also be used to select a specific object such as with
5254
'git-cat-file': "git cat-file blob v1.3.3:refs.c".
5355

56+
. at-open-brace `@{` is used as a notation to access a reflog entry.
57+
5458
With the `--branch` option, it expands a branch name shorthand and
5559
prints the name of the branch the shorthand refers to.
5660

builtin-branch.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,12 +464,21 @@ static void rename_branch(const char *oldname, const char *newname, int force)
464464
struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
465465
unsigned char sha1[20];
466466
struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
467+
int recovery = 0;
467468

468469
if (!oldname)
469470
die("cannot rename the current branch while not on any.");
470471

471-
if (strbuf_check_branch_ref(&oldref, oldname))
472-
die("Invalid branch name: '%s'", oldname);
472+
if (strbuf_check_branch_ref(&oldref, oldname)) {
473+
/*
474+
* Bad name --- this could be an attempt to rename a
475+
* ref that we used to allow to be created by accident.
476+
*/
477+
if (resolve_ref(oldref.buf, sha1, 1, NULL))
478+
recovery = 1;
479+
else
480+
die("Invalid branch name: '%s'", oldname);
481+
}
473482

474483
if (strbuf_check_branch_ref(&newref, newname))
475484
die("Invalid branch name: '%s'", newname);
@@ -484,6 +493,9 @@ static void rename_branch(const char *oldname, const char *newname, int force)
484493
die("Branch rename failed");
485494
strbuf_release(&logmsg);
486495

496+
if (recovery)
497+
warning("Renamed a misnamed branch '%s' away", oldref.buf + 11);
498+
487499
/* no need to pass logmsg here as HEAD didn't really move */
488500
if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL))
489501
die("Branch renamed to %s, but HEAD is not updated!", newname);

refs.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ static inline int bad_ref_char(int ch)
693693

694694
int check_ref_format(const char *ref)
695695
{
696-
int ch, level, bad_type;
696+
int ch, level, bad_type, last;
697697
int ret = CHECK_REF_FORMAT_OK;
698698
const char *cp = ref;
699699

@@ -717,19 +717,24 @@ int check_ref_format(const char *ref)
717717
return CHECK_REF_FORMAT_ERROR;
718718
}
719719

720+
last = ch;
720721
/* scan the rest of the path component */
721722
while ((ch = *cp++) != 0) {
722723
bad_type = bad_ref_char(ch);
723-
if (bad_type) {
724+
if (bad_type)
724725
return CHECK_REF_FORMAT_ERROR;
725-
}
726726
if (ch == '/')
727727
break;
728-
if (ch == '.' && *cp == '.')
728+
if (last == '.' && ch == '.')
729+
return CHECK_REF_FORMAT_ERROR;
730+
if (last == '@' && ch == '{')
729731
return CHECK_REF_FORMAT_ERROR;
732+
last = ch;
730733
}
731734
level++;
732735
if (!ch) {
736+
if (ref <= cp - 2 && cp[-2] == '.')
737+
return CHECK_REF_FORMAT_ERROR;
733738
if (level < 2)
734739
return CHECK_REF_FORMAT_ONELEVEL;
735740
return ret;

0 commit comments

Comments
 (0)