ref-filter: use generation number for --contains

A commit A can reach a commit B only if the generation number of A is strictly larger than the generation number of B. This condition allows significantly short-circuiting commit-graph walks. Use generation number for '--contains' type queries. On a copy of the Linux repository where HEAD is contained in v4.13 but no earlier tag, the command 'git tag --contains HEAD' had the following peformance improvement: Before: 0.81s After: 0.04s Rel %: -95% Helped-by: Jeff King <peff@peff.net> Signed-off-by: Derrick Stolee <dstolee@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Derrick Stolee committed May 1, 2018 at 12:47 UTC 819807b33f820dc17d96f043747daf18c5e38516
1 file changed +20 -4
ref-filter.c
+20 -4
@@ -16,6 +16,7 @@
16 #include "trailer.h"
17 #include "wt-status.h"
18 #include "commit-slab.h"
19 +#include "commit-graph.h"
20
21 static struct ref_msg {
22 const char *gone;
@@ -1587,7 +1588,8 @@ static int in_commit_list(const struct commit_list *want, struct commit *c)
1588 */
1589 static enum contains_result contains_test(struct commit *candidate,
1590 const struct commit_list *want,
1590 - struct contains_cache *cache)
1591 + struct contains_cache *cache,
1592 + uint32_t cutoff)
1593 {
1594 enum contains_result *cached = contains_cache_at(cache, candidate);
1595
@@ -1603,6 +1605,10 @@ static enum contains_result contains_test(struct commit *candidate,
1605
1606 /* Otherwise, we don't know; prepare to recurse */
1607 parse_commit_or_die(candidate);
1608 +
1609 + if (candidate->generation < cutoff)
1610 + return CONTAINS_NO;
1611 +
1612 return CONTAINS_UNKNOWN;
1613 }
1614
@@ -1618,8 +1624,18 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
1624 struct contains_cache *cache)
1625 {
1626 struct contains_stack contains_stack = { 0, 0, NULL };
1621 - enum contains_result result = contains_test(candidate, want, cache);
1627 + enum contains_result result;
1628 + uint32_t cutoff = GENERATION_NUMBER_INFINITY;
1629 + const struct commit_list *p;
1630 +
1631 + for (p = want; p; p = p->next) {
1632 + struct commit *c = p->item;
1633 + load_commit_graph_info(c);
1634 + if (c->generation < cutoff)
1635 + cutoff = c->generation;
1636 + }
1637
1638 + result = contains_test(candidate, want, cache, cutoff);
1639 if (result != CONTAINS_UNKNOWN)
1640 return result;
1641
@@ -1637,7 +1653,7 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
1653 * If we just popped the stack, parents->item has been marked,
1654 * therefore contains_test will return a meaningful yes/no.
1655 */
1640 - else switch (contains_test(parents->item, want, cache)) {
1656 + else switch (contains_test(parents->item, want, cache, cutoff)) {
1657 case CONTAINS_YES:
1658 *contains_cache_at(cache, commit) = CONTAINS_YES;
1659 contains_stack.nr--;
@@ -1651,7 +1667,7 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
1667 }
1668 }
1669 free(contains_stack.contains_stack);
1654 - return contains_test(candidate, want, cache);
1670 + return contains_test(candidate, want, cache, cutoff);
1671 }
1672
1673 static int commit_contains(struct ref_filter *filter, struct commit *commit,