sha1_name: implement @{push} shorthand

In a triangular workflow, each branch may have two distinct points of interest: the @{upstream} that you normally pull from, and the destination that you normally push to. There isn't a shorthand for the latter, but it's useful to have. For instance, you may want to know which commits you haven't pushed yet: git log @{push}.. Or as a more complicated example, imagine that you normally pull changes from origin/master (which you set as your @{upstream}), and push changes to your own personal fork (e.g., as myfork/topic). You may push to your fork from multiple machines, requiring you to integrate the changes from the push destination, rather than upstream. With this patch, you can just do: git rebase @{push} rather than typing out the full name. The heavy lifting is all done by branch_get_push; here we just wire it up to the "@{push}" syntax. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed May 21, 2015 at 00:45 UTC adfe5d04345631299f9a4518d56c6dd3d47eb0b3
3 files changed +101 -1
Documentation/revisions.txt
+25
@@ -98,6 +98,31 @@ some output processing may assume ref names in UTF-8.
98 `branch.<name>.merge`). A missing branchname defaults to the
99 current one.
100
101 +'<branchname>@\{push\}', e.g. 'master@\{push\}', '@\{push\}'::
102 + The suffix '@\{push}' reports the branch "where we would push to" if
103 + `git push` were run while `branchname` was checked out (or the current
104 + 'HEAD' if no branchname is specified). Since our push destination is
105 + in a remote repository, of course, we report the local tracking branch
106 + that corresponds to that branch (i.e., something in 'refs/remotes/').
107 ++
108 +Here's an example to make it more clear:
109 ++
110 +------------------------------
111 +$ git config push.default current
112 +$ git config remote.pushdefault myfork
113 +$ git checkout -b mybranch origin/master
114 +
115 +$ git rev-parse --symbolic-full-name @{upstream}
116 +refs/remotes/origin/master
117 +
118 +$ git rev-parse --symbolic-full-name @{push}
119 +refs/remotes/myfork/mybranch
120 +------------------------------
121 ++
122 +Note in the example that we set up a triangular workflow, where we pull
123 +from one location and push to another. In a non-triangular workflow,
124 +'@\{push}' is the same as '@\{upstream}', and there is no need for it.
125 +
126 '<rev>{caret}', e.g. 'HEAD{caret}, v1.5.1{caret}0'::
127 A suffix '{caret}' to a revision parameter means the first parent of
128 that commit object. '{caret}<n>' means the <n>th parent (i.e.
sha1_name.c
+13 -1
@@ -435,6 +435,12 @@ static inline int upstream_mark(const char *string, int len)
435 return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
436 }
437
438 +static inline int push_mark(const char *string, int len)
439 +{
440 + const char *suffix[] = { "@{push}" };
441 + return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
442 +}
443 +
444 static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
445 static int interpret_nth_prior_checkout(const char *name, int namelen, struct strbuf *buf);
446
@@ -482,7 +488,8 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1,
488 nth_prior = 1;
489 continue;
490 }
485 - if (!upstream_mark(str + at, len - at)) {
491 + if (!upstream_mark(str + at, len - at) &&
492 + !push_mark(str + at, len - at)) {
493 reflog_len = (len-1) - (at+2);
494 len = at;
495 }
@@ -1145,6 +1152,11 @@ int interpret_branch_name(const char *name, int namelen, struct strbuf *buf)
1152 upstream_mark, branch_get_upstream);
1153 if (len > 0)
1154 return len;
1155 +
1156 + len = interpret_branch_mark(name, namelen, at - name, buf,
1157 + push_mark, branch_get_push);
1158 + if (len > 0)
1159 + return len;
1160 }
1161
1162 return -1;
t/t1514-rev-parse-push.sh new
+63
@@ -0,0 +1,63 @@
1 +#!/bin/sh
2 +
3 +test_description='test <branch>@{push} syntax'
4 +. ./test-lib.sh
5 +
6 +resolve () {
7 + echo "$2" >expect &&
8 + git rev-parse --symbolic-full-name "$1" >actual &&
9 + test_cmp expect actual
10 +}
11 +
12 +test_expect_success 'setup' '
13 + git init --bare parent.git &&
14 + git init --bare other.git &&
15 + git remote add origin parent.git &&
16 + git remote add other other.git &&
17 + test_commit base &&
18 + git push origin HEAD &&
19 + git branch --set-upstream-to=origin/master master &&
20 + git branch --track topic origin/master &&
21 + git push origin topic &&
22 + git push other topic
23 +'
24 +
25 +test_expect_success '@{push} with default=nothing' '
26 + test_config push.default nothing &&
27 + test_must_fail git rev-parse master@{push}
28 +'
29 +
30 +test_expect_success '@{push} with default=simple' '
31 + test_config push.default simple &&
32 + resolve master@{push} refs/remotes/origin/master
33 +'
34 +
35 +test_expect_success 'triangular @{push} fails with default=simple' '
36 + test_config push.default simple &&
37 + test_must_fail git rev-parse topic@{push}
38 +'
39 +
40 +test_expect_success '@{push} with default=current' '
41 + test_config push.default current &&
42 + resolve topic@{push} refs/remotes/origin/topic
43 +'
44 +
45 +test_expect_success '@{push} with default=matching' '
46 + test_config push.default matching &&
47 + resolve topic@{push} refs/remotes/origin/topic
48 +'
49 +
50 +test_expect_success '@{push} with pushremote defined' '
51 + test_config push.default current &&
52 + test_config branch.topic.pushremote other &&
53 + resolve topic@{push} refs/remotes/other/topic
54 +'
55 +
56 +test_expect_success '@{push} with push refspecs' '
57 + test_config push.default nothing &&
58 + test_config remote.origin.push refs/heads/*:refs/heads/magic/* &&
59 + git push &&
60 + resolve topic@{push} refs/remotes/origin/magic/topic
61 +'
62 +
63 +test_done