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 <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Johannes Schindelin committed
Sep 10, 2009 at 17:25 UTC
28fb84382b0eb728534dbe2972bbfec3f3d83dd9
3 files changed
+109
-3
Documentation/git-rev-parse.txt
+4
@@ -231,6 +231,10 @@ when you run 'git-merge'.
231
* The special construct '@\{-<n>\}' means the <n>th branch checked out
232
before the current one.
233
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
+
238
* A suffix '{caret}' to a revision parameter means the first parent of
239
that commit object. '{caret}<n>' means the <n>th parent (i.e.
240
'rev{caret}'
sha1_name.c
+36
-3
@@ -5,6 +5,7 @@
5
#include "blob.h"
6
#include "tree-walk.h"
7
#include "refs.h"
8
+#include "remote.h"
9
10
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
11
{
@@ -238,9 +239,24 @@ static int ambiguous_path(const char *path, int len)
239
return slash;
240
}
241
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
+
256
/*
257
* *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}.
260
*/
261
static char *substitute_branch_name(const char **string, int *len)
262
{
@@ -254,6 +270,21 @@ static char *substitute_branch_name(const char **string, int *len)
270
return (char *)*string;
271
}
272
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
+
288
return NULL;
289
}
290
@@ -340,8 +371,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
371
if (len && str[len-1] == '}') {
372
for (at = len-2; at >= 0; at--) {
373
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
+ }
378
break;
379
}
380
}
t/t1506-rev-parse-upstream.sh
new
+69
@@ -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