[PATCH 09/11] config-batch: add 'set' v1 command
From: Derrick Stolee via GitGitGadget <hidden>
Date: 2026-02-04 14:20:21
Subsystem:
documentation, the rest · Maintainers:
Jonathan Corbet, Linus Torvalds
From: Derrick Stolee <redacted> This new command is intended for single-value assignments to a specific chosen scope. More complicated versions of the 'git config set' command will be incorporated into future commands. Signed-off-by: Derrick Stolee <redacted> --- Documentation/git-config-batch.adoc | 24 ++++++++ builtin/config-batch.c | 71 ++++++++++++++++++++++ config.c | 27 +++++++++ config.h | 3 + t/t1312-config-batch.sh | 94 ++++++++++++++++++++++++++++- 5 files changed, 217 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-config-batch.adoc b/Documentation/git-config-batch.adoc
index 3c9a3bb763..feec85c4ef 100644
--- a/Documentation/git-config-batch.adoc
+++ b/Documentation/git-config-batch.adoc@@ -111,6 +111,30 @@ get 1 missing <key> [<value-pattern>|<value>] where `<value-pattern>` or `<value>` is only supplied if provided in the command. +`set` version 1:: + The `set` command writes a single key-value pair to a config + file. It specifies which file by a `<scope>` parameter from + among `system`, `global`, `local`, and `worktree`. The `<key>` + is the next positional argument. The remaining data in the line + is provided as the `<value>` to assign the config. ++ +------------ +set 1 <scope> <key> <value> +------------ ++ +These uses will match the behavior of `git config --set --<scope> <key> +<value>`. Note that replacing all values with the `--all` option or +matching specific value patterns are not supported by this command. ++ +The response of these commands will include a `success` message if the +value is written as expected or `failed` if an unexpected failure +occurs: ++ +------------ +set 1 success <scope> <key> <value> +set 1 failed <scope> <key> <value> +------------ + NUL-Terminated Format ~~~~~~~~~~~~~~~~~~~~~
diff --git a/builtin/config-batch.c b/builtin/config-batch.c
index 9829b16c6f..373b0cad47 100644
--- a/builtin/config-batch.c
+++ b/builtin/config-batch.c@@ -16,6 +16,7 @@ static int zformat = 0; #define UNKNOWN_COMMAND "unknown_command" #define HELP_COMMAND "help" #define GET_COMMAND "get" +#define SET_COMMAND "set" #define COMMAND_PARSE_ERROR "command_parse_error" static void print_word(const char *word, int start)
@@ -379,6 +380,71 @@ cleanup: return res; } + +/** + * 'set' command, version 1. + * + * Positional arguments should be of the form: + * + * [0] scope ("system", "global", "local", or "worktree") + * [1] config key + * [2] config value + */ +static int set_command_1(struct repository *repo, + const char *prefix, + char *data, + size_t data_len) +{ + int res = 0, err = 0; + enum config_scope scope = CONFIG_SCOPE_UNKNOWN; + char *token = NULL, *key = NULL, *value = NULL; + struct config_location_options locopts = CONFIG_LOCATION_OPTIONS_INIT; + + if (!parse_token(&data, &data_len, &token, &err) || err) + goto parse_error; + + if (parse_scope(token, &scope) || + scope == CONFIG_SCOPE_UNKNOWN || + scope == CONFIG_SCOPE_SUBMODULE || + scope == CONFIG_SCOPE_COMMAND) + goto parse_error; + + if (!parse_token(&data, &data_len, &key, &err) || err) + goto parse_error; + + /* Use the remaining data as the value string. */ + if (!zformat) + value = data; + else { + parse_token(&data, &data_len, &value, &err); + if (err) + goto parse_error; + } + + if (location_options_set_scope(&locopts, scope)) + goto parse_error; + location_options_init(repo, &locopts, prefix); + + res = repo_config_set_in_file_gently(repo, locopts.source.file, + key, NULL, value); + + if (res) + res = emit_response(SET_COMMAND, "1", "failure", + scope_str(scope), key, value, NULL); + else + res = emit_response(SET_COMMAND, "1", "success", + scope_str(scope), key, value, NULL); + + goto cleanup; + +parse_error: + res = command_parse_error(SET_COMMAND); + +cleanup: + location_options_release(&locopts); + return res; +} + struct command { const char *name; command_fn fn;
@@ -396,6 +462,11 @@ static struct command commands[] = { .fn = get_command_1, .version = 1, }, + { + .name = SET_COMMAND, + .fn = set_command_1, + .version = 1, + }, /* unknown_command must be last. */ { .name = "",
diff --git a/config.c b/config.c
index 9f1a7b45cf..fa72234750 100644
--- a/config.c
+++ b/config.c@@ -3594,6 +3594,33 @@ int lookup_config(const char **mapping, int nr_mapping, const char *var) return -1; } +int location_options_set_scope(struct config_location_options *opts, + enum config_scope scope) +{ + switch (scope) { + case CONFIG_SCOPE_SYSTEM: + opts->use_system_config = 1; + break; + + case CONFIG_SCOPE_GLOBAL: + opts->use_global_config = 1; + break; + + case CONFIG_SCOPE_LOCAL: + opts->use_local_config = 1; + break; + + case CONFIG_SCOPE_WORKTREE: + opts->use_worktree_config = 1; + break; + + default: + return -1; + } + + return 0; +} + void location_options_init(struct repository *repo, struct config_location_options *opts, const char *prefix)
diff --git a/config.h b/config.h
index 6663964977..f6432c1ec2 100644
--- a/config.h
+++ b/config.h@@ -180,6 +180,9 @@ struct config_location_options { .respect_includes_opt = -1, \ } +int location_options_set_scope(struct config_location_options *opts, + enum config_scope scope); + void location_options_init(struct repository *repo, struct config_location_options *opts, const char *prefix);
diff --git a/t/t1312-config-batch.sh b/t/t1312-config-batch.sh
index f7a74ddc2c..40f6f90ef2 100755
--- a/t/t1312-config-batch.sh
+++ b/t/t1312-config-batch.sh@@ -47,9 +47,10 @@ test_expect_success 'help command' ' echo "help 1" >in && cat >expect <<-\EOF && - help 1 count 2 + help 1 count 3 help 1 help 1 help 1 get 1 + help 1 set 1 EOF git config-batch >out <in &&
@@ -63,9 +64,10 @@ test_expect_success 'help -z' ' EOF cat >expect <<-\EOF && - 4:help 1:1 5:count 1:2 + 4:help 1:1 5:count 1:3 4:help 1:1 4:help 1:1 4:help 1:1 3:get 1:1 + 4:help 1:1 3:set 1:1 15:unknown_command EOF
@@ -205,4 +207,92 @@ test_expect_success 'get config with -z' ' test_cmp expect out ' +test_expect_success 'set config by scope' ' + test_when_finished git config remove-section test.set && + GIT_CONFIG_SYSTEM=system-config-file && + GIT_CONFIG_NOSYSTEM=0 && + GIT_CONFIG_GLOBAL=global-config-file && + export GIT_CONFIG_SYSTEM && + export GIT_CONFIG_NOSYSTEM && + export GIT_CONFIG_GLOBAL && + + cat >in <<-\EOF && + set 1 system test.set.system system + set 1 global test.set.global global + set 1 local test.set.local local with spaces + set 1 worktree test.set.worktree worktree + set 1 submodule test.set.submodule submodule + set 1 command test.set.command command + set 1 inherited test.set.inherited inherited + EOF + + cat >expect <<-\EOF && + set 1 success system test.set.system system + set 1 success global test.set.global global + set 1 success local test.set.local local with spaces + set 1 success worktree test.set.worktree worktree + command_parse_error set + command_parse_error set + command_parse_error set + EOF + + git config-batch <in >out 2>err && + + test_must_be_empty err && + test_cmp expect out && + + cat >expect-values <<-EOF && + file:system-config-file system + file:global-config-file global + file:.git/config local with spaces + file:.git/config.worktree worktree + EOF + + git config get --show-origin --regexp --all test.set.* >values && + test_cmp expect-values values +' + +test_expect_success 'set config by scope with -z' ' + test_when_finished git config remove-section test.set && + GIT_CONFIG_SYSTEM=system-config-file && + GIT_CONFIG_NOSYSTEM=0 && + GIT_CONFIG_GLOBAL=global-config-file && + export GIT_CONFIG_SYSTEM && + export GIT_CONFIG_NOSYSTEM && + export GIT_CONFIG_GLOBAL && + + cat >in <<-\EOF && + 3:set NUL 1:1 NUL 6:system NUL 15:test.set.system NUL 6:system NUL NUL + 3:set NUL 1:1 NUL 6:global NUL 15:test.set.global NUL 6:global NUL NUL + 3:set NUL 1:1 NUL 5:local NUL 14:test.set.local NUL 17:local with spaces NUL NUL + 3:set NUL 1:1 NUL 8:worktree NUL 17:test.set.worktree NUL 8:worktree NUL NUL + 3:set NUL 1:1 NUL 9:submodule NUL 18:test.set.submodule NUL 9:submodule NUL NUL + 3:set NUL 1:1 NUL 7:command NUL 16:test.set.command NUL 7:command NUL NUL + 3:set NUL 1:1 NUL 9:inherited NUL 18:test.set.inherited NUL 9:inherited NUL NUL + EOF + + cat >expect <<-\EOF && + 3:set 1:1 7:success 6:system 15:test.set.system 6:system + 3:set 1:1 7:success 6:global 15:test.set.global 6:global + 3:set 1:1 7:success 5:local 14:test.set.local 17:local with spaces + 3:set 1:1 7:success 8:worktree 17:test.set.worktree 8:worktree + 19:command_parse_error 3:set + 19:command_parse_error 3:set + 19:command_parse_error 3:set + EOF + + test_zformat git config-batch -z >out <in && + test_cmp expect out && + + cat >expect-values <<-EOF && + file:system-config-file system + file:global-config-file global + file:.git/config local with spaces + file:.git/config.worktree worktree + EOF + + git config get --show-origin --regexp --all test.set.* >values && + test_cmp expect-values values +' + test_done
--
gitgitgadget