diff --git a/refs.c b/refs.c index 3bd7cd94f6..7b8819568e 100644 --- a/refs.c +++ b/refs.c @@ -652,6 +652,44 @@ static const char *ref_rev_parse_rules[] = { #define NUM_REV_PARSE_RULES (ARRAY_SIZE(ref_rev_parse_rules) - 1) +/* + * Check that the string refname matches a rule of the form + * "{prefix}%.*s{suffix}". So "foo/bar/baz" would match the rule + * "foo/%.*s/baz", and return the string "bar". + */ +static const char *match_parse_rule(const char *refname, const char *rule, + size_t *len) +{ + /* + * Check that rule matches refname up to the first percent in the rule. + * We can bail immediately if not, but otherwise we leave "rule" at the + * %-placeholder, and "refname" at the start of the potential matched + * name. + */ + while (*rule != '%') { + if (!*rule) + BUG("rev-parse rule did not have percent"); + if (*refname++ != *rule++) + return NULL; + } + + /* + * Check that our "%" is the expected placeholder. This assumes there + * are no other percents (placeholder or quoted) in the string, but + * that is sufficient for our rev-parse rules. + */ + if (!skip_prefix(rule, "%.*s", &rule)) + return NULL; + + /* + * And now check that our suffix (if any) matches. + */ + if (!strip_suffix(refname, rule, len)) + return NULL; + + return refname; /* len set by strip_suffix() */ +} + /* * Is it possible that the caller meant full_name with abbrev_name? * If so return a non-zero value to signal "yes"; the magnitude of @@ -662,12 +700,18 @@ static const char *ref_rev_parse_rules[] = { int refname_match(const char *abbrev_name, const char *full_name) { const char **p; - const int abbrev_name_len = strlen(abbrev_name); + const size_t abbrev_name_len = strlen(abbrev_name); const int num_rules = NUM_REV_PARSE_RULES; - for (p = ref_rev_parse_rules; *p; p++) - if (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name))) + for (p = ref_rev_parse_rules; *p; p++) { + size_t short_name_len; + const char *short_name = match_parse_rule(full_name, *p, + &short_name_len); + + if (short_name && short_name_len == abbrev_name_len && + !memcmp(short_name, abbrev_name, abbrev_name_len)) return &ref_rev_parse_rules[num_rules] - p; + } return 0; } @@ -1616,44 +1660,6 @@ int refs_update_ref(struct ref_store *refs, const char *msg, return 0; } -/* - * Check that the string refname matches a rule of the form - * "{prefix}%.*s{suffix}". So "foo/bar/baz" would match the rule - * "foo/%.*s/baz", and return the string "bar". - */ -static const char *match_parse_rule(const char *refname, const char *rule, - size_t *len) -{ - /* - * Check that rule matches refname up to the first percent in the rule. - * We can bail immediately if not, but otherwise we leave "rule" at the - * %-placeholder, and "refname" at the start of the potential matched - * name. - */ - while (*rule != '%') { - if (!*rule) - BUG("rev-parse rule did not have percent"); - if (*refname++ != *rule++) - return NULL; - } - - /* - * Check that our "%" is the expected placeholder. This assumes there - * are no other percents (placeholder or quoted) in the string, but - * that is sufficient for our rev-parse rules. - */ - if (!skip_prefix(rule, "%.*s", &rule)) - return NULL; - - /* - * And now check that our suffix (if any) matches. - */ - if (!strip_suffix(refname, rule, len)) - return NULL; - - return refname; /* len set by strip_suffix() */ -} - char *refs_shorten_unambiguous_ref(struct ref_store *refs, const char *refname, int strict) { diff --git a/remote.c b/remote.c index bfb631083a..f7f8b3578a 100644 --- a/remote.c +++ b/remote.c @@ -21,6 +21,7 @@ #include "dir.h" #include "setup.h" #include "string-list.h" +#include "strmap.h" #include "strvec.h" #include "commit-reach.h" #include "advice.h" @@ -1063,60 +1064,95 @@ void free_refs(struct ref *ref) } } +struct refspec_match { + struct ref *matched_weak; + struct ref *matched; + int weak_match; + int match; +}; + +static void add_refspec_match(struct refspec_match *m, const char *pattern, + struct ref *ref) +{ + size_t patlen = strlen(pattern); + size_t namelen = strlen(ref->name); + + /* A match is "weak" if it is with refs outside + * heads or tags, and did not specify the pattern + * in full (e.g. "refs/remotes/origin/master") or at + * least from the toplevel (e.g. "remotes/origin/master"); + * otherwise "git push $URL master" would result in + * ambiguity between remotes/origin/master and heads/master + * at the remote site. + */ + if (namelen != patlen && + patlen != namelen - 5 && + !starts_with(ref->name, "refs/heads/") && + !starts_with(ref->name, "refs/tags/")) { + /* We want to catch the case where only weak + * matches are found and there are multiple + * matches, and where more than one strong + * matches are found, as ambiguous. One + * strong match with zero or more weak matches + * are acceptable as a unique match. + */ + m->matched_weak = ref; + m->weak_match++; + } else { + m->matched = ref; + m->match++; + } +} + +static int finish_refspec_match(const struct refspec_match *m, + struct ref **matched_ref) +{ + if (!m->matched) { + if (matched_ref) + *matched_ref = m->matched_weak; + return m->weak_match; + } + if (matched_ref) + *matched_ref = m->matched; + return m->match; +} + int count_refspec_match(const char *pattern, struct ref *refs, struct ref **matched_ref) { - int patlen = strlen(pattern); - struct ref *matched_weak = NULL; - struct ref *matched = NULL; - int weak_match = 0; - int match = 0; + struct refspec_match m = { 0 }; - for (weak_match = match = 0; refs; refs = refs->next) { - char *name = refs->name; - int namelen = strlen(name); + for (; refs; refs = refs->next) { + if (refname_match(pattern, refs->name)) + add_refspec_match(&m, pattern, refs); + } + return finish_refspec_match(&m, matched_ref); +} - if (!refname_match(pattern, name)) - continue; +static void ref_map_init(struct strmap *map, struct ref *refs) +{ + strmap_init_with_options(map, NULL, 0); + for (; refs; refs = refs->next) + strmap_put(map, refs->name, refs); +} - /* A match is "weak" if it is with refs outside - * heads or tags, and did not specify the pattern - * in full (e.g. "refs/remotes/origin/master") or at - * least from the toplevel (e.g. "remotes/origin/master"); - * otherwise "git push $URL master" would result in - * ambiguity between remotes/origin/master and heads/master - * at the remote site. - */ - if (namelen != patlen && - patlen != namelen - 5 && - !starts_with(name, "refs/heads/") && - !starts_with(name, "refs/tags/")) { - /* We want to catch the case where only weak - * matches are found and there are multiple - * matches, and where more than one strong - * matches are found, as ambiguous. One - * strong match with zero or more weak matches - * are acceptable as a unique match. - */ - matched_weak = refs; - weak_match++; - } - else { - matched = refs; - match++; - } - } - if (!matched) { - if (matched_ref) - *matched_ref = matched_weak; - return weak_match; - } - else { - if (matched_ref) - *matched_ref = matched; - return match; +static int count_refspec_match_in_map(const char *pattern, + struct strmap *refs, + struct ref **matched_ref) +{ + struct refspec_match m = { 0 }; + struct strvec names = STRVEC_INIT; + size_t i; + + expand_ref_prefix(&names, pattern); + for (i = 0; i < names.nr; i++) { + struct ref *ref = strmap_get(refs, names.v[i]); + if (ref) + add_refspec_match(&m, pattern, ref); } + strvec_clear(&names); + return finish_refspec_match(&m, matched_ref); } void tail_link_ref(struct ref *ref, struct ref ***tail) @@ -1185,12 +1221,12 @@ static char *guess_ref(const char *name, struct ref *peer) return strbuf_detach(&buf, NULL); } -static int match_explicit_lhs(struct ref *src, +static int match_explicit_lhs(struct strmap *src, struct refspec_item *rs, struct ref **match, int *allocated_match) { - switch (count_refspec_match(rs->src, src, match)) { + switch (count_refspec_match_in_map(rs->src, src, match)) { case 1: if (allocated_match) *allocated_match = 0; @@ -1272,7 +1308,21 @@ static void show_push_unqualified_ref_name_error(const char *dst_value, } } -static int match_explicit(struct ref *src, struct ref *dst, +static bool refspec_item_is_explicit(const struct refspec_item *item) +{ + return !item->pattern && !item->matching && !item->negative; +} + +static bool any_refspec_item_is_explicit(const struct refspec *rs) +{ + for (int i = 0; i < rs->nr; i++) { + if (refspec_item_is_explicit(&rs->items[i])) + return true; + } + return false; +} + +static int match_explicit(struct strmap *src, struct strmap *dst, struct ref ***dst_tail, struct refspec_item *rs) { @@ -1282,7 +1332,7 @@ static int match_explicit(struct ref *src, struct ref *dst, const char *dst_value = rs->dst; char *dst_guess; - if (rs->pattern || rs->matching || rs->negative) { + if (!refspec_item_is_explicit(rs)) { ret = 0; goto out; } @@ -1306,7 +1356,7 @@ static int match_explicit(struct ref *src, struct ref *dst, matched_src->name); } - switch (count_refspec_match(dst_value, dst, &matched_dst)) { + switch (count_refspec_match_in_map(dst_value, dst, &matched_dst)) { case 1: break; case 0: @@ -1322,6 +1372,9 @@ static int match_explicit(struct ref *src, struct ref *dst, show_push_unqualified_ref_name_error(dst_value, matched_src->name); } + /* later refspecs must see the ref we just added to dst */ + if (matched_dst) + strmap_put(dst, matched_dst->name, matched_dst); break; default: matched_dst = NULL; @@ -1359,8 +1412,19 @@ static int match_explicit_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, struct refspec *rs) { int i, errs; + struct strmap src_map, dst_map; + + if (!any_refspec_item_is_explicit(rs)) + return 0; + + ref_map_init(&src_map, src); + ref_map_init(&dst_map, dst); for (i = errs = 0; i < rs->nr; i++) - errs += match_explicit(src, dst, dst_tail, &rs->items[i]); + errs += match_explicit(&src_map, &dst_map, dst_tail, + &rs->items[i]); + strmap_clear(&dst_map, 0); + strmap_clear(&src_map, 0); + return errs; } @@ -1571,17 +1635,21 @@ static void prepare_ref_index(struct string_list *ref_index, struct ref *ref) */ int check_push_refs(struct ref *src, struct refspec *rs) { + struct strmap src_map; int ret = 0; - int i; - for (i = 0; i < rs->nr; i++) { + if (!any_refspec_item_is_explicit(rs)) + return 0; + + ref_map_init(&src_map, src); + for (int i = 0; i < rs->nr; i++) { struct refspec_item *item = &rs->items[i]; - - if (item->pattern || item->matching || item->negative) + if (!refspec_item_is_explicit(item)) continue; - ret |= match_explicit_lhs(src, item, NULL, NULL); + ret |= match_explicit_lhs(&src_map, item, NULL, NULL); } + strmap_clear(&src_map, 0); return ret; } @@ -2864,6 +2932,8 @@ static int parse_push_cas_option(struct push_cas_option *cas, const char *arg, i /* "--