Skip to content

Commit f603196

Browse files
committed
connected: add "auto" connectivity check mode
The incremental connectivity check pays a per-commit cost. For every incoming commit it reads the new trees along the changed paths, and the trees at the same paths in its parents to compare against. A parent tree is often read again even though an earlier commit already read it: on git.git, about a third of the tree reads for 2000 incoming commits are such re-reads. The full check instead pays a mostly fixed cost for walking the boundary trees, so with many incoming commits the incremental check becomes the slower one (see the previous commit). Users who mostly receive small pushes but occasionally a large one should not have to choose between the two. Add an "auto" value for transfer.connectivityCheck that picks the mode based on the incoming history. When the revision walk is limited, which is the case whenever the receiving repository has references, the incoming commit list is already computed by limit_list(), so counting it is cheap. "auto" then uses the incremental check for up to 1000 incoming commits, and the full check above that. The cutoff is a fixed constant based on the p5413 fixture in the previous commit, where breakeven is between 500 and 3000 commits. It is conservative for repositories with large trees, where the fixed cost of the full check is higher. Measured on real repositories (rev-list against a single boundary commit, mean of at least 10 runs): repository files commits full incr. full/incr internal A 556K 1000 1.71s 0.61s 2.8x internal A 556K 4000 2.78s 2.83s 1.0x internal B 288K 1000 1.78s 0.88s 2.0x linux 95K 598 0.33s 0.12s 2.7x linux 95K 1728 0.56s 0.35s 1.6x git 4.9K 984 0.22s 0.27s 0.8x With small trees incremental can be slower within the cutoff, but both modes are fast enough there (0.22s vs 0.27s for git above) that the difference does not matter in practice. No single cutoff is optimal for every repository, so it can be set with "auto=<cutoff>"; plain "auto" uses 1000. A malformed cutoff is treated like any other unknown value: it falls back to "full" with a warning. Suggested-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Kristofer Karlsson <krka@spotify.com>
1 parent d76dae8 commit f603196

5 files changed

Lines changed: 64 additions & 6 deletions

File tree

‎Documentation/config/transfer.adoc‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ transfer.connectivityCheck::
1818
than `full` when there are many incoming commits.
1919
Falls back to `full` for clones, pushes into an empty
2020
repository and deepening fetches.
21+
`auto`;;
22+
`auto=<cutoff>`;;
23+
Choose between `full` and `incremental` with a heuristic.
24+
Currently `incremental` is used when there are at most
25+
_<cutoff>_ incoming commits (1000 by default), and `full`
26+
otherwise. The heuristic may change in future versions.
2127
--
2228
+
2329
Unknown values are treated as `full`, with a warning.

‎Documentation/rev-list-options.adoc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1103,7 +1103,7 @@ It is an error to use `--missing-only` without `--missing=print` or
11031103
traversal, rather than just silencing errors about missing
11041104
objects.
11051105

1106-
`--connectivity-check=(full|incremental)`::
1106+
`--connectivity-check=(full|incremental|auto[=<cutoff>])`::
11071107
(For internal use only.) Used by `check_connected()` to
11081108
select the connectivity check algorithm; see
11091109
`transfer.connectivityCheck` in linkgit:git-config[1].

‎builtin/rev-list.c‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,36 @@
2929
#include "quote.h"
3030
#include "strbuf.h"
3131
#include "tree-verify.h"
32+
#include "parse.h"
33+
34+
#define INCREMENTAL_MAX_COMMITS 1000
3235

3336
enum connectivity_check_mode {
3437
CONNECTIVITY_CHECK_FULL,
3538
CONNECTIVITY_CHECK_INCREMENTAL,
39+
CONNECTIVITY_CHECK_AUTO,
3640
};
3741

3842
struct connectivity_check {
3943
enum connectivity_check_mode mode;
44+
unsigned long max_commits;
4045
};
4146

