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 <johan@xxxxxxxxxxx> --- 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 majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html