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
This commit is contained in:
commit
bb1c8fbcc8
@ -54,6 +54,8 @@ Git imposes the following rules on how references are named:
|
|||||||
|
|
||||||
. They cannot contain a sequence `@{`.
|
. They cannot contain a sequence `@{`.
|
||||||
|
|
||||||
|
. They cannot be the single character `@`.
|
||||||
|
|
||||||
. They cannot contain a `\`.
|
. They cannot contain a `\`.
|
||||||
|
|
||||||
These rules make it easy for shell script based tools to parse
|
These rules make it easy for shell script based tools to parse
|
||||||
|
@ -58,6 +58,9 @@ the '$GIT_DIR/refs' directory or from the '$GIT_DIR/packed-refs' file.
|
|||||||
While the ref name encoding is unspecified, UTF-8 is preferred as
|
While the ref name encoding is unspecified, UTF-8 is preferred as
|
||||||
some output processing may assume ref names in UTF-8.
|
some output processing may assume ref names in UTF-8.
|
||||||
|
|
||||||
|
'@'::
|
||||||
|
'@' alone is a shortcut for 'HEAD'.
|
||||||
|
|
||||||
'<refname>@\{<date>\}', e.g. 'master@\{yesterday\}', 'HEAD@\{5 minutes ago\}'::
|
'<refname>@\{<date>\}', e.g. 'master@\{yesterday\}', 'HEAD@\{5 minutes ago\}'::
|
||||||
A ref followed by the suffix '@' with a date specification
|
A ref followed by the suffix '@' with a date specification
|
||||||
enclosed in a brace
|
enclosed in a brace
|
||||||
|
4
refs.c
4
refs.c
@ -72,6 +72,10 @@ int check_refname_format(const char *refname, int flags)
|
|||||||
{
|
{
|
||||||
int component_len, component_count = 0;
|
int component_len, component_count = 0;
|
||||||
|
|
||||||
|
if (!strcmp(refname, "@"))
|
||||||
|
/* Refname is a single character '@'. */
|
||||||
|
return -1;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
/* We are at the start of a path component. */
|
/* We are at the start of a path component. */
|
||||||
component_len = check_refname_component(refname, flags);
|
component_len = check_refname_component(refname, flags);
|
||||||
|
101
sha1_name.c
101
sha1_name.c
@ -431,6 +431,7 @@ static inline int upstream_mark(const char *string, int len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
|
static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
|
||||||
|
static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf);
|
||||||
|
|
||||||
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
|
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
|
||||||
{
|
{
|
||||||
@ -448,7 +449,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
|
|||||||
unsigned char tmp_sha1[20];
|
unsigned char tmp_sha1[20];
|
||||||
char *real_ref = NULL;
|
char *real_ref = NULL;
|
||||||
int refs_found = 0;
|
int refs_found = 0;
|
||||||
int at, reflog_len;
|
int at, reflog_len, nth_prior = 0;
|
||||||
|
|
||||||
if (len == 40 && !get_sha1_hex(str, sha1)) {
|
if (len == 40 && !get_sha1_hex(str, sha1)) {
|
||||||
refs_found = dwim_ref(str, len, tmp_sha1, &real_ref);
|
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)
|
|||||||
/* basic@{time or number or -number} format to query ref-log */
|
/* basic@{time or number or -number} format to query ref-log */
|
||||||
reflog_len = at = 0;
|
reflog_len = at = 0;
|
||||||
if (len && str[len-1] == '}') {
|
if (len && str[len-1] == '}') {
|
||||||
for (at = len-2; at >= 0; at--) {
|
for (at = len-4; at >= 0; at--) {
|
||||||
if (str[at] == '@' && str[at+1] == '{') {
|
if (str[at] == '@' && str[at+1] == '{') {
|
||||||
|
if (str[at+2] == '-') {
|
||||||
|
if (at != 0)
|
||||||
|
/* @{-N} not at start */
|
||||||
|
return -1;
|
||||||
|
nth_prior = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!upstream_mark(str + at, len - at)) {
|
if (!upstream_mark(str + at, len - at)) {
|
||||||
reflog_len = (len-1) - (at+2);
|
reflog_len = (len-1) - (at+2);
|
||||||
len = at;
|
len = at;
|
||||||
@ -479,20 +487,22 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
|
|||||||
if (len && ambiguous_path(str, len))
|
if (len && ambiguous_path(str, len))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (!len && reflog_len) {
|
if (nth_prior) {
|
||||||
struct strbuf buf = STRBUF_INIT;
|
struct strbuf buf = STRBUF_INIT;
|
||||||
int ret;
|
int detached;
|
||||||
/* try the @{-N} syntax for n-th checkout */
|
|
||||||
ret = interpret_branch_name(str+at, &buf);
|
if (interpret_nth_prior_checkout(str, &buf) > 0) {
|
||||||
if (ret > 0) {
|
detached = (buf.len == 40 && !get_sha1_hex(buf.buf, sha1));
|
||||||
/* substitute this branch name and restart */
|
strbuf_release(&buf);
|
||||||
return get_sha1_1(buf.buf, buf.len, sha1, 0);
|
if (detached)
|
||||||
} else if (ret == 0) {
|
return 0;
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!len && reflog_len)
|
||||||
/* allow "@{...}" to mean the current branch reflog */
|
/* allow "@{...}" to mean the current branch reflog */
|
||||||
refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
|
refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
|
||||||
} else if (reflog_len)
|
else if (reflog_len)
|
||||||
refs_found = dwim_log(str, len, sha1, &real_ref);
|
refs_found = dwim_log(str, len, sha1, &real_ref);
|
||||||
else
|
else
|
||||||
refs_found = dwim_ref(str, len, sha1, &real_ref);
|
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)
|
|||||||
unsigned long co_time;
|
unsigned long co_time;
|
||||||
int co_tz, co_cnt;
|
int co_tz, co_cnt;
|
||||||
|
|
||||||
/* a @{-N} placed anywhere except the start is an error */
|
|
||||||
if (str[at+2] == '-')
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
/* Is it asking for N-th entry, or approxidate? */
|
/* Is it asking for N-th entry, or approxidate? */
|
||||||
for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
|
for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
|
||||||
char ch = str[at+2+i];
|
char ch = str[at+2+i];
|
||||||
@ -996,6 +1002,38 @@ int get_sha1_mb(const char *name, unsigned char *sha1)
|
|||||||
return st;
|
return st;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* parse @something syntax, when 'something' is not {.*} */
|
||||||
|
static int interpret_empty_at(const char *name, int namelen, int len, struct strbuf *buf)
|
||||||
|
{
|
||||||
|
if (len || name[1] == '{')
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
strbuf_reset(buf);
|
||||||
|
strbuf_add(buf, "HEAD", 4);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int reinterpret(const char *name, int namelen, int len, struct strbuf *buf)
|
||||||
|
{
|
||||||
|
/* we have extra data, which might need further processing */
|
||||||
|
struct strbuf tmp = STRBUF_INIT;
|
||||||
|
int used = buf->len;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
strbuf_add(buf, name + len, namelen - len);
|
||||||
|
ret = interpret_branch_name(buf->buf, &tmp);
|
||||||
|
/* that data was not interpreted, remove our cruft */
|
||||||
|
if (ret < 0) {
|
||||||
|
strbuf_setlen(buf, used);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
strbuf_reset(buf);
|
||||||
|
strbuf_addbuf(buf, &tmp);
|
||||||
|
strbuf_release(&tmp);
|
||||||
|
/* tweak for size of {-N} versus expanded ref name */
|
||||||
|
return ret - used + len;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This reads short-hand syntax that not only evaluates to a commit
|
* This reads short-hand syntax that not only evaluates to a commit
|
||||||
* object name, but also can act as if the end user spelled the name
|
* 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)
|
|||||||
int len = interpret_nth_prior_checkout(name, buf);
|
int len = interpret_nth_prior_checkout(name, buf);
|
||||||
int tmp_len;
|
int tmp_len;
|
||||||
|
|
||||||
if (!len)
|
if (!len) {
|
||||||
return len; /* syntax Ok, not enough switches */
|
return len; /* syntax Ok, not enough switches */
|
||||||
if (0 < len && len == namelen)
|
} else if (len > 0) {
|
||||||
|
if (len == namelen)
|
||||||
return len; /* consumed all */
|
return len; /* consumed all */
|
||||||
else if (0 < len) {
|
else
|
||||||
/* we have extra data, which might need further processing */
|
return reinterpret(name, namelen, len, buf);
|
||||||
struct strbuf tmp = STRBUF_INIT;
|
|
||||||
int used = buf->len;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
strbuf_add(buf, name + len, namelen - len);
|
|
||||||
ret = interpret_branch_name(buf->buf, &tmp);
|
|
||||||
/* that data was not interpreted, remove our cruft */
|
|
||||||
if (ret < 0) {
|
|
||||||
strbuf_setlen(buf, used);
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
strbuf_reset(buf);
|
|
||||||
strbuf_addbuf(buf, &tmp);
|
|
||||||
strbuf_release(&tmp);
|
|
||||||
/* tweak for size of {-N} versus expanded ref name */
|
|
||||||
return ret - used + len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cp = strchr(name, '@');
|
cp = strchr(name, '@');
|
||||||
if (!cp)
|
if (!cp)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
len = interpret_empty_at(name, namelen, cp - name, buf);
|
||||||
|
if (len > 0)
|
||||||
|
return reinterpret(name, namelen, len, buf);
|
||||||
|
|
||||||
tmp_len = upstream_mark(cp, namelen - (cp - name));
|
tmp_len = upstream_mark(cp, namelen - (cp - name));
|
||||||
if (!tmp_len)
|
if (!tmp_len)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
len = cp + tmp_len - name;
|
len = cp + tmp_len - name;
|
||||||
cp = xstrndup(name, cp - name);
|
cp = xstrndup(name, cp - name);
|
||||||
upstream = branch_get(*cp ? cp : NULL);
|
upstream = branch_get(*cp ? cp : NULL);
|
||||||
|
@ -4,17 +4,24 @@ test_description='test various @{X} syntax combinations together'
|
|||||||
. ./test-lib.sh
|
. ./test-lib.sh
|
||||||
|
|
||||||
check() {
|
check() {
|
||||||
test_expect_${3:-success} "$1 = $2" "
|
test_expect_${4:-success} "$1 = $3" "
|
||||||
echo '$2' >expect &&
|
echo '$3' >expect &&
|
||||||
git log -1 --format=%s '$1' >actual &&
|
if test '$2' = 'commit'
|
||||||
|
then
|
||||||
|
git log -1 --format=%s '$1' >actual
|
||||||
|
else
|
||||||
|
git rev-parse --symbolic-full-name '$1' >actual
|
||||||
|
fi &&
|
||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
|
|
||||||
nonsense() {
|
nonsense() {
|
||||||
test_expect_${2:-success} "$1 is nonsensical" "
|
test_expect_${2:-success} "$1 is nonsensical" "
|
||||||
test_must_fail git log -1 '$1'
|
test_must_fail git rev-parse --verify '$1'
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
|
|
||||||
fail() {
|
fail() {
|
||||||
"$@" failure
|
"$@" failure
|
||||||
}
|
}
|
||||||
@ -31,21 +38,42 @@ test_expect_success 'setup' '
|
|||||||
git checkout -b new-branch &&
|
git checkout -b new-branch &&
|
||||||
test_commit new-one &&
|
test_commit new-one &&
|
||||||
test_commit new-two &&
|
test_commit new-two &&
|
||||||
git config branch.old-branch.remote . &&
|
git branch -u master old-branch &&
|
||||||
git config branch.old-branch.merge refs/heads/master &&
|
git branch -u upstream-branch new-branch
|
||||||
git config branch.new-branch.remote . &&
|
|
||||||
git config branch.new-branch.merge refs/heads/upstream-branch
|
|
||||||
'
|
'
|
||||||
|
|
||||||
check HEAD new-two
|
check HEAD ref refs/heads/new-branch
|
||||||
check "@{1}" new-one
|
check "@{1}" commit new-one
|
||||||
check "@{-1}" old-two
|
check "HEAD@{1}" commit new-one
|
||||||
check "@{-1}@{1}" old-one
|
check "@{now}" commit new-two
|
||||||
check "@{u}" upstream-two
|
check "HEAD@{now}" commit new-two
|
||||||
check "@{u}@{1}" upstream-one
|
check "@{-1}" ref refs/heads/old-branch
|
||||||
check "@{-1}@{u}" master-two
|
check "@{-1}@{0}" commit old-two
|
||||||
check "@{-1}@{u}@{1}" master-one
|
check "@{-1}@{1}" commit old-one
|
||||||
|
check "@{u}" ref refs/heads/upstream-branch
|
||||||
|
check "HEAD@{u}" ref refs/heads/upstream-branch
|
||||||
|
check "@{u}@{1}" commit upstream-one
|
||||||
|
check "@{-1}@{u}" ref refs/heads/master
|
||||||
|
check "@{-1}@{u}@{1}" commit master-one
|
||||||
|
check "@" commit new-two
|
||||||
|
check "@@{u}" ref refs/heads/upstream-branch
|
||||||
nonsense "@{u}@{-1}"
|
nonsense "@{u}@{-1}"
|
||||||
|
nonsense "@{0}@{0}"
|
||||||
nonsense "@{1}@{u}"
|
nonsense "@{1}@{u}"
|
||||||
|
nonsense "HEAD@{-1}"
|
||||||
|
nonsense "@{-1}@{-1}"
|
||||||
|
|
||||||
|
# @{N} versus HEAD@{N}
|
||||||
|
|
||||||
|
check "HEAD@{3}" commit old-two
|
||||||
|
nonsense "@{3}"
|
||||||
|
|
||||||
|
test_expect_success 'switch to old-branch' '
|
||||||
|
git checkout old-branch
|
||||||
|
'
|
||||||
|
|
||||||
|
check HEAD ref refs/heads/old-branch
|
||||||
|
check "HEAD@{1}" commit new-two
|
||||||
|
check "@{1}" commit old-one
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
Loading…
Reference in New Issue
Block a user