The "$remote/$branch" expression is often used as a shorthand with the intention of referring to the remote-tracking branch corresponding to the given $branch in the $remote repo. Currently, Git resolves this by prepending "refs/remotes/" to the given "$remote/$branch" expression, resulting in "refs/remotes/$remote/$branch", which is equivalent to the above intention only when conventional refspecs are being used (e.g. refs/heads/*:refs/remotes/origin/*). Correspondingly, a full remote-tracking branch name, can only be shortened to the "$remote/$branch" form if it textually matches the "refs/remotes/$remote/$branch" format. If unconventional refspecs (e.g. refs/heads/*:refs/remotes/origin/heads/*) are being used, then the current expansion ("refs/remotes/$remote/$branch") fails to match the remote-tracking branches from the configured remotes. Ditto for the corresponding shortening. Instead of doing pure textual expansion (from "$remote/$branch" to "refs/remotes/$remote/$branch"), we should expand by finding all remotes matching "$remote", look up their fetch refspecs, and map "refs/heads/$branch" through them to find the appropriate remote-tracking branch name corresponding to "$remote/$branch". This would yield the correct remote-tracking branch in all cases, both for conventional and unconventional refspecs. Likewise, when shortening full refnames for remote-tracking branches into their shorthand form ("$remote/$branch"), we should find the refspec with the RHS that matches the full remote-tracking branch name, and map through that refspec to produce the corresponding LHS (minus the leading "refs/heads/" part), and from that construct the corresponding "$remote/$branch" shorthand. This patch adds a new expansion method - ref_expand_refspec() - and a corresponding shortening method - ref_shorten_refspec() - that implements the remote refspec traversal and expanding/shortening logic described above. These expand/shorten methods complement the existing ref_expand_txtly() and ref_shorten_txtly() methods that implement the current textual expanding/shortening logic. Implementing the proper expanding/shortening of "$remote/$branch" is now a simple matter of adding another entry to the ref_expand_rules list, using the new ref_expand_refspec() and ref_shorten_refspec() functions. Note that the existing "refs/remotes/*" textual expansion/shortening rule is kept to preserve backwards compatibility for refs under refs/remotes/* that are not covered by any configured refspec. Signed-off-by: Johan Herland <johan@xxxxxxxxxxx> --- refs.c | 98 +++++++++++++++++++++++++- t/t7900-working-with-namespaced-remote-refs.sh | 21 +++++- 2 files changed, 114 insertions(+), 5 deletions(-) diff --git a/refs.c b/refs.c index 98997c4..18d7188 100644 --- a/refs.c +++ b/refs.c @@ -4,6 +4,7 @@ #include "tag.h" #include "dir.h" #include "string-list.h" +#include "remote.h" /* * Make sure "ref" is something reasonable to have under ".git/refs/"; @@ -1758,12 +1759,102 @@ static char *ref_shorten_txtly(const struct ref_expand_rule *rule, return xstrndup(refname + pre_len, match_len); } +struct ref_expand_refspec_helper_data { + const struct ref_expand_rule *rule; + char *dst; + size_t dst_len; + const char *src; + size_t src_len; +}; + +static int ref_expand_refspec_helper(struct remote *remote, void *cb_data) +{ + struct ref_expand_refspec_helper_data *cb = cb_data; + struct refspec query; + char refspec_src[PATH_MAX]; + size_t ref_start = strlen(remote->name) + 1; + if (prefixcmp(cb->src, remote->name) || + cb->src_len <= ref_start || + cb->src[ref_start - 1] != '/') + return 0; + + mksnpath(refspec_src, sizeof(refspec_src), cb->rule->pattern, + cb->src_len - ref_start, cb->src + ref_start); + + memset(&query, 0, sizeof(struct refspec)); + query.src = refspec_src; + if ((!remote_find_tracking(remote, &query)) && + strlen(query.dst) < cb->dst_len) { + strcpy(cb->dst, query.dst); + return 1; + } + return 0; +} + +static void ref_expand_refspec(const struct ref_expand_rule *rule, + char *dst, size_t dst_len, + const char *shortname, size_t shortname_len) +{ + /* + * Given shortname of the form "$remote/$ref", see if there is a + * fetch refspec configured for $remote whose lhs matches + * rule->pattern % $ref, and use the corresponding rhs of that + * mapping as the expanded result of "$remote/$ref". + */ + const void *has_slash = memchr(shortname, '/', shortname_len); + struct ref_expand_refspec_helper_data cb = { + rule, dst, dst_len, shortname, shortname_len }; + dst[0] = '\0'; + if (has_slash) + for_each_remote(ref_expand_refspec_helper, &cb); +} + +static int ref_shorten_refspec_helper(struct remote *remote, void *cb_data) +{ + struct ref_expand_refspec_helper_data *cb = cb_data; + struct refspec query; + char *lhs_ref; + int ret = 0; + + memset(&query, 0, sizeof(struct refspec)); + query.dst = (char *) cb->src; + if (!remote_find_tracking(remote, &query) && + (lhs_ref = ref_shorten_txtly(cb->rule, query.src))) { + /* refname matches rhs and rule->pattern matches lhs */ + cb->dst_len = strlen(remote->name) + 1 + strlen(lhs_ref); + /* "$remote/$lhs_ref" should be shorter than src */ + if (cb->dst_len < cb->src_len) { + cb->dst = xmalloc(cb->dst_len + 1); + snprintf(cb->dst, cb->dst_len + 1, + "%s/%s", remote->name, lhs_ref); + ret = 1; + } + free(lhs_ref); + } + return ret; +} + +static char *ref_shorten_refspec(const struct ref_expand_rule *rule, + const char *refname) +{ + /* + * See if there is a $remote with a fetch refspec that matches the + * given refname on rhs, and will produce refs/heads/$ref on the + * lhs. If so, construct "$remote/$ref" as the shorthand. + */ + struct ref_expand_refspec_helper_data cb = { + rule, NULL, 0, refname, strlen(refname) }; + for_each_remote(ref_shorten_refspec_helper, &cb); + return cb.dst; +} + const struct ref_expand_rule ref_expand_rules_local[] = { { ref_expand_txtly, NULL, "%.*s" }, { ref_expand_txtly, ref_shorten_txtly, "refs/%.*s" }, { ref_expand_txtly, ref_shorten_txtly, "refs/tags/%.*s" }, { ref_expand_txtly, ref_shorten_txtly, "refs/heads/%.*s" }, { ref_expand_txtly, ref_shorten_txtly, "refs/remotes/%.*s" }, + { ref_expand_refspec, ref_shorten_refspec, "refs/heads/%.*s" }, { ref_expand_txtly, ref_shorten_txtly, "refs/remotes/%.*s/HEAD" }, { NULL, NULL, NULL } }; @@ -3028,13 +3119,14 @@ char *shorten_unambiguous_ref(const char *refname, int strict) /* * the short name is ambiguous, if it resolves - * (with this previous rule) to a valid ref - * read_ref() returns 0 on success + * (with this previous rule) to a valid + * (but different) ref */ if (q->expand) { q->expand(q, resolved, sizeof(resolved), short_name, short_name_len); - if (ref_exists(resolved)) + if (strcmp(refname, resolved) && + ref_exists(resolved)) break; } } diff --git a/t/t7900-working-with-namespaced-remote-refs.sh b/t/t7900-working-with-namespaced-remote-refs.sh index cc25e76..302083e 100755 --- a/t/t7900-working-with-namespaced-remote-refs.sh +++ b/t/t7900-working-with-namespaced-remote-refs.sh @@ -89,7 +89,7 @@ test_expect_success 'enter client repo' ' cd client ' -test_expect_failure 'short-hand notation expands correctly for remote-tracking branches' ' +test_expect_success 'short-hand notation expands correctly for remote-tracking branches' ' echo refs/remotes/origin/heads/master > expect && git rev-parse --symbolic-full-name refs/remotes/origin/heads/master > actual && test_cmp expect actual && @@ -101,7 +101,7 @@ test_expect_failure 'short-hand notation expands correctly for remote-tracking b test_cmp expect actual ' -test_expect_failure 'remote-tracking branches are shortened correctly' ' +test_expect_success 'remote-tracking branches are shortened correctly' ' echo origin/master > expect && git rev-parse --abbrev-ref refs/remotes/origin/heads/master > actual && test_cmp expect actual && @@ -113,4 +113,21 @@ test_expect_failure 'remote-tracking branches are shortened correctly' ' test_cmp expect actual ' +cat > expect.origin_master << EOF +$server_master_b +$server_master_a +EOF + +cat > expect.origin_other << EOF +$server_other_b +$server_master_a +EOF + +test_expect_success 'rev-list machinery should work with $remote/$branch' ' + git rev-list origin/master > actual.origin_master && + test_cmp expect.origin_master actual.origin_master && + git rev-list origin/other > actual.origin_other && + test_cmp expect.origin_other actual.origin_other +' + test_done -- 1.8.1.3.704.g33f7d4f -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html