Skip to content

Commit bb1c8fb

Browse files
committed
Merge branch 'fc/at-head'
Instead of typing four capital letters "HEAD", you can say "@" instead. * fc/at-head: sha1_name: compare variable with constant, not constant with variable Add new @ shortcut for HEAD sha1_name: refactor reinterpret() sha1_name: check @{-N} errors sooner sha1_name: reorganize get_sha1_basic() sha1_name: don't waste cycles in the @-parsing loop sha1_name: remove unnecessary braces sha1_name: remove no-op tests: at-combinations: @{N} versus HEAD@{N} tests: at-combinations: increase coverage tests: at-combinations: improve nonsense() tests: at-combinations: check ref names directly tests: at-combinations: simplify setup
2 parents 96d339f + 1f27e7d commit bb1c8fb

File tree

5 files changed

+123
-57
lines changed

5 files changed

+123
-57
lines changed

Documentation/git-check-ref-format.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ Git imposes the following rules on how references are named:
5454

5555
. They cannot contain a sequence `@{`.
5656

57+
. They cannot be the single character `@`.
58+
5759
. They cannot contain a `\`.
5860

5961
These rules make it easy for shell script based tools to parse

Documentation/revisions.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ the '$GIT_DIR/refs' directory or from the '$GIT_DIR/packed-refs' file.
5858
While the ref name encoding is unspecified, UTF-8 is preferred as
5959
some output processing may assume ref names in UTF-8.
6060

61+
'@'::
62+
'@' alone is a shortcut for 'HEAD'.
63+
6164
'<refname>@\{<date>\}', e.g. 'master@\{yesterday\}', 'HEAD@\{5 minutes ago\}'::
6265
A ref followed by the suffix '@' with a date specification
6366
enclosed in a brace

refs.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ int check_refname_format(const char *refname, int flags)
7272
{
7373
int component_len, component_count = 0;
7474

75+
if (!strcmp(refname, "@"))
76+
/* Refname is a single character '@'. */
77+
return -1;
78+
7579
while (1) {
7680
/* We are at the start of a path component. */
7781
component_len = check_refname_component(refname, flags);

sha1_name.c

Lines changed: 66 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ static inline int upstream_mark(const char *string, int len)
431431
}
432432

433433
static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
434+
static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf);
434435

435436
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
436437
{
@@ -448,7 +449,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
448449
unsigned char tmp_sha1[20];
449450
char *real_ref = NULL;
450451
int refs_found = 0;
451-
int at, reflog_len;
452+
int at, reflog_len, nth_prior = 0;
452453

453454
if (len == 40 && !get_sha1_hex(str, sha1)) {
454455
refs_found = dwim_ref(str, len, tmp_sha1, &real_ref);
@@ -464,8 +465,15 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
464465
/* basic@{time or number or -number} format to query ref-log */
465466
reflog_len = at = 0;
466467
if (len && str[len-1] == '}') {
467-
for (at = len-2; at >= 0; at--) {
468+
for (at = len-4; at >= 0; at--) {
468469
if (str[at] == '@' && str[at+1] == '{') {
470+
if (str[at+2] == '-') {
471+
if (at != 0)
472+
/* @{-N} not at start */
473+
return -1;
474+
nth_prior = 1;
475+
continue;
476+
}
469477
if (!upstream_mark(str + at, len - at)) {
470478
reflog_len = (len-1) - (at+2);
471479
len = at;
@@ -479,20 +487,22 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
479487
if (len && ambiguous_path(str, len))
480488
return -1;
481489

482-
if (!len && reflog_len) {
490+
if (nth_prior) {
483491
struct strbuf buf = STRBUF_INIT;
484-
int ret;
485-
/* try the @{-N} syntax for n-th checkout */
486-
ret = interpret_branch_name(str+at, &buf);
487-
if (ret > 0) {
488-
/* substitute this branch name and restart */
489-
return get_sha1_1(buf.buf, buf.len, sha1, 0);
490-
} else if (ret == 0) {
491-
return -1;
492+
int detached;
493+
494+
if (interpret_nth_prior_checkout(str, &buf) > 0) {
495+
detached = (buf.len == 40 && !get_sha1_hex(buf.buf, sha1));
496+
strbuf_release(&buf);
497+
if (detached)
498+
return 0;
492499
}
500+
}
501+
502+
if (!len && reflog_len)
493503
/* allow "@{...}" to mean the current branch reflog */
494504
refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
495-
} else if (reflog_len)
505+
else if (reflog_len)
496506
refs_found = dwim_log(str, len, sha1, &real_ref);
497507
else
498508
refs_found = dwim_ref(str, len, sha1, &real_ref);
@@ -511,10 +521,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
511521
unsigned long co_time;
512522
int co_tz, co_cnt;
513523

514-
/* a @{-N} placed anywhere except the start is an error */
515-
if (str[at+2] == '-')
516-
return -1;
517-
518524
/* Is it asking for N-th entry, or approxidate? */
519525
for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
520526
char ch = str[at+2+i];
@@ -996,6 +1002,38 @@ int get_sha1_mb(const char *name, unsigned char *sha1)
9961002
return st;
9971003
}
9981004

1005+
/* parse @something syntax, when 'something' is not {.*} */
1006+
static int interpret_empty_at(const char *name, int namelen, int len, struct strbuf *buf)
1007+
{
1008+
if (len || name[1] == '{')
1009+
return -1;
1010+
1011+
strbuf_reset(buf);
1012+
strbuf_add(buf, "HEAD", 4);
1013+
return 1;
1014+
}
1015+
1016+
static int reinterpret(const char *name, int namelen, int len, struct strbuf *buf)
1017+
{
1018+
/* we have extra data, which might need further processing */
1019+
struct strbuf tmp = STRBUF_INIT;
1020+
int used = buf->len;
1021+
int ret;
1022+
1023+
strbuf_add(buf, name + len, namelen - len);
1024+
ret = interpret_branch_name(buf->buf, &tmp);
1025+
/* that data was not interpreted, remove our cruft */
1026+
if (ret < 0) {
1027+
strbuf_setlen(buf, used);
1028+
return len;
1029+
}
1030+
strbuf_reset(buf);
1031+
strbuf_addbuf(buf, &tmp);
1032+
strbuf_release(&tmp);
1033+
/* tweak for size of {-N} versus expanded ref name */
1034+
return ret - used + len;
1035+
}
1036+
9991037
/*
10001038
* This reads short-hand syntax that not only evaluates to a commit
10011039
* object name, but also can act as if the end user spelled the name
@@ -1025,36 +1063,27 @@ int interpret_branch_name(const char *name, struct strbuf *buf)
10251063
int len = interpret_nth_prior_checkout(name, buf);
10261064
int tmp_len;
10271065

1028-
if (!len)
1066+
if (!len) {
10291067
return len; /* syntax Ok, not enough switches */
1030-
if (0 < len && len == namelen)
1031-
return len; /* consumed all */
1032-
else if (0 < len) {
1033-
/* we have extra data, which might need further processing */
1034-
struct strbuf tmp = STRBUF_INIT;
1035-
int used = buf->len;
1036-
int ret;
1037-
1038-
strbuf_add(buf, name + len, namelen - len);
1039-
ret = interpret_branch_name(buf->buf, &tmp);
1040-
/* that data was not interpreted, remove our cruft */
1041-
if (ret < 0) {
1042-
strbuf_setlen(buf, used);
1043-
return len;
1044-
}
1045-
strbuf_reset(buf);
1046-
strbuf_addbuf(buf, &tmp);
1047-
strbuf_release(&tmp);
1048-
/* tweak for size of {-N} versus expanded ref name */
1049-
return ret - used + len;
1068+
} else if (len > 0) {
1069+
if (len == namelen)
1070+
return len; /* consumed all */
1071+
else
1072+
return reinterpret(name, namelen, len, buf);
10501073
}
10511074

10521075
cp = strchr(name, '@');
10531076
if (!cp)
10541077
return -1;
1078+
1079+
len = interpret_empty_at(name, namelen, cp - name, buf);
1080+
if (len > 0)
1081+
return reinterpret(name, namelen, len, buf);
1082+
10551083
tmp_len = upstream_mark(cp, namelen - (cp - name));
10561084
if (!tmp_len)
10571085
return -1;
1086+
10581087
len = cp + tmp_len - name;
10591088
cp = xstrndup(name, cp - name);
10601089
upstream = branch_get(*cp ? cp : NULL);

t/t1508-at-combinations.sh

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,24 @@ test_description='test various @{X} syntax combinations together'
44
. ./test-lib.sh
55

66
check() {
7-
test_expect_${3:-success} "$1 = $2" "
8-
echo '$2' >expect &&
9-
git log -1 --format=%s '$1' >actual &&
10-
test_cmp expect actual
11-
"
7+
test_expect_${4:-success} "$1 = $3" "
8+
echo '$3' >expect &&
9+
if test '$2' = 'commit'
10+
then
11+
git log -1 --format=%s '$1' >actual
12+
else
13+
git rev-parse --symbolic-full-name '$1' >actual
14+
fi &&
15+
test_cmp expect actual
16+
"
1217
}
18+
1319
nonsense() {
14-
test_expect_${2:-success} "$1 is nonsensical" "
15-
test_must_fail git log -1 '$1'
16-
"
20+
test_expect_${2:-success} "$1 is nonsensical" "
21+
test_must_fail git rev-parse --verify '$1'
22+
"
1723
}
24+
1825
fail() {
1926
"$@" failure
2027
}
@@ -31,21 +38,42 @@ test_expect_success 'setup' '
3138
git checkout -b new-branch &&
3239
test_commit new-one &&
3340
test_commit new-two &&
34-
git config branch.old-branch.remote . &&
35-
git config branch.old-branch.merge refs/heads/master &&
36-
git config branch.new-branch.remote . &&
37-
git config branch.new-branch.merge refs/heads/upstream-branch
41+
git branch -u master old-branch &&
42+
git branch -u upstream-branch new-branch
3843
'
3944

40-
check HEAD new-two
41-
check "@{1}" new-one
42-
check "@{-1}" old-two
43-
check "@{-1}@{1}" old-one
44-
check "@{u}" upstream-two
45-
check "@{u}@{1}" upstream-one
46-
check "@{-1}@{u}" master-two
47-
check "@{-1}@{u}@{1}" master-one
45+
check HEAD ref refs/heads/new-branch
46+
check "@{1}" commit new-one
47+
check "HEAD@{1}" commit new-one
48+
check "@{now}" commit new-two
49+
check "HEAD@{now}" commit new-two
50+
check "@{-1}" ref refs/heads/old-branch
51+
check "@{-1}@{0}" commit old-two
52+
check "@{-1}@{1}" commit old-one
53+
check "@{u}" ref refs/heads/upstream-branch
54+
check "HEAD@{u}" ref refs/heads/upstream-branch
55+
check "@{u}@{1}" commit upstream-one
56+
check "@{-1}@{u}" ref refs/heads/master
57+
check "@{-1}@{u}@{1}" commit master-one
58+
check "@" commit new-two
59+
check "@@{u}" ref refs/heads/upstream-branch
4860
nonsense "@{u}@{-1}"
61+
nonsense "@{0}@{0}"
4962
nonsense "@{1}@{u}"
63+
nonsense "HEAD@{-1}"
64+
nonsense "@{-1}@{-1}"
65+
66+
# @{N} versus HEAD@{N}
67+
68+
check "HEAD@{3}" commit old-two
69+
nonsense "@{3}"
70+
71+
test_expect_success 'switch to old-branch' '
72+
git checkout old-branch
73+
'
74+
75+
check HEAD ref refs/heads/old-branch
76+
check "HEAD@{1}" commit new-two
77+
check "@{1}" commit old-one
5078

5179
test_done

0 commit comments

Comments
 (0)