This patch adds the following patterns for expanding/shortening refs:

  "refs/peers/%*"
  "refs/peers/%1/tags/%*"
  "refs/peers/%1/heads/%*"

These allow shorthand names like "origin/master" to expand to refs in
the refs/peers/* hierarchy (in this case, the likely expansion would be
by the middle rule above, resulting in "refs/peers/origin/heads/master").

To accomplish this, we have added the new "%1" wildcard which shall
expand into the first component (i.e. up to the first '/') of the given
shorthand. The other wildcard ("%*") shall then expand into the remainder
of the shorthand (i.e. following the first '/').

Correspondingly, when shortening according to a pattern with "%1", a
single component (not including any '/' character) shall be extracted
from that point in the given refname, and shall be added to the resulting
shorthand, with a trailing '/'. Then, when hitting the "%*", the
remainder of the given refname (modulo a trailing match in the pattern)
shall be extracted and appended to the portion previously extracted by
the "%1" wildcard.

The need to split the "$remote/$ref" into its $remote and $ref parts is
the reason why multi-level remote names will no longer work (and hence
were disallowed in the previous patch). A testcase demonstrating how
multi-level remote names fail is therefore included in this patch.

Signed-off-by: Johan Herland <jo...@herland.net>
---
 refs.c                                         | 101 +++++++++++++++++++++----
 t/t7900-working-with-namespaced-remote-refs.sh |   4 +-
 t/t7901-multi-level-remote-name-failure.sh     |  20 +++++
 3 files changed, 107 insertions(+), 18 deletions(-)
 create mode 100755 t/t7901-multi-level-remote-name-failure.sh

diff --git a/refs.c b/refs.c
index ab5e120..188a9eb 100644
--- a/refs.c
+++ b/refs.c
@@ -1789,7 +1789,10 @@ static const char *refname_patterns[] = {
        "refs/tags/%*",
        "refs/heads/%*",
        "refs/remotes/%*",
-       "refs/remotes/%*/HEAD"
+       "refs/remotes/%*/HEAD",
+       "refs/peers/%*",
+       "refs/peers/%1/tags/%*",
+       "refs/peers/%1/heads/%*"
 };
 
 struct wildcard_data {
@@ -1806,6 +1809,15 @@ static size_t refname_expand_helper(struct strbuf *sb, 
const char *placeholder,
                strbuf_add(sb, cb->s, cb->len);
                cb->done = 1;
                return 1;
+       } else if (*placeholder == '1' && !cb->done) {
+               const char *p = memchr(cb->s, '/', cb->len);
+               size_t copy_len = p ? p - cb->s : cb->len;
+               strbuf_add(sb, cb->s, copy_len);
+               if (copy_len < cb->len && cb->s[copy_len] == '/')
+                       copy_len++;
+               cb->s += copy_len;
+               cb->len -= copy_len;
+               return 1;
        }
        return 0;
 }
@@ -1821,6 +1833,46 @@ static int refname_expand(struct strbuf *dst, const char 
*pattern,
        return !cbdata.done;
 }
 
