refs: stop using mkpath() in refname_match()
refname_match() formats every ref_rev_parse_rules entry through mkpath() to then strcmp() with its given full_name. It turns out this formatting function dominates the client-side cost of matching explicit refspecs against a remote that advertises lots of refs. Reuse match_parse_rule() instead to efficiently compare each rule against its given full_name. With this change we avoid mkpath()'s strbuf_vaddf(), and each match is now a direct prefix + suffix comparison, length check, and memcmp of the refname at hand. Timings from the test added in the previous commit: Test HEAD~1 HEAD ----------------------------------------------------------------------- 5516.3: empty:refspecs:1 0.15(0.09+0.11) 0.14(0.07+0.11) -6.7% 5516.5: empty:refspecs:10 0.38(0.32+0.11) 0.17(0.11+0.11) -55.3% 5516.7: empty:refspecs:100 2.50(2.43+0.11) 0.49(0.42+0.11) -80.4% 5516.9: mirror:refspecs:1 0.21(0.15+0.11) 0.16(0.09+0.11) -23.8% 5516.11: mirror:refspecs:10 0.80(0.73+0.11) 0.26(0.19+0.11) -67.5% 5516.13: mirror:refspecs:100 6.68(6.59+0.13) 1.18(1.11+0.11) -82.3% Notes on a change in behavior: - mkpath() runs cleanup_path(), which strips leading "./". So, "./refs/heads/foo" previously matched "refs/heads/foo" through the "%.*s" rule. As of this commit, this is no longer true. - It's considered a bug that "./"-prefixed strings previously could have been matched with refnames in this way: - cleanup_path() dates back toseen26c8a533af(Add "mkpath()" helper function, 2005-07-08), and is intended to be used for filepaths, not refnames. The leading-"./" strip itself comes fromf17a1b1bec(Fix up path-cleanup in git_path() properly, 2005-07-05). - refname_match() has used mkpath() in its matching loop since its inception with79803322c1(add refname_match(), 2007-11-11). -6cd4a8982d(avoid using mksnpath for refs, 2017-03-28) previously removed other cleanup_path() spots reachable from mksnpath() and notes they were "questionable when dealing with refnames, as we could silently canonicalize a syntactically bogus refname into a valid one." Tests in t5510 and t5516 that cover the aliasing behavior are toggled to test_expect_success. Signed-off-by: Jon Simons <jon@jonsimons.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
parent
a4c803513e
commit
ac7a88513c
88
refs.c
88
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;
|
||||
}
|
||||
|
|
@ -1615,44 +1659,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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1074,7 +1074,7 @@ test_expect_success 'LHS of refspec follows ref disambiguation rules' '
|
|||
)
|
||||
'
|
||||
|
||||
test_expect_failure 'fetch with "./"-prefixed branch.<name>.merge does not mark any ref for merge' '
|
||||
test_expect_success 'fetch with "./"-prefixed branch.<name>.merge does not mark any ref for merge' '
|
||||
mkdir dotslash-merge-default-refspec &&
|
||||
(
|
||||
cd dotslash-merge-default-refspec &&
|
||||
|
|
@ -1096,7 +1096,7 @@ test_expect_failure 'fetch with "./"-prefixed branch.<name>.merge does not mark
|
|||
)
|
||||
'
|
||||
|
||||
test_expect_failure 'fetch protocol v0 with "./"-prefixed branch.<name>.merge does not match any remote ref' '
|
||||
test_expect_success 'fetch protocol v0 with "./"-prefixed branch.<name>.merge does not match any remote ref' '
|
||||
mkdir dotslash-merge-fetch-protocol-v0 &&
|
||||
(
|
||||
cd dotslash-merge-fetch-protocol-v0 &&
|
||||
|
|
|
|||
|
|
@ -433,7 +433,7 @@ test_expect_success 'push with onelevel ref' '
|
|||
test_must_fail git push testrepo HEAD:refs/onelevel
|
||||
'
|
||||
|
||||
test_expect_failure 'push with "./"-prefixed src does not match any ref' '
|
||||
test_expect_success 'push with "./"-prefixed src does not match any ref' '
|
||||
mk_test testrepo heads/main &&
|
||||
test_must_fail git push testrepo ./refs/heads/main:refs/heads/frotz 2>err &&
|
||||
test_grep "src refspec ./refs/heads/main does not match any" err
|
||||
|
|
|
|||
Loading…
Reference in New Issue