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. Signed-off-by: Damien Robert <damien.olivier.robert+git@xxxxxxxxx> --- remote.c | 106 +++++++++++++++++++++++++++++++--------- t/t6300-for-each-ref.sh | 29 ++++++++++- 2 files changed, 112 insertions(+), 23 deletions(-) diff --git a/remote.c b/remote.c index c43196ec06..b3ce992d01 100644 --- a/remote.c +++ b/remote.c @@ -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 *)) { @@ -1656,6 +1634,76 @@ static const char *tracking_for_push_dest(struct remote *remote, return ret; } +/** + * 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) { + char *dst; + + dst = apply_refspecs(&remote->push, branch->refname); + if (!dst) + return NULL; + + return dst; + } + + 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 (!branch || !branch->merge || + !branch->merge[0] || !branch->merge[0]->dst) + return NULL; + + return branch->merge[0]->src; + } + + case PUSH_DEFAULT_UNSPECIFIED: + case PUSH_DEFAULT_SIMPLE: + { + const char *up, *cur; + + up = branch_get_upstream(branch, NULL); + if (!up) + return NULL; + cur = tracking_for_push_dest(remote, branch->refname, NULL); + if (!cur) + return NULL; + if (strcmp(cur, up)) + return NULL; + + return branch->refname; + } + } + + 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. + * See also branch_get_push_remoteref above. + */ static const char *branch_get_push_1(struct branch *branch, struct strbuf *err) { struct remote *remote; @@ -1735,6 +1783,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. diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 9c910ce746..60e21834fd 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -874,7 +874,34 @@ test_expect_success ':remotename and :remoteref' ' actual="$(git for-each-ref \ --format="%(push:remotename),%(push:remoteref)" \ refs/heads/push-simple)" && - test from, = "$actual" + test from, = "$actual" && + git config branch.push-simple.remote from && + git config branch.push-simple.merge refs/heads/master && + actual="$(git for-each-ref \ + --format="%(push:remotename),%(push:remoteref)" \ + refs/heads/push-simple)" && + test from, = "$actual" && + actual="$(git -c push.default=upstream for-each-ref \ + --format="%(push:remotename),%(push:remoteref)" \ + refs/heads/push-simple)" && + test from,refs/heads/master = "$actual" && + actual="$(git -c push.default=current for-each-ref \ + --format="%(push:remotename),%(push:remoteref)" \ + refs/heads/push-simple)" && + test from,refs/heads/push-simple = "$actual" && + actual="$(git -c push.default=matching for-each-ref \ + --format="%(push:remotename),%(push:remoteref)" \ + refs/heads/push-simple)" && + test from,refs/heads/push-simple = "$actual" && + actual="$(git -c push.default=nothing for-each-ref \ + --format="%(push:remotename),%(push:remoteref)" \ + refs/heads/push-simple)" && + test from, = "$actual" && + git config branch.push-simple.merge refs/heads/push-simple && + actual="$(git for-each-ref \ + --format="%(push:remotename),%(push:remoteref)" \ + refs/heads/push-simple)" && + test from,refs/heads/push-simple = "$actual" ) ' -- Patched on top of v2.25.1-377-g2d2118b814 (git version 2.25.1)