ref-filter: limit traversal to prefix

When we are matching refnames against a pattern, then we know that the beginning of any refname that can match the pattern has to match the part of the pattern up to the first glob character. For example, if the pattern is `refs/heads/foo*bar`, then it can only match a reference that has the prefix `refs/heads/foo`. So pass that prefix to `for_each_fullref_in()`. This lets the ref code avoid passing us the full set of refs, and in some cases avoid reading them in the first place. Note that this applies only when the `match_as_path` flag is set (i.e., when `for-each-ref` is the caller), as the matching rules for git-branch and git-tag are subtly different. This could be generalized to the case of multiple patterns, but (a) it probably doesn't come up that often, and (b) it is more awkward to deal with multiple patterns (e.g., the patterns might not be disjoint). So, since this is just an optimization, punt on the case of multiple patterns. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed May 22, 2017 at 16:17 UTC cfe004a5a9e5116d003332a64142b0f73f8f9cdf
1 file changed +63 -1
ref-filter.c
+63 -1
@@ -1665,6 +1665,68 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname)
1665 return match_pattern(filter, refname);
1666 }
1667
1668 +/*
1669 + * Find the longest prefix of pattern we can pass to
1670 + * `for_each_fullref_in()`, namely the part of pattern preceding the
1671 + * first glob character. (Note that `for_each_fullref_in()` is
1672 + * perfectly happy working with a prefix that doesn't end at a
1673 + * pathname component boundary.)
1674 + */
1675 +static void find_longest_prefix(struct strbuf *out, const char *pattern)
1676 +{
1677 + const char *p;
1678 +
1679 + for (p = pattern; *p && !is_glob_special(*p); p++)
1680 + ;
1681 +
1682 + strbuf_add(out, pattern, p - pattern);
1683 +}
1684 +
1685 +/*
1686 + * This is the same as for_each_fullref_in(), but it tries to iterate
1687 + * only over the patterns we'll care about. Note that it _doesn't_ do a full
1688 + * pattern match, so the callback still has to match each ref individually.
1689 + */
1690 +static int for_each_fullref_in_pattern(struct ref_filter *filter,
1691 + each_ref_fn cb,
1692 + void *cb_data,
1693 + int broken)
1694 +{
1695 + struct strbuf prefix = STRBUF_INIT;
1696 + int ret;
1697 +
1698 + if (!filter->match_as_path) {
1699 + /*
1700 + * in this case, the patterns are applied after
1701 + * prefixes like "refs/heads/" etc. are stripped off,
1702 + * so we have to look at everything:
1703 + */
1704 + return for_each_fullref_in("", cb, cb_data, broken);
1705 + }
1706 +
1707 + if (!filter->name_patterns[0]) {
1708 + /* no patterns; we have to look at everything */
1709 + return for_each_fullref_in("", cb, cb_data, broken);
1710 + }
1711 +
1712 + if (filter->name_patterns[1]) {
1713 + /*
1714 + * multiple patterns; in theory this could still work as long
1715 + * as the patterns are disjoint. We'd just make multiple calls
1716 + * to for_each_ref(). But if they're not disjoint, we'd end up
1717 + * reporting the same ref multiple times. So let's punt on that
1718 + * for now.
1719 + */
1720 + return for_each_fullref_in("", cb, cb_data, broken);
1721 + }
1722 +
1723 + find_longest_prefix(&prefix, filter->name_patterns[0]);
1724 +
1725 + ret = for_each_fullref_in(prefix.buf, cb, cb_data, broken);
1726 + strbuf_release(&prefix);
1727 + return ret;
1728 +}
1729 +
1730 /*
1731 * Given a ref (sha1, refname), check if the ref belongs to the array
1732 * of sha1s. If the given ref is a tag, check if the given tag points
@@ -1911,7 +1973,7 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
1973 else if (filter->kind == FILTER_REFS_TAGS)
1974 ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata, broken);
1975 else if (filter->kind & FILTER_REFS_ALL)
1914 - ret = for_each_fullref_in("", ref_filter_handler, &ref_cbdata, broken);
1976 + ret = for_each_fullref_in_pattern(filter, ref_filter_handler, &ref_cbdata, broken);
1977 if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD))
1978 head_ref(ref_filter_handler, &ref_cbdata);
1979 }