Skip to content

Commit a91aca4

Browse files
peffgitster
authored andcommitted
ref-filter: use separate cache for contains_tag_algo
The algorithm which powers "tag --contains" uses the TMP_MARK and UNINTERESTING bits, but never cleans up after itself. As a result, stale UNINTERESTING bits may impact later traversals (like "--merged"). We could fix this by clearing the bits after we're done with the --contains traversal. That would be enough to fix the existing problem, but it leaves future developers in a bad spot: they cannot add other traversals that operate simultaneously with --contains (e.g., if you wanted to add "--no-contains" and use both filters at the same time). Instead, we can use a commit slab to store our cached results, which will store the bits outside of the commit structs entirely. This adds an extra level of indirection, but in my tests (running "git tag --contains HEAD" on linux.git), there was no measurable slowdown. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent d344d1c commit a91aca4

1 file changed

Lines changed: 35 additions & 20 deletions

File tree

ref-filter.c

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "version.h"
1616
#include "trailer.h"
1717
#include "wt-status.h"
18+
#include "commit-slab.h"
1819

1920
static struct ref_msg {
2021
const char *gone;
@@ -1470,15 +1471,22 @@ static void get_ref_atom_value(struct ref_array_item *ref, int atom, struct atom
14701471
*v = &ref->value[atom];
14711472
}
14721473

1474+
/*
1475+
* Unknown has to be "0" here, because that's the default value for
1476+
* contains_cache slab entries that have not yet been assigned.
1477+
*/
14731478
enum contains_result {
1474-
CONTAINS_UNKNOWN = -1,
1475-
CONTAINS_NO = 0,
1476-
CONTAINS_YES = 1
1479+
CONTAINS_UNKNOWN = 0,
1480+
CONTAINS_NO,
1481+
CONTAINS_YES
14771482
};
14781483

1484+
define_commit_slab(contains_cache, enum contains_result);
1485+
14791486
struct ref_filter_cbdata {
14801487
struct ref_array *array;
14811488
struct ref_filter *filter;
1489+
struct contains_cache contains_cache;
14821490
};
14831491

14841492
/*
@@ -1509,20 +1517,22 @@ static int in_commit_list(const struct commit_list *want, struct commit *c)
15091517
* Do not recurse to find out, though, but return -1 if inconclusive.
15101518
*/
15111519
static enum contains_result contains_test(struct commit *candidate,
1512-
const struct commit_list *want)
1520+
const struct commit_list *want,
1521+
struct contains_cache *cache)
15131522
{
1514-
/* was it previously marked as containing a want commit? */
1515-
if (candidate->object.flags & TMP_MARK)
1516-
return CONTAINS_YES;
1517-
/* or marked as not possibly containing a want commit? */
1518-
if (candidate->object.flags & UNINTERESTING)
1519-
return CONTAINS_NO;
1523+
enum contains_result *cached = contains_cache_at(cache, candidate);
1524+
1525+
/* If we already have the answer cached, return that. */
1526+
if (*cached)
1527+
return *cached;
1528+
15201529
/* or are we it? */
15211530
if (in_commit_list(want, candidate)) {
1522-
candidate->object.flags |= TMP_MARK;
1531+
*cached = CONTAINS_YES;
15231532
return CONTAINS_YES;
15241533
}
15251534

1535+
/* Otherwise, we don't know; prepare to recurse */
15261536
parse_commit_or_die(candidate);
15271537
return CONTAINS_UNKNOWN;
15281538
}
@@ -1535,10 +1545,11 @@ static void push_to_contains_stack(struct commit *candidate, struct contains_sta
15351545
}
15361546

15371547
static enum contains_result contains_tag_algo(struct commit *candidate,
1538-
const struct commit_list *want)
1548+
const struct commit_list *want,
1549+
struct contains_cache *cache)
15391550
{
15401551
struct contains_stack contains_stack = { 0, 0, NULL };
1541-
enum contains_result result = contains_test(candidate, want);
1552+
enum contains_result result = contains_test(candidate, want, cache);
15421553

15431554
if (result != CONTAINS_UNKNOWN)
15441555
return result;
@@ -1550,16 +1561,16 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
15501561
struct commit_list *parents = entry->parents;
15511562

15521563
if (!parents) {
1553-
commit->object.flags |= UNINTERESTING;
1564+
*contains_cache_at(cache, commit) = CONTAINS_NO;
15541565
contains_stack.nr--;
15551566
}
15561567
/*
15571568
* If we just popped the stack, parents->item has been marked,
15581569
* therefore contains_test will return a meaningful yes/no.
15591570
*/
1560-
else switch (contains_test(parents->item, want)) {
1571+
else switch (contains_test(parents->item, want, cache)) {
15611572
case CONTAINS_YES:
1562-
commit->object.flags |= TMP_MARK;
1573+
*contains_cache_at(cache, commit) = CONTAINS_YES;
15631574
contains_stack.nr--;
15641575
break;
15651576
case CONTAINS_NO:
@@ -1571,13 +1582,14 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
15711582
}
15721583
}
15731584
free(contains_stack.contains_stack);
1574-
return contains_test(candidate, want);
1585+
return contains_test(candidate, want, cache);
15751586
}
15761587

1577-
static int commit_contains(struct ref_filter *filter, struct commit *commit)
1588+
static int commit_contains(struct ref_filter *filter, struct commit *commit,
1589+
struct contains_cache *cache)
15781590
{
15791591
if (filter->with_commit_tag_algo)
1580-
return contains_tag_algo(commit, filter->with_commit) == CONTAINS_YES;
1592+
return contains_tag_algo(commit, filter->with_commit, cache) == CONTAINS_YES;
15811593
return is_descendant_of(commit, filter->with_commit);
15821594
}
15831595

@@ -1774,7 +1786,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid,
17741786
return 0;
17751787
/* We perform the filtering for the '--contains' option */
17761788
if (filter->with_commit &&
1777-
!commit_contains(filter, commit))
1789+
!commit_contains(filter, commit, &ref_cbdata->contains_cache))
17781790
return 0;
17791791
}
17801792

@@ -1874,6 +1886,8 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
18741886
broken = 1;
18751887
filter->kind = type & FILTER_REFS_KIND_MASK;
18761888

1889+
init_contains_cache(&ref_cbdata.contains_cache);
1890+
18771891
/* Simple per-ref filtering */
18781892
if (!filter->kind)
18791893
die("filter_refs: invalid type");
@@ -1896,6 +1910,7 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int
18961910
head_ref(ref_filter_handler, &ref_cbdata);
18971911
}
18981912

1913+
clear_contains_cache(&ref_cbdata.contains_cache);
18991914

19001915
/* Filters that need revision walking */
19011916
if (filter->merge_commit)

0 commit comments

Comments
 (0)