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 <jo...@herland.net>
---
 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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to