Merge branch 'ds/rev-list-maximal-only-optim'

"git rev-list --maximal-only" has been optimized by borrowing the
logic used by "git show-branch --independent", which computes the
same kind of information much more efficiently.

* ds/rev-list-maximal-only-optim:
  rev-list: use reduce_heads() for --maximal-only
  p6011: add perf test for rev-list --maximal-only
  t6600: test --maximal-only and --independent
This commit is contained in:
Junio C Hamano committed 2026-04-09 11:21:59 -07:00
commit c343f9cdc2
4 files changed
+164

No files matched your search

+59
View File
@@ -25,6 +25,7 @@
#include "oidset.h"
#include "oidmap.h"
#include "packfile.h"
#include "commit-reach.h"
#include "quote.h"
#include "strbuf.h"
@@ -633,6 +634,61 @@ static int try_bitmap_disk_usage(struct rev_info *revs,
return 0;
}
/*
* If revs->maximal_only is set and no other walk modifiers are provided,
* run a faster computation to filter the independent commits and prepare
* them for output. Set revs->no_walk to prevent later walking.
*
* If this algorithm doesn't apply, then no changes are made to revs.
*/
static void prepare_maximal_independent(struct rev_info *revs)
{
struct commit_list *c;
if (!revs->maximal_only)
return;
for (c = revs->commits; c; c = c->next) {
if (c->item->object.flags & UNINTERESTING)
return;
}
if (revs->limited ||
revs->topo_order ||
revs->first_parent_only ||
revs->reverse ||
revs->max_count >= 0 ||
revs->skip_count >= 0 ||
revs->min_age != (timestamp_t)-1 ||
revs->max_age != (timestamp_t)-1 ||
revs->min_parents > 0 ||
revs->max_parents >= 0 ||
revs->prune_data.nr ||
revs->count ||
revs->left_right ||
revs->boundary ||
revs->tag_objects ||
revs->tree_objects ||
revs->blob_objects ||
revs->filter.choice ||
revs->reflog_info ||
revs->diff ||
revs->grep_filter.pattern_list ||
revs->grep_filter.header_list ||
revs->verbose_header ||
revs->print_parents ||
revs->edge_hint ||
revs->unpacked ||
revs->no_kept_objects ||
revs->line_level_traverse)
return;
reduce_heads_replace(&revs->commits);
/* Modify 'revs' to only output this commit list. */
revs->no_walk = 1;
}
int cmd_rev_list(int argc,
const char **argv,
const char *prefix,
@@ -875,6 +931,9 @@ int cmd_rev_list(int argc,
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
prepare_maximal_independent(&revs);
if (revs.tree_objects)
mark_edges_uninteresting(&revs, show_edge, 0);
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
test_description='Test --maximal-only and --independent options'
. ./perf-lib.sh
test_perf_default_repo
test_expect_success 'setup' '
git for-each-ref --format="%(*objecttype) %(objecttype) %(objectname)" \
"refs/heads/*" "refs/tags/*" |
sed -n -e "s/^commit commit //p" -e "s/^ commit //p" |
head -n 50 >commits &&
git commit-graph write --reachable
'
test_perf 'merge-base --independent' '
git merge-base --independent $(cat commits) >/dev/null
'
test_perf 'rev-list --maximal-only' '
git rev-list --maximal-only $(cat commits) >/dev/null
'
test_perf 'rev-list --maximal-only --since' '
git rev-list --maximal-only --since=2000-01-01 $(cat commits) >/dev/null
'
test_done
+31
View File
@@ -263,4 +263,35 @@ test_expect_success 'rev-list --boundary incompatible with --maximal-only' '
test_grep "cannot be used together" err
'
test_expect_success 'rev-list --maximal-only and --pretty' '
test_when_finished rm -rf repo &&
git init repo &&
test_commit -C repo 1 &&
oid1=$(git -C repo rev-parse HEAD) &&
test_commit -C repo 2 &&
oid2=$(git -C repo rev-parse HEAD) &&
git -C repo checkout --detach HEAD~1 &&
test_commit -C repo 3 &&
oid3=$(git -C repo rev-parse HEAD) &&
cat >expect <<-EOF &&
commit $oid3
$oid3
commit $oid2
$oid2
EOF
git -C repo rev-list --pretty="%H" --maximal-only $oid1 $oid2 $oid3 >out &&
test_cmp expect out &&
cat >expect <<-EOF &&
$oid3
$oid2
EOF
git -C repo log --pretty="%H" --maximal-only $oid1 $oid2 $oid3 >out &&
test_cmp expect out
'
test_done
+45
View File
@@ -837,4 +837,49 @@ test_expect_success 'rev-list --maximal-only (range)' '
--first-parent --exclude-first-parent-only
'
test_expect_success 'rev-list --maximal-only matches merge-base --independent' '
# Mix of independent and dependent
git merge-base --independent \
refs/heads/commit-5-2 \
refs/heads/commit-3-2 \
refs/heads/commit-2-5 >expect &&
sort expect >expect.sorted &&
git rev-list --maximal-only \
refs/heads/commit-5-2 \
refs/heads/commit-3-2 \
refs/heads/commit-2-5 >actual &&
sort actual >actual.sorted &&
test_cmp expect.sorted actual.sorted &&
# All independent commits.
git merge-base --independent \
refs/heads/commit-5-2 \
refs/heads/commit-4-3 \
refs/heads/commit-3-4 \
refs/heads/commit-2-5 >expect &&
sort expect >expect.sorted &&
git rev-list --maximal-only \
refs/heads/commit-5-2 \
refs/heads/commit-4-3 \
refs/heads/commit-3-4 \
refs/heads/commit-2-5 >actual &&
sort actual >actual.sorted &&
test_cmp expect.sorted actual.sorted &&
# Only one independent.
git merge-base --independent \
refs/heads/commit-1-1 \
refs/heads/commit-4-2 \
refs/heads/commit-4-4 \
refs/heads/commit-8-4 >expect &&
sort expect >expect.sorted &&
git rev-list --maximal-only \
refs/heads/commit-1-1 \
refs/heads/commit-4-2 \
refs/heads/commit-4-4 \
refs/heads/commit-8-4 >actual &&
sort actual >actual.sorted &&
test_cmp expect.sorted actual.sorted
'
test_done