Skip to content

Commit 21261fa

Browse files
committed
Merge branch 'jk/interpret-branch-name-fix' into maint
A handful of bugs around interpreting $branch@{upstream} notation and its lookalike, when $branch part has interesting characters, e.g. "@", and ":", have been fixed. * jk/interpret-branch-name-fix: interpret_branch_name: find all possible @-marks interpret_branch_name: avoid @{upstream} past colon interpret_branch_name: always respect "namelen" parameter interpret_branch_name: rename "cp" variable to "at" interpret_branch_name: factor out upstream handling
2 parents 7c9b668 + 9892d5d commit 21261fa

File tree

3 files changed

+124
-45
lines changed

3 files changed

+124
-45
lines changed

sha1_name.c

Lines changed: 73 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ static inline int upstream_mark(const char *string, int len)
430430
}
431431

432432
static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
433-
static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf);
433+
static int interpret_nth_prior_checkout(const char *name, int namelen, struct strbuf *buf);
434434

435435
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
436436
{
@@ -492,7 +492,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
492492
struct strbuf buf = STRBUF_INIT;
493493
int detached;
494494

495-
if (interpret_nth_prior_checkout(str, &buf) > 0) {
495+
if (interpret_nth_prior_checkout(str, len, &buf) > 0) {
496496
detached = (buf.len == 40 && !get_sha1_hex(buf.buf, sha1));
497497
strbuf_release(&buf);
498498
if (detached)
@@ -931,17 +931,20 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
931931
* Parse @{-N} syntax, return the number of characters parsed
932932
* if successful; otherwise signal an error with negative value.
933933
*/
934-
static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
934+
static int interpret_nth_prior_checkout(const char *name, int namelen,
935+
struct strbuf *buf)
935936
{
936937
long nth;
937938
int retval;
938939
struct grab_nth_branch_switch_cbdata cb;
939940
const char *brace;
940941
char *num_end;
941942

943+
if (namelen < 4)
944+
return -1;
942945
if (name[0] != '@' || name[1] != '{' || name[2] != '-')
943946
return -1;
944-
brace = strchr(name, '}');
947+
brace = memchr(name, '}', namelen);
945948
if (!brace)
946949
return -1;
947950
nth = strtol(name + 3, &num_end, 10);
@@ -1014,7 +1017,7 @@ static int interpret_empty_at(const char *name, int namelen, int len, struct str
10141017
return -1;
10151018

10161019
/* make sure it's a single @, or @@{.*}, not @foo */
1017-
next = strchr(name + len + 1, '@');
1020+
next = memchr(name + len + 1, '@', namelen - len - 1);
10181021
if (next && next[1] != '{')
10191022
return -1;
10201023
if (!next)
@@ -1048,6 +1051,57 @@ static int reinterpret(const char *name, int namelen, int len, struct strbuf *bu
10481051
return ret - used + len;
10491052
}
10501053

1054+
static void set_shortened_ref(struct strbuf *buf, const char *ref)
1055+
{
1056+
char *s = shorten_unambiguous_ref(ref, 0);
1057+
strbuf_reset(buf);
1058+
strbuf_addstr(buf, s);
1059+
free(s);
1060+
}
1061+
1062+
static const char *get_upstream_branch(const char *branch_buf, int len)
1063+
{
1064+
char *branch = xstrndup(branch_buf, len);
1065+
struct branch *upstream = branch_get(*branch ? branch : NULL);
1066+
1067+
/*
1068+
* Upstream can be NULL only if branch refers to HEAD and HEAD
1069+
* points to something different than a branch.
1070+
*/
1071+
if (!upstream)
1072+
die(_("HEAD does not point to a branch"));
1073+
if (!upstream->merge || !upstream->merge[0]->dst) {
1074+
if (!ref_exists(upstream->refname))
1075+
die(_("No such branch: '%s'"), branch);
1076+
if (!upstream->merge) {
1077+
die(_("No upstream configured for branch '%s'"),
1078+
upstream->name);
1079+
}
1080+
die(
1081+
_("Upstream branch '%s' not stored as a remote-tracking branch"),
1082+
upstream->merge[0]->src);
1083+
}
1084+
free(branch);
1085+
1086+
return upstream->merge[0]->dst;
1087+
}
1088+
1089+
static int interpret_upstream_mark(const char *name, int namelen,
1090+
int at, struct strbuf *buf)
1091+
{
1092+
int len;
1093+
1094+
len = upstream_mark(name + at, namelen - at);
1095+
if (!len)
1096+
return -1;
1097+
1098+
if (memchr(name, ':', at))
1099+
return -1;
1100+
1101+
set_shortened_ref(buf, get_upstream_branch(name, at));
1102+
return len + at;
1103+
}
1104+
10511105
/*
10521106
* This reads short-hand syntax that not only evaluates to a commit
10531107
* object name, but also can act as if the end user spelled the name
@@ -1071,10 +1125,9 @@ static int reinterpret(const char *name, int namelen, int len, struct strbuf *bu
10711125
*/
10721126
int interpret_branch_name(const char *name, int namelen, struct strbuf *buf)
10731127
{
1074-
char *cp;
1075-
struct branch *upstream;
1076-
int len = interpret_nth_prior_checkout(name, buf);
1077-
int tmp_len;
1128+
char *at;
1129+
const char *start;
1130+
int len = interpret_nth_prior_checkout(name, namelen, buf);
10781131

10791132
if (!namelen)
10801133
namelen = strlen(name);
@@ -1088,44 +1141,20 @@ int interpret_branch_name(const char *name, int namelen, struct strbuf *buf)
10881141
return reinterpret(name, namelen, len, buf);
10891142
}
10901143

1091-
cp = strchr(name, '@');
1092-
if (!cp)
1093-
return -1;
1094-
1095-
len = interpret_empty_at(name, namelen, cp - name, buf);
1096-
if (len > 0)
1097-
return reinterpret(name, namelen, len, buf);
1144+
for (start = name;
1145+
(at = memchr(start, '@', namelen - (start - name)));
1146+
start = at + 1) {
10981147

1099-
tmp_len = upstream_mark(cp, namelen - (cp - name));
1100-
if (!tmp_len)
1101-
return -1;
1148+
len = interpret_empty_at(name, namelen, at - name, buf);
1149+
if (len > 0)
1150+
return reinterpret(name, namelen, len, buf);
11021151

1103-
len = cp + tmp_len - name;
1104-
cp = xstrndup(name, cp - name);
1105-
upstream = branch_get(*cp ? cp : NULL);
1106-
/*
1107-
* Upstream can be NULL only if cp refers to HEAD and HEAD
1108-
* points to something different than a branch.
1109-
*/
1110-
if (!upstream)
1111-
die(_("HEAD does not point to a branch"));
1112-
if (!upstream->merge || !upstream->merge[0]->dst) {
1113-
if (!ref_exists(upstream->refname))
1114-
die(_("No such branch: '%s'"), cp);
1115-
if (!upstream->merge) {
1116-
die(_("No upstream configured for branch '%s'"),
1117-
upstream->name);
1118-
}
1119-
die(
1120-
_("Upstream branch '%s' not stored as a remote-tracking branch"),
1121-
upstream->merge[0]->src);
1152+
len = interpret_upstream_mark(name, namelen, at - name, buf);
1153+
if (len > 0)
1154+
return len;
11221155
}
1123-
free(cp);
1124-
cp = shorten_unambiguous_ref(upstream->merge[0]->dst, 0);
1125-
strbuf_reset(buf);
1126-
strbuf_addstr(buf, cp);
1127-
free(cp);
1128-
return len;
1156+
1157+
return -1;
11291158
}
11301159

11311160
int strbuf_branchname(struct strbuf *sb, const char *name)

t/t1507-rev-parse-upstream.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ test_expect_success 'setup' '
1717
test_commit 4 &&
1818
git branch --track my-side origin/side &&
1919
git branch --track local-master master &&
20+
git branch --track fun@ny origin/side &&
21+
git branch --track @funny origin/side &&
22+
git branch --track funny@ origin/side &&
2023
git remote add -t master master-only .. &&
2124
git fetch master-only &&
2225
git branch bad-upstream &&
@@ -54,6 +57,24 @@ test_expect_success 'my-side@{upstream} resolves to correct full name' '
5457
test refs/remotes/origin/side = "$(full_name my-side@{u})"
5558
'
5659

60+
test_expect_success 'upstream of branch with @ in middle' '
61+
full_name fun@ny@{u} >actual &&
62+
echo refs/remotes/origin/side >expect &&
63+
test_cmp expect actual
64+
'
65+
66+
test_expect_success 'upstream of branch with @ at start' '
67+
full_name @funny@{u} >actual &&
68+
echo refs/remotes/origin/side >expect &&
69+
test_cmp expect actual
70+
'
71+
72+
test_expect_success 'upstream of branch with @ at end' '
73+
full_name funny@@{u} >actual &&
74+
echo refs/remotes/origin/side >expect &&
75+
test_cmp expect actual
76+
'
77+
5778
test_expect_success 'refs/heads/my-side@{upstream} does not resolve to my-side{upstream}' '
5879
test_must_fail full_name refs/heads/my-side@{upstream}
5980
'
@@ -210,4 +231,20 @@ test_expect_success 'log -g other@{u}@{now}' '
210231
test_cmp expect actual
211232
'
212233

234+
test_expect_success '@{reflog}-parsing does not look beyond colon' '
235+
echo content >@{yesterday} &&
236+
git add @{yesterday} &&
237+
git commit -m "funny reflog file" &&
238+
git hash-object @{yesterday} >expect &&
239+
git rev-parse HEAD:@{yesterday} >actual
240+
'
241+
242+
test_expect_success '@{upstream}-parsing does not look beyond colon' '
243+
echo content >@{upstream} &&
244+
git add @{upstream} &&
245+
git commit -m "funny upstream file" &&
246+
git hash-object @{upstream} >expect &&
247+
git rev-parse HEAD:@{upstream} >actual
248+
'
249+
213250
test_done

t/t1508-at-combinations.sh

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ check() {
99
if test '$2' = 'commit'
1010
then
1111
git log -1 --format=%s '$1' >actual
12-
else
12+
elif test '$2' = 'ref'
13+
then
1314
git rev-parse --symbolic-full-name '$1' >actual
15+
else
16+
git cat-file -p '$1' >actual
1417
fi &&
1518
test_cmp expect actual
1619
"
@@ -82,4 +85,14 @@ check HEAD ref refs/heads/old-branch
8285
check "HEAD@{1}" commit new-two
8386
check "@{1}" commit old-one
8487

88+
test_expect_success 'create path with @' '
89+
echo content >normal &&
90+
echo content >fun@ny &&
91+
git add normal fun@ny &&
92+
git commit -m "funny path"
93+
'
94+
95+
check "@:normal" blob content
96+
check "@:fun@ny" blob content
97+
8598
test_done

0 commit comments

Comments
 (0)