Introduce <branch>@{upstream} notation
authorJohannes Schindelin <Johannes.Schindelin@gmx.de>
Thu, 10 Sep 2009 15:25:57 +0000 (17:25 +0200)
committerJunio C Hamano <gitster@pobox.com>
Tue, 12 Jan 2010 22:00:58 +0000 (14:00 -0800)
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>
Documentation/git-rev-parse.txt
sha1_name.c
t/t1506-rev-parse-upstream.sh [new file with mode: 0755]

index 82045a2522799ccf3a03479bf4f4cd1fa1809879..923b56a1606877a5a38c315fdfd6bdf0da1eb966 100644 (file)
@@ -231,6 +231,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}'
index 44bb62d270739a232e87c90c05ce89fcc86bc15b..fb4e214a33f3b0ad588a313426fa65350040266e 100644 (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)
 {
@@ -238,9 +239,24 @@ static int ambiguous_path(const char *path, int len)
        return slash;
 }
 
+static inline int tracked_suffix(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 (len >= suffix_len && !memcmp(string + len - suffix_len,
+                                       suffix[i], suffix_len))
+                       return suffix_len;
+       }
+       return 0;
+}
+
 /*
  * *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 of the form @{-<n>} or
+ * of the form <branch>@{upstream}.
  */
 static char *substitute_branch_name(const char **string, int *len)
 {
@@ -254,6 +270,21 @@ static char *substitute_branch_name(const char **string, int *len)
                return (char *)*string;
        }
 
+       ret = tracked_suffix(*string, *len);
+       if (ret) {
+               char *ref = xstrndup(*string, *len - ret);
+               struct branch *tracking = branch_get(*ref ? ref : NULL);
+
+               if (!tracking)
+                       die ("No tracking branch found for '%s'", ref);
+               free(ref);
+               if (tracking->merge && tracking->merge[0]->dst) {
+                       *string = xstrdup(tracking->merge[0]->dst);
+                       *len = strlen(*string);
+                       return (char *)*string;
+               }
+       }
+
        return NULL;
 }
 
@@ -340,8 +371,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 (!tracked_suffix(str + at, len - at)) {
+                                       reflog_len = (len-1) - (at+2);
+                                       len = at;
+                               }
                                break;
                        }
                }
diff --git a/t/t1506-rev-parse-upstream.sh b/t/t1506-rev-parse-upstream.sh
new file mode 100755 (executable)
index 0000000..5abdc13
--- /dev/null
@@ -0,0 +1,69 @@
+#!/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_done