Merge branch 'js/refer-upstream'

* js/refer-upstream:
  Teach @{upstream} syntax to strbuf_branchanme()
  t1506: more test for @{upstream} syntax
  Introduce <branch>@{upstream} notation
This commit is contained in:
Junio C Hamano 2010-01-22 16:08:13 -08:00
commit 4ca1b62386
3 changed files with 192 additions and 13 deletions

View File

@ -234,6 +234,10 @@ when you run 'git merge'.
* The special construct '@\{-<n>\}' means the <n>th branch checked out
before the current one.
* The suffix '@{upstream}' to a ref (short form 'ref@{u}') refers to
the branch the ref is set to build on top of. Missing ref defaults
to the current branch.
* A suffix '{caret}' to a revision parameter means the first parent of
that commit object. '{caret}<n>' means the <n>th parent (i.e.
'rev{caret}'

View File

@ -5,6 +5,7 @@
#include "blob.h"
#include "tree-walk.h"
#include "refs.h"
#include "remote.h"
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
{
@ -240,7 +241,8 @@ static int ambiguous_path(const char *path, int len)
/*
* *string and *len will only be substituted, and *string returned (for
* later free()ing) if the string passed in is of the form @{-<n>}.
* later free()ing) if the string passed in is a magic short-hand form
* to name a branch.
*/
static char *substitute_branch_name(const char **string, int *len)
{
@ -323,6 +325,20 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
return logs_found;
}
static inline int upstream_mark(const char *string, int len)
{
const char *suffix[] = { "@{upstream}", "@{u}" };
int i;
for (i = 0; i < ARRAY_SIZE(suffix); i++) {
int suffix_len = strlen(suffix[i]);
if (suffix_len <= len
&& !memcmp(string, suffix[i], suffix_len))
return suffix_len;
}
return 0;
}
static int get_sha1_1(const char *name, int len, unsigned char *sha1);
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
@ -340,8 +356,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
if (len && str[len-1] == '}') {
for (at = len-2; at >= 0; at--) {
if (str[at] == '@' && str[at+1] == '{') {
reflog_len = (len-1) - (at+2);
len = at;
if (!upstream_mark(str + at, len - at)) {
reflog_len = (len-1) - (at+2);
len = at;
}
break;
}
}
@ -740,17 +758,10 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
}
/*
* This reads "@{-N}" syntax, finds the name of the Nth previous
* branch we were on, and places the name of the branch in the given
* buf and returns the number of characters parsed if successful.
*
* If the input is not of the accepted format, it returns a negative
* number to signal an error.
*
* If the input was ok but there are not N branch switches in the
* reflog, it returns 0.
* Parse @{-N} syntax, return the number of characters parsed
* if successful; otherwise signal an error with negative value.
*/
int interpret_branch_name(const char *name, struct strbuf *buf)
static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
{
long nth;
int i, retval;
@ -836,6 +847,60 @@ int get_sha1_mb(const char *name, unsigned char *sha1)
return st;
}
/*
* 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
* of the branch from the command line.
*
* - "@{-N}" finds the name of the Nth previous branch we were on, and
* places the name of the branch in the given buf and returns the
* number of characters parsed if successful.
*
* - "<branch>@{upstream}" finds the name of the other ref that
* <branch> is configured to merge with (missing <branch> defaults
* to the current branch), and places the name of the branch in the
* given buf and returns the number of characters parsed if
* successful.
*
* If the input is not of the accepted format, it returns a negative
* number to signal an error.
*
* If the input was ok but there are not N branch switches in the
* reflog, it returns 0.
*/
int interpret_branch_name(const char *name, struct strbuf *buf)
{
char *cp;
struct branch *upstream;
int namelen = strlen(name);
int len = interpret_nth_prior_checkout(name, buf);
int tmp_len;
if (!len)
return len; /* syntax Ok, not enough switches */
if (0 < len)
return len; /* consumed from the front */
cp = strchr(name, '@');
if (!cp)
return -1;
tmp_len = upstream_mark(cp, namelen - (cp - name));
if (!tmp_len)
return -1;
len = cp + tmp_len - name;
cp = xstrndup(name, cp - name);
upstream = branch_get(*cp ? cp : NULL);
if (!upstream
|| !upstream->merge
|| !upstream->merge[0]->dst)
return error("No upstream branch found for '%s'", cp);
free(cp);
cp = shorten_unambiguous_ref(upstream->merge[0]->dst, 0);
strbuf_reset(buf);
strbuf_addstr(buf, cp);
free(cp);
return len;
}
/*
* This is like "get_sha1_basic()", except it allows "sha1 expressions",
* notably "xyz^" for "parent of xyz"

110
t/t1506-rev-parse-upstream.sh Executable file
View File

@ -0,0 +1,110 @@
#!/bin/sh
test_description='test <branch>@{upstream} syntax'
. ./test-lib.sh
test_expect_success 'setup' '
test_commit 1 &&
git checkout -b side &&
test_commit 2 &&
git checkout master &&
git clone . clone &&
test_commit 3 &&
(cd clone &&
test_commit 4 &&
git branch --track my-side origin/side)
'
full_name () {
(cd clone &&
git rev-parse --symbolic-full-name "$@")
}
commit_subject () {
(cd clone &&
git show -s --pretty=format:%s "$@")
}
test_expect_success '@{upstream} resolves to correct full name' '
test refs/remotes/origin/master = "$(full_name @{upstream})"
'
test_expect_success '@{u} resolves to correct full name' '
test refs/remotes/origin/master = "$(full_name @{u})"
'
test_expect_success 'my-side@{upstream} resolves to correct full name' '
test refs/remotes/origin/side = "$(full_name my-side@{u})"
'
test_expect_success 'my-side@{u} resolves to correct commit' '
git checkout side &&
test_commit 5 &&
(cd clone && git fetch) &&
test 2 = "$(commit_subject my-side)" &&
test 5 = "$(commit_subject my-side@{u})"
'
test_expect_success 'not-tracking@{u} fails' '
test_must_fail full_name non-tracking@{u} &&
(cd clone && git checkout --no-track -b non-tracking) &&
test_must_fail full_name non-tracking@{u}
'
test_expect_success '<branch>@{u}@{1} resolves correctly' '
test_commit 6 &&
(cd clone && git fetch) &&
test 5 = $(commit_subject my-side@{u}@{1})
'
test_expect_success '@{u} without specifying branch fails on a detached HEAD' '
git checkout HEAD^0 &&
test_must_fail git rev-parse @{u}
'
test_expect_success 'checkout -b new my-side@{u} forks from the same' '
(
cd clone &&
git checkout -b new my-side@{u} &&
git rev-parse --symbolic-full-name my-side@{u} >expect &&
git rev-parse --symbolic-full-name new@{u} >actual &&
test_cmp expect actual
)
'
test_expect_success 'merge my-side@{u} records the correct name' '
(
sq="'\''" &&
cd clone || exit
git checkout master || exit
git branch -D new ;# can fail but is ok
git branch -t new my-side@{u} &&
git merge -s ours new@{u} &&
git show -s --pretty=format:%s >actual &&
echo "Merge remote branch ${sq}origin/side${sq}" >expect &&
test_cmp expect actual
)
'
test_expect_success 'branch -d other@{u}' '
git checkout -t -b other master &&
git branch -d @{u} &&
git for-each-ref refs/heads/master >actual &&
>expect &&
test_cmp expect actual
'
test_expect_success 'checkout other@{u}' '
git branch -f master HEAD &&
git checkout -t -b another master &&
git checkout @{u} &&
git symbolic-ref HEAD >actual &&
echo refs/heads/master >expect &&
test_cmp expect actual
'
test_done