Skip to content

Commit 37fe8bd

Browse files
committed
repo-settings: add fetch.haveRefs config
In some large repos and strange commit history data shapes, it can be very expensive to not advertise certain references. For example, a parallel 'release' branch that occasionally merges the 'main' branch and augments the content with release metadata could create a long commit history that is not reachable from any other user development branches. If the client fails to advertise its copy of the 'release' branch, then it could end up redownloading that entire history in the range 'main..origin/release' instead of only 'release..origin/release'. Add a new multi-valued config key 'fetch.haveRefs' that forces certain references to be advertised during negotiation. This change focuses on safely parsing and storing those strings in the repo_settings struct. Some things that I needed to be careful about: the repo_config_get_string_multi() method fills the given list with a pointer to a list that is managed by the config subsystem. Holding a pointer to that list is not safe, so it must be copied before moving on to other config reads. Since we have a deep copy of these values, we need to free them as the settings are cleared. Signed-off-by: Derrick Stolee <stolee@gmail.com>
1 parent 3ad2ea4 commit 37fe8bd

3 files changed

Lines changed: 36 additions & 0 deletions

File tree

Documentation/config/fetch.adoc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,25 @@
7979
See also the `--negotiate-only` and `--negotiation-tip` options to
8080
linkgit:git-fetch[1].
8181

82+
`fetch.haveRefs`::
83+
When negotiating with a remote during `git fetch` and `git push`,
84+
the client advertises a list of commits that exist locally. In
85+
repos with many references, this list of "haves" can be truncated.
86+
Depending on data shape, dropping certain references may be
87+
expensive. This multi-valued config option specifies ref patterns
88+
whose tips should always be sent as "have" commits during fetch
89+
negotiation.
90+
+
91+
Each value is either an exact ref name (e.g. `refs/heads/release`) or a
92+
glob pattern (e.g. `refs/heads/release/*`). The pattern syntax is the same
93+
as for `--negotiation-tip`.
94+
+
95+
This option is additive with the normal negotiation process: the
96+
negotiation algorithm still runs and advertises its own selected commits,
97+
but the refs matching `fetch.haveRefs` are sent unconditionally on top of
98+
those heuristically selected commits. This option is also used during push
99+
negotiation when `push.negotiate` is enabled.
100+
82101
`fetch.showForcedUpdates`::
83102
Set to `false` to enable `--no-show-forced-updates` in
84103
linkgit:git-fetch[1] and linkgit:git-pull[1] commands.

repo-settings.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ void prepare_repo_settings(struct repository *r)
3535
int manyfiles;
3636
int read_changed_paths;
3737
unsigned long ulongval;
38+
const struct string_list *have_refs_config;
3839

3940
if (!r->gitdir)
4041
BUG("Cannot add settings for uninitialized repository");
@@ -131,6 +132,16 @@ void prepare_repo_settings(struct repository *r)
131132
die("unknown fetch negotiation algorithm '%s'", strval);
132133
}
133134

135+
if (!repo_config_get_string_multi(r, "fetch.haverefs",
136+
&have_refs_config)) {
137+
struct string_list_item *item;
138+
r->settings.fetch_have_refs = xmalloc(sizeof(*r->settings.fetch_have_refs));
139+
string_list_init_dup(r->settings.fetch_have_refs);
140+
for_each_string_list_item(item, have_refs_config)
141+
string_list_append(r->settings.fetch_have_refs,
142+
item->string);
143+
}
144+
134145
/*
135146
* This setting guards all index reads to require a full index
136147
* over a sparse index. After suitable guards are placed in the
@@ -161,6 +172,10 @@ void repo_settings_clear(struct repository *r)
161172
struct repo_settings empty = REPO_SETTINGS_INIT;
162173
FREE_AND_NULL(r->settings.fsmonitor);
163174
FREE_AND_NULL(r->settings.hooks_path);
175+
if (r->settings.fetch_have_refs) {
176+
string_list_clear(r->settings.fetch_have_refs, 0);
177+
FREE_AND_NULL(r->settings.fetch_have_refs);
178+
}
164179
r->settings = empty;
165180
}
166181

repo-settings.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
struct fsmonitor_settings;
55
struct repository;
6+
struct string_list;
67

78
enum untracked_cache_setting {
89
UNTRACKED_CACHE_KEEP,
@@ -58,6 +59,7 @@ struct repo_settings {
5859
int pack_use_sparse;
5960
int pack_use_path_walk;
6061
enum fetch_negotiation_setting fetch_negotiation_algorithm;
62+
struct string_list *fetch_have_refs;
6163

6264
int core_multi_pack_index;
6365
int warn_ambiguous_refs; /* lazily loaded via accessor */

0 commit comments

Comments
 (0)