Skip to content

Commit 28fb843

Browse files
dschogitster
authored andcommitted
Introduce <branch>@{upstream} notation
A new notation '<branch>@{upstream}' refers to the branch <branch> is set to build on top of. Missing <branch> (i.e. '@{upstream}') defaults to the current branch. This allows you to run, for example, for l in list of local branches do git log --oneline --left-right $l...$l@{upstream} done to inspect each of the local branches you are interested in for the divergence from its upstream. Signed-off-by: Johannes Schindelin <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 902f235 commit 28fb843

File tree

3 files changed

+109
-3
lines changed

3 files changed

+109
-3
lines changed

Documentation/git-rev-parse.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ when you run 'git-merge'.
231231
* The special construct '@\{-<n>\}' means the <n>th branch checked out
232232
before the current one.
233233

234+
* The suffix '@{upstream}' to a ref (short form 'ref@{u}') refers to
235+
the branch the ref is set to build on top of. Missing ref defaults
236+
to the current branch.
237+
234238
* A suffix '{caret}' to a revision parameter means the first parent of
235239
that commit object. '{caret}<n>' means the <n>th parent (i.e.
236240
'rev{caret}'

sha1_name.c

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "blob.h"
66
#include "tree-walk.h"
77
#include "refs.h"
8+
#include "remote.h"
89

910
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
1011
{
@@ -238,9 +239,24 @@ static int ambiguous_path(const char *path, int len)
238239
return slash;
239240
}
240241

242+
static inline int tracked_suffix(const char *string, int len)
243+
{
244+
const char *suffix[] = { "@{upstream}", "@{u}" };
245+
int i;
246+
247+
for (i = 0; i < ARRAY_SIZE(suffix); i++) {
248+
int suffix_len = strlen(suffix[i]);
249+
if (len >= suffix_len && !memcmp(string + len - suffix_len,
250+
suffix[i], suffix_len))
251+
return suffix_len;
252+
}
253+
return 0;
254+
}
255+
241256
/*
242257
* *string and *len will only be substituted, and *string returned (for
243-
* later free()ing) if the string passed in is of the form @{-<n>}.
258+
* later free()ing) if the string passed in is of the form @{-<n>} or
259+
* of the form <branch>@{upstream}.
244260
*/
245261
static char *substitute_branch_name(const char **string, int *len)
246262
{
@@ -254,6 +270,21 @@ static char *substitute_branch_name(const char **string, int *len)
254270
return (char *)*string;
255271
}
256272

273+
ret = tracked_suffix(*string, *len);
274+
if (ret) {
275+
char *ref = xstrndup(*string, *len - ret);
276+
struct branch *tracking = branch_get(*ref ? ref : NULL);
277+
278+
if (!tracking)
279+
die ("No tracking branch found for '%s'", ref);
280+
free(ref);
281+
if (tracking->merge && tracking->merge[0]->dst) {
282+
*string = xstrdup(tracking->merge[0]->dst);
283+
*len = strlen(*string);
284+
return (char *)*string;
285+
}
286+
}
287+
257288
return NULL;
258289
}
259290

@@ -340,8 +371,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
340371
if (len && str[len-1] == '}') {
341372
for (at = len-2; at >= 0; at--) {
342373
if (str[at] == '@' && str[at+1] == '{') {
343-
reflog_len = (len-1) - (at+2);
344-
len = at;
374+
if (!tracked_suffix(str + at, len - at)) {
375+
reflog_len = (len-1) - (at+2);
376+
len = at;
377+
}
345378
break;
346379
}
347380
}

t/t1506-rev-parse-upstream.sh

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/bin/sh
2+
3+
test_description='test <branch>@{upstream} syntax'
4+
5+
. ./test-lib.sh
6+
7+
8+
test_expect_success 'setup' '
9+
10+
test_commit 1 &&
11+
git checkout -b side &&
12+
test_commit 2 &&
13+
git checkout master &&
14+
git clone . clone &&
15+
test_commit 3 &&
16+
(cd clone &&
17+
test_commit 4 &&
18+
git branch --track my-side origin/side)
19+
20+
'
21+
22+
full_name () {
23+
(cd clone &&
24+
git rev-parse --symbolic-full-name "$@")
25+
}
26+
27+
commit_subject () {
28+
(cd clone &&
29+
git show -s --pretty=format:%s "$@")
30+
}
31+
32+
test_expect_success '@{upstream} resolves to correct full name' '
33+
test refs/remotes/origin/master = "$(full_name @{upstream})"
34+
'
35+
36+
test_expect_success '@{u} resolves to correct full name' '
37+
test refs/remotes/origin/master = "$(full_name @{u})"
38+
'
39+
40+
test_expect_success 'my-side@{upstream} resolves to correct full name' '
41+
test refs/remotes/origin/side = "$(full_name my-side@{u})"
42+
'
43+
44+
test_expect_success 'my-side@{u} resolves to correct commit' '
45+
git checkout side &&
46+
test_commit 5 &&
47+
(cd clone && git fetch) &&
48+
test 2 = "$(commit_subject my-side)" &&
49+
test 5 = "$(commit_subject my-side@{u})"
50+
'
51+
52+
test_expect_success 'not-tracking@{u} fails' '
53+
test_must_fail full_name non-tracking@{u} &&
54+
(cd clone && git checkout --no-track -b non-tracking) &&
55+
test_must_fail full_name non-tracking@{u}
56+
'
57+
58+
test_expect_success '<branch>@{u}@{1} resolves correctly' '
59+
test_commit 6 &&
60+
(cd clone && git fetch) &&
61+
test 5 = $(commit_subject my-side@{u}@{1})
62+
'
63+
64+
test_expect_success '@{u} without specifying branch fails on a detached HEAD' '
65+
git checkout HEAD^0 &&
66+
test_must_fail git rev-parse @{u}
67+
'
68+
69+
test_done

0 commit comments

Comments
 (0)