@@ -516,28 +516,6 @@ const char *pushremote_for_branch(struct branch *branch, int *explicit)
return remote_for_branch(branch, explicit);
}
-const char *remote_ref_for_branch(struct branch *branch, int for_push)
-{
- if (branch) {
- if (!for_push) {
- if (branch->merge_nr) {
- return branch->merge_name[0];
- }
- } else {
- const char *dst, *remote_name =
- pushremote_for_branch(branch, NULL);
- struct remote *remote = remote_get(remote_name);
-
- if (remote && remote->push.nr &&
- (dst = apply_refspecs(&remote->push,
- branch->refname))) {
- return dst;
- }
- }
- }
- return NULL;
-}
-
static struct remote *remote_get_1(const char *name,
const char *(*get_default)(struct branch *, int *))
{
@@ -1663,6 +1641,67 @@ static int is_workflow_triangular(struct branch *branch)
return (fetch_remote && push_remote && fetch_remote != push_remote);
}
+/**
+ * Return the local name of the remote tracking branch, as in
+ * %(push:remoteref), that corresponds to the ref we would push to given a
+ * bare `git push` while `branch` is checked out.
+ * See also branch_get_push_1 below.
+ */
+static const char *branch_get_push_remoteref(struct branch *branch)
+{
+ struct remote *remote;
+
+ remote = remote_get(pushremote_for_branch(branch, NULL));
+ if (!remote)
+ return NULL;
+
+ if (remote->push.nr) {
+ return apply_refspecs(&remote->push, branch->refname);
+ }
+
+ if (remote->mirror)
+ return branch->refname;
+
+ switch (push_default) {
+ case PUSH_DEFAULT_NOTHING:
+ return NULL;
+
+ case PUSH_DEFAULT_MATCHING:
+ case PUSH_DEFAULT_CURRENT:
+ return branch->refname;
+
+ case PUSH_DEFAULT_UPSTREAM:
+ if (is_workflow_triangular(branch))
+ return NULL;
+ else {
+ if (branch && branch->merge && branch->merge[0] &&
+ branch->merge[0]->dst)
+ return branch->merge[0]->src;
+ else
+ return NULL;
+ }
+
+ case PUSH_DEFAULT_UNSPECIFIED:
+ case PUSH_DEFAULT_SIMPLE:
+ {
+ const char *up, *cur;
+
+ if (is_workflow_triangular(branch))
+ return branch->refname;
+ else {
+ up = branch_get_upstream(branch, NULL);
+ cur = tracking_for_push_dest(remote, branch->refname, NULL);
+ if (up && cur && !strcmp(cur, up))
+ return branch->refname;
+ else
+ return NULL;
+ }
+
+ }
+ }
+ BUG("unhandled push situation");
+}
+
/**
* Return the tracking branch, as in %(push), that corresponds to the ref we
* would push to given a bare `git push` while `branch` is checked out.
@@ -1755,6 +1794,20 @@ static int ignore_symref_update(const char *refname)
return (flag & REF_ISSYMREF);
}
+const char *remote_ref_for_branch(struct branch *branch, int for_push)
+{
+ if (branch) {
+ if (!for_push) {
+ if (branch->merge_nr) {
+ return branch->merge_name[0];
+ }
+ } else {
+ return branch_get_push_remoteref(branch);
+ }
+ }
+ return NULL;
+}
+
/*
* Create and return a list of (struct ref) consisting of copies of
* each remote_ref that matches refspec. refspec must be a pattern.
@@ -875,13 +875,80 @@ test_expect_success ':remotename and :remoteref' '
git for-each-ref --format="${pair%=*}" \
refs/heads/master >actual &&
test_cmp expect actual
- done &&
- git branch push-simple &&
- git config branch.push-simple.pushRemote from &&
- actual="$(git for-each-ref \
- --format="%(push:remotename),%(push:remoteref)" \
- refs/heads/push-simple)" &&
- test from, = "$actual"
+ done
+ )
+'
+
+test_expect_success '%(push) and %(push:remoteref)' '
+ git init pushremote-tests &&
+ (
+ cd pushremote-tests &&
+ test_commit initial &&
+ git remote add from fifth.coffee:blub &&
+ git config branch.master.remote from &&
+ actual="$(git -c push.default=simple for-each-ref \
+ --format="%(push:remotename),%(push:remoteref),%(push)" \
+ refs/heads/master)" &&
+ test from,, = "$actual" &&
+ git config branch.master.merge refs/heads/master &&
+ actual="$(git -c push.default=simple for-each-ref \
+ --format="%(push:remotename),%(push:remoteref),%(push)" \
+ refs/heads/master)" &&
+ test from,refs/heads/master,refs/remotes/from/master = "$actual" &&
+ git config branch.master.merge refs/heads/other &&
+ actual="$(git -c push.default=simple for-each-ref \
+ --format="%(push:remotename),%(push:remoteref),%(push)" \
+ refs/heads/master)" &&
+ test from,, = "$actual" &&
+ actual="$(git -c push.default=upstream for-each-ref \
+ --format="%(push:remotename),%(push:remoteref),%(push)" \
+ refs/heads/master)" &&
+ test from,refs/heads/other,refs/remotes/from/other = "$actual" &&
+ actual="$(git -c push.default=current for-each-ref \
+ --format="%(push:remotename),%(push:remoteref),%(push)" \
+ refs/heads/master)" &&
+ test from,refs/heads/master,refs/remotes/from/master = "$actual" &&
+ actual="$(git -c push.default=matching for-each-ref \
+ --format="%(push:remotename),%(push:remoteref),%(push)" \
+ refs/heads/master)" &&
+ test from,refs/heads/master,refs/remotes/from/master = "$actual" &&
+ actual="$(git -c push.default=nothing for-each-ref \
+ --format="%(push:remotename),%(push:remoteref),%(push)" \
+ refs/heads/master)" &&
+ test from,, = "$actual" &&
+ git remote add to southridge.audio:repo &&
+ git config branch.master.pushRemote to &&
+ git config --unset branch.master.merge &&
+ actual="$(git -c push.default=simple for-each-ref \
+ --format="%(push:remotename),%(push:remoteref),%(push)" \
+ refs/heads/master)" &&
+ test to,refs/heads/master,refs/remotes/to/master = "$actual" &&
+ git config branch.master.merge refs/heads/master &&
+ actual="$(git -c push.default=simple for-each-ref \
+ --format="%(push:remotename),%(push:remoteref),%(push)" \
+ refs/heads/master)" &&
+ test to,refs/heads/master,refs/remotes/to/master = "$actual" &&
+ git config branch.master.merge refs/heads/other &&
+ actual="$(git -c push.default=simple for-each-ref \
+ --format="%(push:remotename),%(push:remoteref),%(push)" \
+ refs/heads/master)" &&
+ test to,refs/heads/master,refs/remotes/to/master = "$actual" &&
+ actual="$(git -c push.default=upstream for-each-ref \
+ --format="%(push:remotename),%(push:remoteref),%(push)" \
+ refs/heads/master)" &&
+ test to,, = "$actual" &&
+ actual="$(git -c push.default=current for-each-ref \
+ --format="%(push:remotename),%(push:remoteref),%(push)" \
+ refs/heads/master)" &&
+ test to,refs/heads/master,refs/remotes/to/master = "$actual" &&
+ actual="$(git -c push.default=matching for-each-ref \
+ --format="%(push:remotename),%(push:remoteref),%(push)" \
+ refs/heads/master)" &&
+ test to,refs/heads/master,refs/remotes/to/master = "$actual" &&
+ actual="$(git -c push.default=nothing for-each-ref \
+ --format="%(push:remotename),%(push:remoteref),%(push)" \
+ refs/heads/master)" &&
+ test to,, = "$actual"
)
'
Looking at the value of %(push:remoteref) only handles the case when an explicit push refspec is passed. But it does not handle the fallback cases of looking at the configuration value of `push.default`. In particular, doing something like git config push.default current git for-each-ref --format='%(push)' git for-each-ref --format='%(push:remoteref)' prints a useful tracking ref for the first for-each-ref, but an empty string for the second. Since the intention of %(push:remoteref), from 9700fae5ee (for-each-ref: let upstream/push report the remote ref name) is to get exactly which branch `git push` will push to, even in the fallback cases, fix this. To get the meaning of %(push:remoteref), `ref-filter.c` calls `remote_ref_for_branch`. We simply add a new static helper function, `branch_get_push_remoteref` that follows the logic of `branch_get_push_1`, and call it from `remote_ref_for_branch`. We also update t/6300-for-each-ref.sh to handle all `push.default` strategies. This involves testing `push.default=simple` twice, once where there is a matching upstream branch and once when there is none. Finally we also test for triangular workflows. Signed-off-by: Damien Robert <damien.olivier.robert+git@gmail.com> --- remote.c | 97 +++++++++++++++++++++++++++++++---------- t/t6300-for-each-ref.sh | 81 +++++++++++++++++++++++++++++++--- 2 files changed, 149 insertions(+), 29 deletions(-)