mirror of
https://github.com/git-for-windows/git.git
synced 2026-06-13 08:57:56 -05:00
Merge branch 'td/ref-filter-memoize-contains' into seen
'git branch --contains' and 'git for-each-ref --contains' have been optimized to use the memoized commit traversal previously used only by 'git tag --contains', significantly speeding up connectivity checks across many candidate refs with shared history. * td/ref-filter-memoize-contains: commit-reach: die on contains walk errors ref-filter: memoize --contains with generations commit-reach: reject cycles in contains walk
This commit is contained in:
@@ -757,7 +757,8 @@ static int in_commit_list(const struct commit_list *want, struct commit *c)
|
||||
|
||||
/*
|
||||
* Test whether the candidate is contained in the list.
|
||||
* Do not recurse to find out, though, but return -1 if inconclusive.
|
||||
* Do not recurse to find out, though, but return CONTAINS_UNKNOWN if
|
||||
* inconclusive.
|
||||
*/
|
||||
static enum contains_result contains_test(struct commit *candidate,
|
||||
const struct commit_list *want,
|
||||
@@ -814,6 +815,7 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
|
||||
if (result != CONTAINS_UNKNOWN)
|
||||
return result;
|
||||
|
||||
*contains_cache_at(cache, candidate) = CONTAINS_IN_PROGRESS;
|
||||
push_to_contains_stack(candidate, &contains_stack);
|
||||
while (contains_stack.nr) {
|
||||
struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1];
|
||||
@@ -825,8 +827,8 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
|
||||
contains_stack.nr--;
|
||||
}
|
||||
/*
|
||||
* If we just popped the stack, parents->item has been marked,
|
||||
* therefore contains_test will return a meaningful yes/no.
|
||||
* A parent may have just been popped and marked, or may still
|
||||
* be active when replacement refs create a cycle.
|
||||
*/
|
||||
else switch (contains_test(parents->item, want, cache, cutoff)) {
|
||||
case CONTAINS_YES:
|
||||
@@ -836,7 +838,11 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
|
||||
case CONTAINS_NO:
|
||||
entry->parents = parents->next;
|
||||
break;
|
||||
case CONTAINS_IN_PROGRESS:
|
||||
die(_("commit ancestry contains a cycle"));
|
||||
case CONTAINS_UNKNOWN:
|
||||
*contains_cache_at(cache, parents->item) =
|
||||
CONTAINS_IN_PROGRESS;
|
||||
push_to_contains_stack(parents->item, &contains_stack);
|
||||
break;
|
||||
}
|
||||
@@ -848,9 +854,16 @@ static enum contains_result contains_tag_algo(struct commit *candidate,
|
||||
int commit_contains(struct ref_filter *filter, struct commit *commit,
|
||||
struct commit_list *list, struct contains_cache *cache)
|
||||
{
|
||||
if (filter->with_commit_tag_algo)
|
||||
int result;
|
||||
|
||||
if (filter->with_commit_tag_algo ||
|
||||
generation_numbers_enabled(the_repository))
|
||||
return contains_tag_algo(commit, list, cache) == CONTAINS_YES;
|
||||
return repo_is_descendant_of(the_repository, commit, list);
|
||||
|
||||
result = repo_is_descendant_of(the_repository, commit, list);
|
||||
if (result < 0)
|
||||
die(_("failed to check reachability"));
|
||||
return result;
|
||||
}
|
||||
|
||||
int can_all_from_reach_with_flag(struct object_array *from,
|
||||
|
||||
@@ -73,7 +73,8 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid);
|
||||
enum contains_result {
|
||||
CONTAINS_UNKNOWN = 0,
|
||||
CONTAINS_NO,
|
||||
CONTAINS_YES
|
||||
CONTAINS_YES,
|
||||
CONTAINS_IN_PROGRESS
|
||||
};
|
||||
|
||||
define_commit_slab(contains_cache, enum contains_result);
|
||||
|
||||
@@ -32,7 +32,16 @@ test_expect_success 'setup' '
|
||||
echo "X:$line" >>test-tool-tags || return 1
|
||||
done &&
|
||||
|
||||
commit=$(git commit-tree $(git rev-parse HEAD^{tree})) &&
|
||||
git rev-list --first-parent --max-count=8192 HEAD >contains-commits &&
|
||||
test_file_not_empty contains-commits &&
|
||||
git update-ref refs/contains-perf-base "$(tail -n 1 contains-commits)" &&
|
||||
awk "{
|
||||
printf \"update refs/contains-perf/%04d %s\\n\", NR, \$1
|
||||
}" contains-commits |
|
||||
git update-ref --stdin &&
|
||||
git pack-refs --include "refs/contains-perf/*" &&
|
||||
|
||||
commit=$(git commit-tree HEAD^{tree}) &&
|
||||
git update-ref refs/heads/disjoint-base $commit &&
|
||||
|
||||
git commit-graph write --reachable
|
||||
@@ -62,6 +71,23 @@ test_perf 'contains: git tag --merged' '
|
||||
xargs git tag --merged=HEAD <tags
|
||||
'
|
||||
|
||||
test_perf 'contains: git for-each-ref' '
|
||||
git for-each-ref --contains=refs/contains-perf-base --stdin <refs
|
||||
'
|
||||
|
||||
test_perf 'contains: git branch' '
|
||||
xargs git branch --contains=refs/contains-perf-base <branches
|
||||
'
|
||||
|
||||
test_perf 'contains: git tag' '
|
||||
xargs git tag --contains=refs/contains-perf-base <tags
|
||||
'
|
||||
|
||||
test_perf 'contains: synthetic shared history' '
|
||||
git for-each-ref --contains=refs/contains-perf-base \
|
||||
refs/contains-perf/ >/dev/null
|
||||
'
|
||||
|
||||
test_perf 'is-base check: test-tool reach (refs)' '
|
||||
test-tool reach get_branch_base_for_tip <test-tool-refs
|
||||
'
|
||||
|
||||
@@ -52,6 +52,28 @@ test_expect_success 'Missing objects are reported correctly' '
|
||||
test_must_be_empty brief-err
|
||||
'
|
||||
|
||||
test_expect_success 'missing ancestors are reported by contains filters' '
|
||||
test_when_finished "git update-ref -d refs/heads/missing-parent" &&
|
||||
{
|
||||
echo "tree $(git rev-parse HEAD^{tree})" &&
|
||||
echo "parent $MISSING" &&
|
||||
git cat-file commit HEAD |
|
||||
sed -n -e "/^author /p" -e "/^committer /p" &&
|
||||
echo &&
|
||||
echo "missing parent"
|
||||
} >commit &&
|
||||
broken=$(git hash-object -t commit -w commit) &&
|
||||
git update-ref refs/heads/missing-parent "$broken" &&
|
||||
for option in --contains --no-contains
|
||||
do
|
||||
test_must_fail git for-each-ref "$option=HEAD" \
|
||||
refs/heads/missing-parent >out 2>err &&
|
||||
test_must_be_empty out &&
|
||||
test_grep "parse commit $MISSING" err ||
|
||||
return 1
|
||||
done
|
||||
'
|
||||
|
||||
test_expect_success 'ahead-behind requires an argument' '
|
||||
test_must_fail git for-each-ref \
|
||||
--format="%(ahead-behind)" 2>err &&
|
||||
|
||||
@@ -1611,6 +1611,24 @@ test_expect_success 'checking that first commit is in all tags (hash)' '
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'tag --contains rejects cyclic replacement histories' '
|
||||
first=$(git rev-parse HEAD~2) &&
|
||||
second=$(git rev-parse HEAD~) &&
|
||||
third=$(git rev-parse HEAD) &&
|
||||
test_when_finished "
|
||||
git replace -d $first &&
|
||||
git replace -d $third &&
|
||||
git tag -d cycle-a cycle-b
|
||||
" &&
|
||||
git tag cycle-a "$first" &&
|
||||
git tag cycle-b "$third" &&
|
||||
git replace --graft "$first" "$third" "$second" &&
|
||||
git replace --graft "$third" "$first" &&
|
||||
test_must_fail git tag --contains="$second" --list "cycle-*" \
|
||||
>/dev/null 2>err &&
|
||||
test_grep "fatal: commit ancestry contains a cycle" err
|
||||
'
|
||||
|
||||
# other ways of specifying the commit
|
||||
test_expect_success 'checking that first commit is in all tags (tag)' '
|
||||
cat >expected <<-\EOF &&
|
||||
|
||||
Reference in New Issue
Block a user