+static int handle_fragment(struct strbuf *dst, struct strbuf *fragment,
+                          int first, int last,
+                          const char *refname, size_t refname_len)
+{
+       const char *ref = refname, *p;
+       size_t ref_len = refname_len, trail_len;
+       if (!first) {
+               /* extract wildcard according to wildcard char */
+               switch (fragment->buf[0]) {
+               case '1':
+                       /* extract up to next '/' from refname */
+                       p = memchr(ref, '/', ref_len);
+                       if (!p)
+                               return -1;
+                       strbuf_add(dst, ref, p - ref);
+                       strbuf_addch(dst, '/');
+                       ref_len -= p - ref;
+                       ref += p - ref;
+                       break;
+               case '*':
+                       /* extract all up to matching trailer */
+                       assert(last);
+                       trail_len = fragment->len - 1;
+                       if (trail_len > ref_len)
+                               return -1;
+                       strbuf_add(dst, ref, ref_len - trail_len);
+                       ref += ref_len - trail_len;
+                       ref_len -= ref_len - trail_len;
+                       break;
+               }
+       }
+
+       /* match rest of fragment verbatim */
+       p = fragment->buf + (first ? 0 : 1); /* skip wildcard character */
+       trail_len = fragment->len - ((first ? 0 : 1) + (last ? 0 : 1));
+       if (trail_len > ref_len || memcmp(ref, p, trail_len))
+               return -1;
+       return (ref - refname) + trail_len;
+}
+
 static int refname_shorten(struct strbuf *dst, const char *pattern,
                           const char *refname, size_t refname_len)
 {
@@ -1830,26 +1882,43 @@ static int refname_shorten(struct strbuf *dst, const 
char *pattern,
         * Return 0 on success (positive match, wildcard-matching portion
         * copied into dst), and non-zero on failure (no match, dst empty).
         */
-       const char *match_end;
-       struct strbuf **fragments = strbuf_split_str(pattern, '%', 2);
-       struct strbuf *prefix = fragments[0], *suffix = fragments[1];
-       assert(!fragments[2]);
-       assert(prefix->len && prefix->buf[prefix->len - 1] == '%');
-       assert(suffix->len && suffix->buf[0] == '*');
+       struct strbuf **fragments = strbuf_split_str(pattern, '%', 0);
+       struct strbuf **it;
+       int first = 1, last, ret = 1;
 
        strbuf_reset(dst);
-       match_end = refname + refname_len - (suffix->len - 1);
-       if (refname_len <= (prefix->len - 1) + (suffix->len - 1) ||
-           memcmp(prefix->buf, refname, prefix->len - 1) ||
-           memcmp(suffix->buf + 1, match_end, suffix->len - 1)) {
-               strbuf_list_free(fragments);
-               return 1; /* refname does not match pattern */
+       for (it = fragments; *it; it++) {
+               int consumed;
+               struct strbuf *cur = *it;
+               last = *(it + 1) == NULL;
+
+               /* all but last ends with '%' */
+               assert(last || cur->buf[cur->len - 1] == '%');
+               /* all but first starts with '*' or '1' */
+               assert(first || cur->buf[0] == '*' || cur->buf[0] == '1');
+               /* only last starts with '*' */
+               assert((cur->buf[0] == '*' && last) ||
+                      (cur->buf[0] != '*' && !last));
+
+               consumed = handle_fragment(dst, cur, first, last,
+                                          refname, refname_len);
+               if (consumed < 0)
+                       goto cleanup;
+               else {
+                       refname += consumed;
+                       refname_len -= consumed;
+               }
+
+               first = 0;
        }
+       if (refname_len == 0)
+               ret = 0;
 
-       refname += prefix->len - 1;
-       strbuf_add(dst, refname, match_end - refname);
+cleanup:
+       if (ret)
+               strbuf_reset(dst);
        strbuf_list_free(fragments);
-       return 0;
+       return ret;
 }
 
 int refname_match(const char *abbrev_name, const char *full_name)
diff --git a/t/t7900-working-with-namespaced-remote-refs.sh 
b/t/t7900-working-with-namespaced-remote-refs.sh
index 109e9b8..33266e0 100755
--- a/t/t7900-working-with-namespaced-remote-refs.sh
+++ b/t/t7900-working-with-namespaced-remote-refs.sh
@@ -83,7 +83,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/peers/origin/heads/master >expect &&
        git rev-parse --symbolic-full-name refs/peers/origin/heads/master 
>actual &&
        test_cmp expect actual &&
@@ -95,7 +95,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/peers/origin/heads/master >actual &&
        test_cmp expect actual &&
diff --git a/t/t7901-multi-level-remote-name-failure.sh 
b/t/t7901-multi-level-remote-name-failure.sh
new file mode 100755
index 0000000..8d2a617
--- /dev/null
+++ b/t/t7901-multi-level-remote-name-failure.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+test_description='Show why multi-level remote names can no longer be used'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit a &&
+       git config remote.multi/level.url . &&
+       git config remote.multi/level.fetch 
"+refs/heads/*:refs/peers/multi/level/heads/*" &&
+       git fetch multi/level
+'
+
+test_expect_failure 'Fail to use shorthand notation: "$remote/$branch"' '
+       git rev-parse --verify a >expect &&
+       git rev-parse --verify multi/level/master >actual &&
+       test_cmp expect actual
+'
+
+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