Merge branch 'xx/remote-server-option-config'

A new configuration variable remote.<name>.serverOption makes the
transport layer act as if the --serverOption=<value> option is
given from the command line.

* xx/remote-server-option-config:
  ls-remote: leakfix for not clearing server_options
  fetch: respect --server-option when fetching multiple remotes
  transport.c:🤝 make use of server options from remote
  remote: introduce remote.<name>.serverOption configuration
  transport: introduce parse_transport_option() method
This commit is contained in:
Taylor Blau
2024-10-15 16:56:43 -04:00
12 changed files with 184 additions and 8 deletions

View File

@ -96,3 +96,13 @@ remote.<name>.partialclonefilter::
Changing or clearing this value will only affect fetches for new commits. Changing or clearing this value will only affect fetches for new commits.
To fetch associated objects for commits already present in the local object To fetch associated objects for commits already present in the local object
database, use the `--refetch` option of linkgit:git-fetch[1]. database, use the `--refetch` option of linkgit:git-fetch[1].
remote.<name>.serverOption::
The default set of server options used when fetching from this remote.
These server options can be overridden by the `--server-option=` command
line arguments.
+
This is a multi-valued variable, and an empty value can be used in a higher
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`).

View File

@ -305,6 +305,9 @@ endif::git-pull[]
unknown ones, is server-specific. unknown ones, is server-specific.
When multiple `--server-option=<option>` are given, they are all When multiple `--server-option=<option>` are given, they are all
sent to the other side in the order listed on the command line. sent to the other side in the order listed on the command line.
When no `--server-option=<option>` is given from the command line,
the values of configuration variable `remote.<name>.serverOption`
are used instead.
--show-forced-updates:: --show-forced-updates::
By default, git checks if a branch is force-updated during By default, git checks if a branch is force-updated during

View File

@ -149,6 +149,9 @@ objects from the source repository into a pack in the cloned repository.
unknown ones, is server-specific. unknown ones, is server-specific.
When multiple `--server-option=<option>` are given, they are all When multiple `--server-option=<option>` are given, they are all
sent to the other side in the order listed on the command line. sent to the other side in the order listed on the command line.
When no ++--server-option=++__<option>__ is given from the command
line, the values of configuration variable `remote.<name>.serverOption`
are used instead.
`-n`:: `-n`::
`--no-checkout`:: `--no-checkout`::

View File

@ -81,6 +81,9 @@ OPTIONS
character. character.
When multiple `--server-option=<option>` are given, they are all When multiple `--server-option=<option>` are given, they are all
sent to the other side in the order listed on the command line. sent to the other side in the order listed on the command line.
When no `--server-option=<option>` is given from the command line,
the values of configuration variable `remote.<name>.serverOption`
are used instead.
<repository>:: <repository>::
The "remote" repository to query. This parameter can be The "remote" repository to query. This parameter can be

View File

@ -1981,6 +1981,8 @@ static int fetch_multiple(struct string_list *list, int max_children,
strvec_pushl(&argv, "-c", "fetch.bundleURI=", strvec_pushl(&argv, "-c", "fetch.bundleURI=",
"fetch", "--append", "--no-auto-gc", "fetch", "--append", "--no-auto-gc",
"--no-write-commit-graph", NULL); "--no-write-commit-graph", NULL);
for (i = 0; i < server_options.nr; i++)
strvec_pushf(&argv, "--server-option=%s", server_options.items[i].string);
add_options_to_argv(&argv, config); add_options_to_argv(&argv, config);
if (max_children != 1 && list->nr != 1) { if (max_children != 1 && list->nr != 1) {

View File

@ -173,5 +173,6 @@ int cmd_ls_remote(int argc,
transport_ls_refs_options_release(&transport_options); transport_ls_refs_options_release(&transport_options);
strvec_clear(&pattern); strvec_clear(&pattern);
string_list_clear(&server_options, 0);
return status; return status;
} }

View File

@ -519,14 +519,7 @@ static int git_push_config(const char *k, const char *v,
RECURSE_SUBMODULES_ON_DEMAND : RECURSE_SUBMODULES_OFF; RECURSE_SUBMODULES_ON_DEMAND : RECURSE_SUBMODULES_OFF;
recurse_submodules = val; recurse_submodules = val;
} else if (!strcmp(k, "push.pushoption")) { } else if (!strcmp(k, "push.pushoption")) {
if (!v) return parse_transport_option(k, v, &push_options_config);
return config_error_nonbool(k);
else
if (!*v)
string_list_clear(&push_options_config, 0);
else
string_list_append(&push_options_config, v);
return 0;
} else if (!strcmp(k, "color.push")) { } else if (!strcmp(k, "color.push")) {
push_use_color = git_config_colorbool(k, v); push_use_color = git_config_colorbool(k, v);
return 0; return 0;

View File

@ -24,6 +24,7 @@
#include "advice.h" #include "advice.h"
#include "connect.h" #include "connect.h"
#include "parse-options.h" #include "parse-options.h"
#include "transport.h"
enum map_direction { FROM_SRC, FROM_DST }; enum map_direction { FROM_SRC, FROM_DST };
@ -143,6 +144,7 @@ static struct remote *make_remote(struct remote_state *remote_state,
ret->name = xstrndup(name, len); ret->name = xstrndup(name, len);
refspec_init(&ret->push, REFSPEC_PUSH); refspec_init(&ret->push, REFSPEC_PUSH);
refspec_init(&ret->fetch, REFSPEC_FETCH); refspec_init(&ret->fetch, REFSPEC_FETCH);
string_list_init_dup(&ret->server_options);
ALLOC_GROW(remote_state->remotes, remote_state->remotes_nr + 1, ALLOC_GROW(remote_state->remotes, remote_state->remotes_nr + 1,
remote_state->remotes_alloc); remote_state->remotes_alloc);
@ -166,6 +168,7 @@ static void remote_clear(struct remote *remote)
free((char *)remote->uploadpack); free((char *)remote->uploadpack);
FREE_AND_NULL(remote->http_proxy); FREE_AND_NULL(remote->http_proxy);
FREE_AND_NULL(remote->http_proxy_authmethod); FREE_AND_NULL(remote->http_proxy_authmethod);
string_list_clear(&remote->server_options, 0);
} }
static void add_merge(struct branch *branch, const char *name) static void add_merge(struct branch *branch, const char *name)
@ -508,6 +511,9 @@ static int handle_config(const char *key, const char *value,
} else if (!strcmp(subkey, "vcs")) { } else if (!strcmp(subkey, "vcs")) {
FREE_AND_NULL(remote->foreign_vcs); FREE_AND_NULL(remote->foreign_vcs);
return git_config_string(&remote->foreign_vcs, key, value); return git_config_string(&remote->foreign_vcs, key, value);
} else if (!strcmp(subkey, "serveroption")) {
return parse_transport_option(key, value,
&remote->server_options);
} }
return 0; return 0;
} }

View File

@ -4,6 +4,7 @@
#include "hash.h" #include "hash.h"
#include "hashmap.h" #include "hashmap.h"
#include "refspec.h" #include "refspec.h"
#include "string-list.h"
#include "strvec.h" #include "strvec.h"
struct option; struct option;
@ -104,6 +105,8 @@ struct remote {
/* The method used for authenticating against `http_proxy`. */ /* The method used for authenticating against `http_proxy`. */
char *http_proxy_authmethod; char *http_proxy_authmethod;
struct string_list server_options;
}; };
/** /**

View File

@ -185,6 +185,43 @@ test_expect_success 'server-options are sent when using ls-remote' '
grep "server-option=world" log grep "server-option=world" log
' '
test_expect_success 'server-options from configuration are used by ls-remote' '
test_when_finished "rm -rf log myclone" &&
git clone "file://$(pwd)/file_parent" myclone &&
cat >expect <<-EOF &&
$(git -C file_parent rev-parse refs/heads/main)$(printf "\t")refs/heads/main
EOF
# Default server options from configuration are used
git -C myclone config --add remote.origin.serverOption foo &&
git -C myclone config --add remote.origin.serverOption bar &&
GIT_TRACE_PACKET="$(pwd)/log" git -C myclone -c protocol.version=2 \
ls-remote origin main >actual &&
test_cmp expect actual &&
test_grep "ls-remote> server-option=foo" log &&
test_grep "ls-remote> server-option=bar" log &&
rm -f log &&
# Empty value of remote.<name>.serverOption clears the list
git -C myclone config --add remote.origin.serverOption "" &&
git -C myclone config --add remote.origin.serverOption tar &&
GIT_TRACE_PACKET="$(pwd)/log" git -C myclone -c protocol.version=2 \
ls-remote origin main >actual &&
test_cmp expect actual &&
test_grep "ls-remote> server-option=tar" log &&
test_grep ! "ls-remote> server-option=foo" log &&
test_grep ! "ls-remote> server-option=bar" log &&
rm -f log &&
# Server option from command line overrides those from configuration
GIT_TRACE_PACKET="$(pwd)/log" git -C myclone -c protocol.version=2 \
ls-remote -o hello -o world origin main >actual &&
test_cmp expect actual &&
test_grep "ls-remote> server-option=hello" log &&
test_grep "ls-remote> server-option=world" log &&
test_grep ! "ls-remote> server-option=tar" log
'
test_expect_success 'warn if using server-option with ls-remote with legacy protocol' ' test_expect_success 'warn if using server-option with ls-remote with legacy protocol' '
test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 git -c protocol.version=0 \ test_must_fail env GIT_TEST_PROTOCOL_VERSION=0 git -c protocol.version=0 \
ls-remote -o hello -o world "file://$(pwd)/file_parent" main 2>err && ls-remote -o hello -o world "file://$(pwd)/file_parent" main 2>err &&
@ -381,6 +418,54 @@ test_expect_success 'server-options are sent when fetching' '
grep "server-option=world" log grep "server-option=world" log
' '
test_expect_success 'server-options are sent when fetch multiple remotes' '
test_when_finished "rm -f log server_options_sent" &&
git clone "file://$(pwd)/file_parent" child_multi_remotes &&
git -C child_multi_remotes remote add another "file://$(pwd)/file_parent" &&
GIT_TRACE_PACKET="$(pwd)/log" git -C child_multi_remotes -c protocol.version=2 \
fetch -o hello --all &&
grep "fetch> server-option=hello" log >server_options_sent &&
test_line_count = 2 server_options_sent
'
test_expect_success 'server-options from configuration are used by git-fetch' '
test_when_finished "rm -rf log myclone" &&
git clone "file://$(pwd)/file_parent" myclone &&
git -C file_parent log -1 --format=%s >expect &&
# Default server options from configuration are used
git -C myclone config --add remote.origin.serverOption foo &&
git -C myclone config --add remote.origin.serverOption bar &&
GIT_TRACE_PACKET="$(pwd)/log" git -C myclone -c protocol.version=2 \
fetch origin main &&
git -C myclone log -1 --format=%s origin/main >actual &&
test_cmp expect actual &&
test_grep "fetch> server-option=foo" log &&
test_grep "fetch> server-option=bar" log &&
rm -f log &&
# Empty value of remote.<name>.serverOption clears the list
git -C myclone config --add remote.origin.serverOption "" &&
git -C myclone config --add remote.origin.serverOption tar &&
GIT_TRACE_PACKET="$(pwd)/log" git -C myclone -c protocol.version=2 \
fetch origin main &&
git -C myclone log -1 --format=%s origin/main >actual &&
test_cmp expect actual &&
test_grep "fetch> server-option=tar" log &&
test_grep ! "fetch> server-option=foo" log &&
test_grep ! "fetch> server-option=bar" log &&
rm -f log &&
# Server option from command line overrides those from configuration
GIT_TRACE_PACKET="$(pwd)/log" git -C myclone -c protocol.version=2 \
fetch -o hello -o world origin main &&
git -C myclone log -1 --format=%s origin/main >actual &&
test_cmp expect actual &&
test_grep "fetch> server-option=hello" log &&
test_grep "fetch> server-option=world" log &&
test_grep ! "fetch> server-option=tar" log
'
test_expect_success 'warn if using server-option with fetch with legacy protocol' ' test_expect_success 'warn if using server-option with fetch with legacy protocol' '
test_when_finished "rm -rf temp_child" && test_when_finished "rm -rf temp_child" &&
@ -404,6 +489,37 @@ test_expect_success 'server-options are sent when cloning' '
grep "server-option=world" log grep "server-option=world" log
' '
test_expect_success 'server-options from configuration are used by git-clone' '
test_when_finished "rm -rf log myclone" &&
# Default server options from configuration are used
GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
-c remote.origin.serverOption=foo -c remote.origin.serverOption=bar \
clone "file://$(pwd)/file_parent" myclone &&
test_grep "clone> server-option=foo" log &&
test_grep "clone> server-option=bar" log &&
rm -rf log myclone &&
# Empty value of remote.<name>.serverOption clears the list
GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
-c remote.origin.serverOption=foo -c remote.origin.serverOption=bar \
-c remote.origin.serverOption= -c remote.origin.serverOption=tar \
clone "file://$(pwd)/file_parent" myclone &&
test_grep "clone> server-option=tar" log &&
test_grep ! "clone> server-option=foo" log &&
test_grep ! "clone> server-option=bar" log &&
rm -rf log myclone &&
# Server option from command line overrides those from configuration
GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
-c remote.origin.serverOption=tar \
clone --server-option=hello --server-option=world \
"file://$(pwd)/file_parent" myclone &&
test_grep "clone> server-option=hello" log &&
test_grep "clone> server-option=world" log &&
test_grep ! "clone> server-option=tar" log
'
test_expect_success 'warn if using server-option with clone with legacy protocol' ' test_expect_success 'warn if using server-option with clone with legacy protocol' '
test_when_finished "rm -rf myclone" && test_when_finished "rm -rf myclone" &&
@ -415,6 +531,23 @@ test_expect_success 'warn if using server-option with clone with legacy protocol
test_grep "server options require protocol version 2 or later" err test_grep "server options require protocol version 2 or later" err
' '
test_expect_success 'server-option configuration with legacy protocol is ok' '
test_when_finished "rm -rf myclone" &&
env GIT_TEST_PROTOCOL_VERSION=0 git -c protocol.version=0 \
-c remote.origin.serverOption=foo -c remote.origin.serverOption=bar \
clone "file://$(pwd)/file_parent" myclone
'
test_expect_success 'invalid server-option configuration' '
test_when_finished "rm -rf myclone" &&
test_must_fail git -c protocol.version=2 \
-c remote.origin.serverOption \
clone "file://$(pwd)/file_parent" myclone 2>err &&
test_grep "error: missing value for '\''remote.origin.serveroption'\''" err
'
test_expect_success 'upload-pack respects config using protocol v2' ' test_expect_success 'upload-pack respects config using protocol v2' '
git init server && git init server &&
write_script server/.git/hook <<-\EOF && write_script server/.git/hook <<-\EOF &&

View File

@ -334,6 +334,9 @@ static struct ref *handshake(struct transport *transport, int for_push,
data->version = discover_version(&reader); data->version = discover_version(&reader);
switch (data->version) { switch (data->version) {
case protocol_v2: case protocol_v2:
if ((!transport->server_options || !transport->server_options->nr) &&
transport->remote->server_options.nr)
transport->server_options = &transport->remote->server_options;
if (server_feature_v2("session-id", &server_sid)) if (server_feature_v2("session-id", &server_sid))
trace2_data_string("transfer", NULL, "server-sid", server_sid); trace2_data_string("transfer", NULL, "server-sid", server_sid);
if (must_list_refs) if (must_list_refs)
@ -1108,6 +1111,18 @@ int is_transport_allowed(const char *type, int from_user)
BUG("invalid protocol_allow_config type"); BUG("invalid protocol_allow_config type");
} }
int parse_transport_option(const char *var, const char *value,
struct string_list *transport_options)
{
if (!value)
return config_error_nonbool(var);
if (!*value)
string_list_clear(transport_options, 0);
else
string_list_append(transport_options, value);
return 0;
}
void transport_check_allowed(const char *type) void transport_check_allowed(const char *type)
{ {
if (!is_transport_allowed(type, -1)) if (!is_transport_allowed(type, -1))

View File

@ -342,4 +342,8 @@ void transport_print_push_status(const char *dest, struct ref *refs,
/* common method used by transport-helper.c and send-pack.c */ /* common method used by transport-helper.c and send-pack.c */
void reject_atomic_push(struct ref *refs, int mirror_mode); void reject_atomic_push(struct ref *refs, int mirror_mode);
/* common method to parse push-option or server-option from config */
int parse_transport_option(const char *var, const char *value,
struct string_list *transport_options);
#endif #endif