4247
static void parse_connectivity_check(struct repository *repo,
4348
const char *arg,
4449
struct connectivity_check *check)
4550
{
51+
const char *cutoff;
52+
4653
if (!strcmp(arg, "full"))
4754
check->mode = CONNECTIVITY_CHECK_FULL;
4855
else if (!strcmp(arg, "incremental"))
4956
check->mode = CONNECTIVITY_CHECK_INCREMENTAL;
57+
else if (!strcmp(arg, "auto"))
58+
check->mode = CONNECTIVITY_CHECK_AUTO;
59+
else if (skip_prefix(arg, "auto=", &cutoff) &&
60+
git_parse_ulong(cutoff, &check->max_commits))
61+
check->mode = CONNECTIVITY_CHECK_AUTO;
5062
else
5163
die(_("unknown --connectivity-check mode '%s'"), arg);
5264

@@ -752,7 +764,9 @@ int cmd_rev_list(int argc,
752764
int bisect_find_all = 0;
753765
int use_bitmap_index = 0;
754766
int filter_provided_objects = 0;
755-
struct connectivity_check connectivity_check = { 0 };
767+
struct connectivity_check connectivity_check = {
768+
.max_commits = INCREMENTAL_MAX_COMMITS,
769+
};
756770
const char *show_progress = NULL;
757771
int ret = 0;
758772

@@ -1005,9 +1019,19 @@ int cmd_rev_list(int argc,
10051019
* The incremental check would still work, but it would verify
10061020
* the whole object graph like the full check, with extra
10071021
* per-commit overhead on top, so use the full check.
1022+
*
1023+
* Otherwise the walk is limited, and revs.commits already holds
1024+
* all incoming commits, so counting them for "auto" is cheap.
10081025
*/
1009-
if (!revs.limited)
1026+
if (!revs.limited) {
10101027
connectivity_check.mode = CONNECTIVITY_CHECK_FULL;
1028+
} else if (connectivity_check.mode == CONNECTIVITY_CHECK_AUTO) {
1029+
if (commit_list_count(revs.commits) <=
1030+
connectivity_check.max_commits)
1031+
connectivity_check.mode = CONNECTIVITY_CHECK_INCREMENTAL;
1032+
else
1033+
connectivity_check.mode = CONNECTIVITY_CHECK_FULL;
1034+
}
10111035

10121036
if (connectivity_check.mode == CONNECTIVITY_CHECK_INCREMENTAL) {
10131037
struct commit *commit;

‎connected.c‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "gettext.h"
44
#include "hex.h"
55
#include "odb.h"
6+
#include "parse.h"
67
#include "run-command.h"
78
#include "sigchain.h"
89
#include "connected.h"
@@ -74,7 +75,8 @@ static void connectivity_check_mode(struct repository *repo,
7475
struct check_connected_options *opt,
7576
struct strbuf *mode)
7677
{
77-
const char *algorithm = NULL;
78+
const char *algorithm = NULL, *cutoff;
79+
unsigned long max_commits;
7880

7981
/*
8082
* Deepening fetches do not have the usual boundary of
@@ -93,6 +95,11 @@ static void connectivity_check_mode(struct repository *repo,
9395
strbuf_addstr(mode, "full");
9496
else if (!strcasecmp(algorithm, "incremental"))
9597
strbuf_addstr(mode, "incremental");
98+
else if (!strcasecmp(algorithm, "auto"))
99+
strbuf_addstr(mode, "auto");
100+
else if (skip_iprefix(algorithm, "auto=", &cutoff) &&
101+
git_parse_ulong(cutoff, &max_commits))
102+
strbuf_addf(mode, "auto=%lu", max_commits);
96103
else {
97104
warning(_("unknown transfer.connectivityCheck algorithm '%s', "
98105
"using 'full'"), algorithm);

‎t/t5413-connectivity-check.sh‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,7 @@ test_expect_success 'unknown transfer.connectivityCheck falls back to full' '
725725
test_when_finished "rm -rf dst" &&
726726
git init --bare dst &&
727727
git -C dst fetch ../src base:refs/heads/main &&
728-
for value in bogus
728+
for value in bogus auto= auto=x
729729
do
730730
set_connectivity_check dst "$value" &&
731731
run_traced git -C dst fetch ../src main &&
@@ -753,7 +753,7 @@ test_expect_success 'fetch uses incremental when configured' '
753753
expect_mode incremental
754754
'
755755

756-
for mode in incremental
756+
for mode in incremental auto
757757
do
758758
test_expect_success "push with $mode into an empty repository uses full" '
759759
test_when_finished "rm -rf dst" &&
@@ -771,4 +771,25 @@ do
771771
'
772772
done
773773

774+
for value in auto auto=1
775+
do
776+
test_expect_success "push with $value uses incremental up to the cutoff" '
777+
test_when_finished "rm -rf dst" &&
778+
git init --bare dst &&
779+
git -C src push ../dst base:refs/heads/main &&
780+
set_connectivity_check dst $value &&
781+
run_traced git -C src push ../dst main &&
782+
expect_mode incremental
783+
'
784+
done
785+
786+
test_expect_success 'push with auto uses full above the cutoff' '
787+
test_when_finished "rm -rf dst" &&
788+
git init --bare dst &&
789+
git -C src push ../dst base:refs/heads/main &&
790+
set_connectivity_check dst auto=0 &&
791+
run_traced git -C src push ../dst main &&
792+
expect_mode full
793+
'
794+
774795
test_done

0 commit comments

Comments
 (0)