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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.