From: Jeff King <peff@peff.net>
To: git@vger.kernel.org
Subject: [PATCH 6/6] sha1_name: implement @{push} shorthand
Date: Tue, 31 Mar 2015 13:38:35 -0400 [thread overview]
Message-ID: <20150331173834.GF18912@peff.net> (raw)
In-Reply-To: <20150331173339.GA17732@peff.net>
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.
Signed-off-by: Jeff King <peff@peff.net>
---
As an aside, I messed up an &&-chain in the tests, which was caught by
GIT_TEST_CHAIN_LINT before submitting. The system works!
Documentation/revisions.txt | 25 +++++++++++
sha1_name.c | 102 +++++++++++++++++++++++++++++++++++++++++++-
t/t1514-rev-parse-push.sh | 63 +++++++++++++++++++++++++++
3 files changed, 189 insertions(+), 1 deletion(-)
create mode 100755 t/t1514-rev-parse-push.sh
diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt
index 0796118..5d9df25 100644
--- a/Documentation/revisions.txt
+++ b/Documentation/revisions.txt
@@ -98,6 +98,31 @@ some output processing may assume ref names in UTF-8.
`branch.<name>.merge`). A missing branchname defaults to the
current one.
+'<branchname>@\{push\}', e.g. 'master@\{push\}', '@\{push\}'::
+ The suffix `@{push}` reports the branch "where we would push to" if
+ `git push` were run while `branchname` was checked out (or the current
+ `HEAD` is no branchname is specified). Since our push destination is
+ in a remote repository, of course, we report the local tracking branch
+ that corresponds to that branch (i.e., something in `refs/remotes/`).
++
+Here's an example to make it more clear:
++
+------------------------------
+$ git config push.default current
+$ git config remote.pushdefault myfork
+$ git checkout -b mybranch origin/master
+
+$ git rev-parse --symbolic-full-name @{upstream}
+refs/remotes/origin/master
+
+$ git rev-parse --symbolic-full-name @{push}
+refs/remotes/myfork/mybranch
+------------------------------
++
+Note in the example that we set up a triangular workflow, where we pull
+from one location and push to another. In a non-triangular workflow,
+`@{push}` is the same as `@{upstream}`, and there is no need for it.
+
'<rev>{caret}', e.g. 'HEAD{caret}, v1.5.1{caret}0'::
A suffix '{caret}' to a revision parameter means the first parent of
that commit object. '{caret}<n>' means the <n>th parent (i.e.
diff --git a/sha1_name.c b/sha1_name.c
index 3741ca3..1da3291 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -435,6 +435,12 @@ static inline int upstream_mark(const char *string, int len)
return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
}
+static inline int push_mark(const char *string, int len)
+{
+ const char *suffix[] = { "@{push}" };
+ return at_mark(string, len, suffix, ARRAY_SIZE(suffix));
+}
+
static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
static int interpret_nth_prior_checkout(const char *name, int namelen, struct strbuf *buf);
@@ -482,7 +488,8 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1,
nth_prior = 1;
continue;
}
- if (!upstream_mark(str + at, len - at)) {
+ if (!upstream_mark(str + at, len - at) &&
+ !push_mark(str + at, len - at)) {
reflog_len = (len-1) - (at+2);
len = at;
}
@@ -1104,6 +1111,95 @@ static int interpret_upstream_mark(const char *name, int namelen,
return len + at;
}
+static char *tracking_ref_for(struct remote *remote, const char *refname)
+{
+ char *ret;
+
+ ret = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname);
+ if (!ret)
+ die(_("@{push} has no local tracking branch for remote '%s'"),
+ refname);
+ return ret;
+}
+
+static char *get_push_branch(const char *name, int len)
+{
+ char *branch_name;
+ struct branch *branch;
+ struct remote *remote;
+
+ branch_name = xmemdupz(name, len);
+ branch = branch_get(*branch_name ? branch_name : NULL);
+ if (!branch)
+ die(_("HEAD does not point to a branch"));
+ free(branch_name);
+
+ remote = remote_get(pushremote_for_branch(branch, NULL));
+ if (!remote)
+ die(_("branch '%s' has no remote for pushing"), branch->name);
+
+ if (remote->push_refspec_nr) {
+ char *dst, *ret;
+
+ dst = apply_refspecs(remote->push, remote->push_refspec_nr,
+ branch->refname);
+ if (!dst)
+ die(_("push refspecs for '%s' do not include '%s'"),
+ remote->name, branch->name);
+
+ ret = tracking_ref_for(remote, dst);
+ free(dst);
+ return ret;
+ }
+
+ if (remote->mirror)
+ return tracking_ref_for(remote, branch->refname);
+
+ switch (push_default) {
+ case PUSH_DEFAULT_NOTHING:
+ die(_("@{push} has no destination (push.default is 'nothing'"));
+
+ case PUSH_DEFAULT_MATCHING:
+ case PUSH_DEFAULT_CURRENT:
+ return tracking_ref_for(remote, branch->refname);
+
+ case PUSH_DEFAULT_UPSTREAM:
+ return xstrdup(get_upstream_branch(name, len));
+
+ case PUSH_DEFAULT_UNSPECIFIED:
+ case PUSH_DEFAULT_SIMPLE:
+ {
+ const char *up = get_upstream_branch(name, len);
+ char *cur = tracking_ref_for(remote, branch->refname);
+ if (strcmp(cur, up))
+ die("cannot resolve 'simple' @{push} to a single destination");
+ return cur;
+ }
+ }
+
+ die("BUG: unhandled @{push} situation");
+}
+
+static int interpret_push_mark(const char *name, int namelen,
+ int at, struct strbuf *buf)
+{
+ int len;
+ char *result;
+
+ len = push_mark(name + at, namelen - at);
+ if (!len)
+ return -1;
+
+ if (memchr(name, ':', at))
+ return -1;
+
+ result = get_push_branch(name, at);
+ set_shortened_ref(buf, result);
+ free(result);
+
+ return len + at;
+}
+
/*
* 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
@@ -1154,6 +1250,10 @@ int interpret_branch_name(const char *name, int namelen, struct strbuf *buf)
len = interpret_upstream_mark(name, namelen, at - name, buf);
if (len > 0)
return len;
+
+ len = interpret_push_mark(name, namelen, at - name, buf);
+ if (len > 0)
+ return len;
}
return -1;
diff --git a/t/t1514-rev-parse-push.sh b/t/t1514-rev-parse-push.sh
new file mode 100755
index 0000000..7214f5b
--- /dev/null
+++ b/t/t1514-rev-parse-push.sh
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+test_description='test <branch>@{push} syntax'
+. ./test-lib.sh
+
+resolve () {
+ echo "$2" >expect &&
+ git rev-parse --symbolic-full-name "$1" >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'setup' '
+ git init --bare parent.git &&
+ git init --bare other.git &&
+ git remote add origin parent.git &&
+ git remote add other other.git &&
+ test_commit base &&
+ git push origin HEAD &&
+ git branch --set-upstream-to=origin/master master &&
+ git branch --track topic origin/master &&
+ git push origin topic &&
+ git push other topic
+'
+
+test_expect_success '@{push} with default=nothing' '
+ test_config push.default nothing &&
+ test_must_fail git rev-parse master@{push}
+'
+
+test_expect_success '@{push} with default=simple' '
+ test_config push.default simple &&
+ resolve master@{push} refs/remotes/origin/master
+'
+
+test_expect_success 'triangular @{push} fails with default=simple' '
+ test_config push.default simple &&
+ test_must_fail git rev-parse topic@{push}
+'
+
+test_expect_success '@{push} with default=current' '
+ test_config push.default current &&
+ resolve topic@{push} refs/remotes/origin/topic
+'
+
+test_expect_success '@{push} with default=matching' '
+ test_config push.default matching &&
+ resolve topic@{push} refs/remotes/origin/topic
+'
+
+test_expect_success '@{push} with pushremote defined' '
+ test_config push.default current &&
+ test_config branch.topic.pushremote other &&
+ resolve topic@{push} refs/remotes/other/topic
+'
+
+test_expect_success '@{push} with push refspecs' '
+ test_config push.default nothing &&
+ test_config remote.origin.push refs/heads/*:refs/heads/magic/* &&
+ git push &&
+ resolve topic@{push} refs/remotes/origin/magic/topic
+'
+
+test_done
--
2.4.0.rc0.363.gf9f328b
next prev parent reply other threads:[~2015-03-31 17:38 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2015-03-31 17:33 [PATCH 0/6] implement @{push} shorthand Jeff King
2015-03-31 17:34 ` [PATCH 1/6] remote.c: drop default_remote_name variable Jeff King
2015-03-31 20:37 ` Junio C Hamano
2015-03-31 22:22 ` Jeff King
2015-03-31 17:35 ` [PATCH 2/6] remote.c: drop "remote" pointer from "struct branch" Jeff King
2015-03-31 20:50 ` Junio C Hamano
2015-03-31 22:24 ` Jeff King
2015-03-31 22:29 ` Junio C Hamano
2015-03-31 17:36 ` [PATCH 3/6] remote.c: hoist branch.*.remote lookup out of remote_get_1 Jeff King
2015-03-31 17:37 ` [PATCH 4/6] remote.c: provide per-branch pushremote name Jeff King
2015-03-31 21:41 ` Junio C Hamano
2015-03-31 17:37 ` [PATCH 5/6] sha1_name: refactor upstream_mark Jeff King
2015-03-31 17:38 ` Jeff King [this message]
2015-03-31 21:37 ` [PATCH 6/6] sha1_name: implement @{push} shorthand Junio C Hamano
2015-03-31 22:32 ` Jeff King
2015-03-31 22:57 ` Junio C Hamano
2015-03-31 21:41 ` Eric Sunshine
2015-03-31 22:33 ` Jeff King
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20150331173834.GF18912@peff.net \
--to=peff@peff.net \
--cc=git@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).