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
/* "--