From: Derrick Stolee via GitGitGadget Date: Wed, 08 Apr 2026 14:36:09 GMT Subject: [PATCH 3/4] remote: add mustHave config as default for --must-have Message-ID: In-Reply-To: From: Derrick Stolee Add a new multi-valued config option remote..mustHave that specifies ref patterns whose tips should always be sent as "have" commits during fetch negotiation with that remote. Parse the option in handle_config() following the same pattern as remote..serverOption. Store the values in a string_list on struct remote so they are available per-remote. In builtin/fetch.c, when no --must-have options are given on the command line, use the remote..mustHave config values as the default. If the user explicitly provides --must-have on the CLI, the config is not used, giving CLI precedence. Signed-off-by: Derrick Stolee --- Documentation/config/remote.adoc | 23 +++++++++++++++ Documentation/fetch-options.adoc | 4 +++ builtin/fetch.c | 3 ++ remote.c | 6 ++++ remote.h | 1 + t/t5510-fetch.sh | 48 ++++++++++++++++++++++++++++++++ 6 files changed, 85 insertions(+) diff --git a/Documentation/config/remote.adoc b/Documentation/config/remote.adoc index 91e46f66f5..9df8be27eb 100644 --- a/Documentation/config/remote.adoc +++ b/Documentation/config/remote.adoc @@ -107,6 +107,29 @@ priority configuration file (e.g. `.git/config` in a repository) to clear the values inherited from a lower priority configuration files (e.g. `$HOME/.gitconfig`). +remote..mustHave:: + When negotiating with this remote during `git fetch` and `git push`, + the client advertises a list of commits that exist locally. In + repos with many references, this list of "haves" can be truncated. + Depending on data shape, dropping certain references may be + expensive. This multi-valued config option specifies ref patterns + whose tips should always be sent as "have" commits during fetch + negotiation with this remote. ++ +Each value is either an exact ref name (e.g. `refs/heads/release`) or a +glob pattern (e.g. `refs/heads/release/*`). The pattern syntax is the same +as for `--negotiation-tip`. ++ +These config values are used as defaults for the `--must-have` command-line +option. If `--must-have` is specified on the command line, then the config +values are not used. ++ +This option is additive with the normal negotiation process: the +negotiation algorithm still runs and advertises its own selected commits, +but the refs matching `remote..mustHave` are sent unconditionally on +top of those heuristically selected commits. This option is also used +during push negotiation when `push.negotiate` is enabled. + remote..followRemoteHEAD:: How linkgit:git-fetch[1] should handle updates to `remotes//HEAD` when fetching using the configured refspecs of a remote. diff --git a/Documentation/fetch-options.adoc b/Documentation/fetch-options.adoc index 852e30191e..fa3969d68d 100644 --- a/Documentation/fetch-options.adoc +++ b/Documentation/fetch-options.adoc @@ -86,6 +86,10 @@ is the same as for `--negotiation-tip`. + If `--negotiation-tip` is used, the have set is first restricted by that option and then increased to include the tips specified by `--must-have`. ++ +If this option is not specified on the command line, then any +`remote..mustHave` config values for the current remote are used +instead. `--negotiate-only`:: Do not fetch anything from the server, and instead print the diff --git a/builtin/fetch.c b/builtin/fetch.c index 5d29cc6b1a..fa491c106f 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1607,6 +1607,9 @@ static struct transport *prepare_transport(struct remote *remote, int deepen, transport->smart_options->must_have = &must_have; else warning(_("ignoring %s because the protocol does not support it"), "--must-have"); + } else if (remote->must_have.nr) { + if (transport->smart_options) + transport->smart_options->must_have = &remote->must_have; } return transport; } diff --git a/remote.c b/remote.c index 7ca2a6501b..e07ec08fb3 100644 --- a/remote.c +++ b/remote.c @@ -152,6 +152,7 @@ static struct remote *make_remote(struct remote_state *remote_state, refspec_init_push(&ret->push); refspec_init_fetch(&ret->fetch); string_list_init_dup(&ret->server_options); + string_list_init_dup(&ret->must_have); ALLOC_GROW(remote_state->remotes, remote_state->remotes_nr + 1, remote_state->remotes_alloc); @@ -179,6 +180,7 @@ static void remote_clear(struct remote *remote) FREE_AND_NULL(remote->http_proxy); FREE_AND_NULL(remote->http_proxy_authmethod); string_list_clear(&remote->server_options, 0); + string_list_clear(&remote->must_have, 0); } static void add_merge(struct branch *branch, const char *name) @@ -562,6 +564,10 @@ static int handle_config(const char *key, const char *value, } else if (!strcmp(subkey, "serveroption")) { return parse_transport_option(key, value, &remote->server_options); + } else if (!strcmp(subkey, "musthave")) { + if (!value) + return config_error_nonbool(key); + string_list_append(&remote->must_have, value); } else if (!strcmp(subkey, "followremotehead")) { const char *no_warn_branch; if (!strcmp(value, "never")) diff --git a/remote.h b/remote.h index fc052945ee..e125313f45 100644 --- a/remote.h +++ b/remote.h @@ -117,6 +117,7 @@ struct remote { char *http_proxy_authmethod; struct string_list server_options; + struct string_list must_have; enum follow_remote_head_settings follow_remote_head; const char *no_warn_branch; diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index c34f3805c1..09e7b613a5 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -1801,6 +1801,54 @@ test_expect_success '--must-have avoids duplicates with negotiator' ' test_line_count = 1 matches ' +test_expect_success 'remote..mustHave used as default for --must-have' ' + test_when_finished rm -f trace && + setup_negotiation_tip server server 0 && + + # No --must-have on CLI; config should be used as default. + git -C client config --add remote.origin.mustHave refs/tags/beta_1 && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-tip=alpha_1 \ + origin alpha_s beta_s && + + ALPHA_1=$(git -C client rev-parse alpha_1) && + test_grep "fetch> have $ALPHA_1" trace && + BETA_1=$(git -C client rev-parse beta_1) && + test_grep "fetch> have $BETA_1" trace +' + +test_expect_success 'remote..mustHave works with glob patterns' ' + test_when_finished rm -f trace && + setup_negotiation_tip server server 0 && + + git -C client config --add remote.origin.mustHave "refs/tags/beta_*" && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-tip=alpha_1 \ + origin alpha_s beta_s && + + BETA_1=$(git -C client rev-parse beta_1) && + test_grep "fetch> have $BETA_1" trace && + BETA_2=$(git -C client rev-parse beta_2) && + test_grep "fetch> have $BETA_2" trace +' + +test_expect_success 'CLI --must-have overrides remote..mustHave' ' + test_when_finished rm -f trace && + setup_negotiation_tip server server 0 && + + # Config says beta_2, CLI says beta_1; only CLI should be used. + git -C client config --add remote.origin.mustHave refs/tags/beta_2 && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-tip=alpha_1 \ + --must-have=refs/tags/beta_1 \ + origin alpha_s beta_s && + + BETA_1=$(git -C client rev-parse beta_1) && + test_grep "fetch> have $BETA_1" trace && + BETA_2=$(git -C client rev-parse beta_2) && + test_grep ! "fetch> have $BETA_2" trace +' + . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd -- gitgitgadget