From: "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: gitster@pobox.com, Derrick Stolee <stolee@gmail.com>,
Derrick Stolee <stolee@gmail.com>
Subject: [PATCH 11/11] config-batch: add unset v1 command
Date: Wed, 04 Feb 2026 14:20:03 +0000 [thread overview]
Message-ID: <59d19fee5f5bd34c5864bebb8243afdc6bc9ea7a.1770214803.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2033.git.1770214803.gitgitgadget@gmail.com>
From: Derrick Stolee <stolee@gmail.com>
Add a new 'unset' command with version 1 that mimics 'git config
--unset' with optional regex pattern or '--fixed-value' arguments.
Signed-off-by: Derrick Stolee <stolee@gmail.com>
---
Documentation/git-config-batch.adoc | 28 ++++++++
builtin/config-batch.c | 99 +++++++++++++++++++++++++++++
t/t1312-config-batch.sh | 61 ++++++++++++++++--
3 files changed, 181 insertions(+), 7 deletions(-)
diff --git a/Documentation/git-config-batch.adoc b/Documentation/git-config-batch.adoc
index feec85c4ef..bdfd872d65 100644
--- a/Documentation/git-config-batch.adoc
+++ b/Documentation/git-config-batch.adoc
@@ -135,6 +135,34 @@ set 1 success <scope> <key> <value>
set 1 failed <scope> <key> <value>
------------
+`unset` version 1::
+ The `unset` command removes a single value from 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. There could be two additional
+ arguments used to match specific config values, where the first
+ is either `arg:regex` or `arg:fixed-value` to specify the type
+ of match.
++
+------------
+unset 1 <scope> <key>
+unset 1 <scope> <key> arg:regex <value-pattern>
+unset 1 <scope> <key> arg:fixed-value <value>
+------------
++
+These uses will match the behavior of `git config --unset --<scope> <key>`
+with the additional arguments of `<value-pattern>` if `arg:regex` is
+given or `--fixed-value <value>` if `arg:fixed-value` is given.
++
+The response of these commands will include a `success` message
+if matched values are found and removed as expected or `failed` if an
+unexpected failure occurs:
++
+------------
+unset 1 success <scope> <key>
+unset 1 failed <scope> <key>
+------------
+
NUL-Terminated Format
~~~~~~~~~~~~~~~~~~~~~
diff --git a/builtin/config-batch.c b/builtin/config-batch.c
index 373b0cad47..25a942ba61 100644
--- a/builtin/config-batch.c
+++ b/builtin/config-batch.c
@@ -17,6 +17,7 @@ static int zformat = 0;
#define HELP_COMMAND "help"
#define GET_COMMAND "get"
#define SET_COMMAND "set"
+#define UNSET_COMMAND "unset"
#define COMMAND_PARSE_ERROR "command_parse_error"
static void print_word(const char *word, int start)
@@ -445,6 +446,99 @@ cleanup:
return res;
}
+/**
+ * 'unset' command, version 1.
+ *
+ * Positional arguments should be of the form:
+ *
+ * [0] scope ("system", "global", "local", or "worktree")
+ * [1] config key
+ * [2] config value
+ * [3*] match ("regex", "fixed-value")
+ * [4*] value regex OR value string
+ *
+ * [N*] indicates optional parameters that are not needed.
+ */
+static int unset_command_1(struct repository *repo,
+ const char *prefix,
+ char *data,
+ size_t data_len)
+{
+ int res = 0, err = 0, flags = 0;
+ enum config_scope scope = CONFIG_SCOPE_UNKNOWN;
+ char *token = NULL, *key = NULL, *value_pattern = NULL;
+ size_t token_len;
+ 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;
+
+ token_len = parse_token(&data, &data_len, &token, &err);
+ if (err)
+ goto parse_error;
+
+ if (token_len && !strncmp(token, "arg:", 4)) {
+ if (!strcmp(token + 4, "fixed-value"))
+ flags |= CONFIG_FLAGS_FIXED_VALUE;
+ /* no special logic for arg:regex. */
+ else if (strcmp(token + 4, "regex"))
+ goto parse_error; /* unknown arg. */
+
+ /* Use the remaining data as the value string. */
+ if (!zformat)
+ value_pattern = data;
+ else {
+ parse_token(&data, &data_len, &value_pattern, &err);
+ if (err)
+ goto parse_error;
+ }
+ } else if (token_len) {
+ /*
+ * If we have remaining tokens not starting in "arg:",
+ * then we don't understand them.
+ */
+ goto parse_error;
+ }
+
+ if (location_options_set_scope(&locopts, scope))
+ goto parse_error;
+ location_options_init(repo, &locopts, prefix);
+
+ res = repo_config_set_multivar_in_file_gently(
+ repo,
+ locopts.source.file,
+ key,
+ /* value */ NULL,
+ value_pattern,
+ /* comment */ NULL,
+ flags);
+
+ if (res)
+ res = emit_response(UNSET_COMMAND, "1", "failure",
+ scope_str(scope), key, NULL);
+ else
+ res = emit_response(UNSET_COMMAND, "1", "success",
+ scope_str(scope), key, NULL);
+
+ goto cleanup;
+
+parse_error:
+ res = command_parse_error(UNSET_COMMAND);
+
+cleanup:
+ location_options_release(&locopts);
+ return res;
+}
+
struct command {
const char *name;
command_fn fn;
@@ -467,6 +561,11 @@ static struct command commands[] = {
.fn = set_command_1,
.version = 1,
},
+ {
+ .name = UNSET_COMMAND,
+ .fn = unset_command_1,
+ .version = 1,
+ },
/* unknown_command must be last. */
{
.name = "",
diff --git a/t/t1312-config-batch.sh b/t/t1312-config-batch.sh
index 11380f4247..3bddbc0de3 100755
--- a/t/t1312-config-batch.sh
+++ b/t/t1312-config-batch.sh
@@ -47,10 +47,11 @@ test_expect_success 'help command' '
echo "help 1" >in &&
cat >expect <<-\EOF &&
- help 1 count 3
+ help 1 count 4
help 1 help 1
help 1 get 1
help 1 set 1
+ help 1 unset 1
EOF
git config-batch >out <in &&
@@ -64,10 +65,11 @@ test_expect_success 'help -z' '
EOF
cat >expect <<-\EOF &&
- 4:help 1:1 5:count 1:3
+ 4:help 1:1 5:count 1:4
4:help 1:1 4:help 1:1
4:help 1:1 3:get 1:1
4:help 1:1 3:set 1:1
+ 4:help 1:1 5:unset 1:1
15:unknown_command
EOF
@@ -295,15 +297,60 @@ test_expect_success 'set config by scope with -z' '
test_cmp expect-values values
'
-test_expect_success 'read/write interactions in sequence' '
- test_when_finished git config remove-section test.rw &&
+test_expect_success 'unset config by scope and filter' '
+ 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.unset.key system
+ set 1 global test.unset.key global
+ set 1 local test.unset.key local with spaces
+ set 1 worktree test.unset.key worktree
+ unset 1 system test.unset.key
+ unset 1 global test.unset.key arg:regex g.*
+ unset 1 local test.unset.key arg:fixed-value local with spaces
+ unset 1 worktree test.unset.key arg:fixed-value submodule
+ unset 1 worktree test.unset.key arg:regex l.*
+ EOF
+
+ cat >expect <<-\EOF &&
+ set 1 success system test.unset.key system
+ set 1 success global test.unset.key global
+ set 1 success local test.unset.key local with spaces
+ set 1 success worktree test.unset.key worktree
+ unset 1 success system test.unset.key
+ unset 1 success global test.unset.key
+ unset 1 success local test.unset.key
+ unset 1 failure worktree test.unset.key
+ unset 1 failure worktree test.unset.key
+ EOF
+
+ git config-batch <in >out 2>err &&
+ test_must_be_empty err &&
+ test_cmp expect out &&
+
+ cat >expect-values <<-EOF &&
+ file:.git/config.worktree worktree
+ EOF
+
+ git config get --show-origin --regexp --all test.unset.key >values &&
+ test_cmp expect-values values
+'
+
+test_expect_success 'read/write interactions in sequence' '
cat >in <<-\EOF &&
get 1 local test.rw.missing
set 1 local test.rw.found found
get 1 local test.rw.found
set 1 local test.rw.found updated
get 1 local test.rw.found
+ unset 1 local test.rw.found arg:fixed-value updated
+ get 1 local test.rw.found
EOF
cat >expect <<-\EOF &&
@@ -312,14 +359,14 @@ test_expect_success 'read/write interactions in sequence' '
get 1 found test.rw.found local found
set 1 success local test.rw.found updated
get 1 found test.rw.found local updated
+ unset 1 success local test.rw.found
+ get 1 missing test.rw.found
EOF
git config-batch <in >out 2>err &&
test_must_be_empty err &&
- test_cmp expect out &&
-
- test_cmp_config updated test.rw.found
+ test_cmp expect out
'
test_done
--
gitgitgadget
next prev parent reply other threads:[~2026-02-04 14:20 UTC|newest]
Thread overview: 40+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-04 14:19 [PATCH 00/11] [RFC] config-batch: a new builtin for tools querying config Derrick Stolee via GitGitGadget
2026-02-04 14:19 ` [PATCH 01/11] config-batch: basic boilerplate of new builtin Derrick Stolee via GitGitGadget
2026-02-04 23:23 ` Junio C Hamano
2026-02-05 14:17 ` Derrick Stolee
2026-02-05 17:26 ` Kristoffer Haugsbakk
2026-02-05 17:29 ` Kristoffer Haugsbakk
2026-02-06 4:11 ` Jean-Noël Avila
2026-02-04 14:19 ` [PATCH 02/11] config-batch: create parse loop and unknown command Derrick Stolee via GitGitGadget
2026-02-04 23:26 ` Junio C Hamano
2026-02-05 17:30 ` Kristoffer Haugsbakk
2026-02-06 4:15 ` Jean-Noël Avila
2026-02-04 14:19 ` [PATCH 03/11] config-batch: implement get v1 Derrick Stolee via GitGitGadget
2026-02-06 4:41 ` Jean-Noël Avila
2026-02-04 14:19 ` [PATCH 04/11] config-batch: create 'help' command Derrick Stolee via GitGitGadget
2026-02-06 4:49 ` Jean-Noël Avila
2026-02-10 4:20 ` Derrick Stolee
2026-02-04 14:19 ` [PATCH 05/11] config-batch: add NUL-terminated I/O format Derrick Stolee via GitGitGadget
2026-02-05 17:44 ` Kristoffer Haugsbakk
2026-02-06 4:58 ` Jean-Noël Avila
2026-02-04 14:19 ` [PATCH 06/11] docs: add design doc for config-batch Derrick Stolee via GitGitGadget
2026-02-05 17:38 ` Kristoffer Haugsbakk
2026-02-10 4:22 ` Derrick Stolee
2026-02-04 14:19 ` [PATCH 07/11] config: extract location structs from builtin Derrick Stolee via GitGitGadget
2026-02-04 14:20 ` [PATCH 08/11] config-batch: pass prefix through commands Derrick Stolee via GitGitGadget
2026-02-04 14:20 ` [PATCH 09/11] config-batch: add 'set' v1 command Derrick Stolee via GitGitGadget
2026-02-05 17:21 ` Kristoffer Haugsbakk
2026-02-05 18:58 ` Kristoffer Haugsbakk
2026-02-05 19:01 ` Kristoffer Haugsbakk
2026-02-10 4:25 ` Derrick Stolee
2026-02-06 5:04 ` Jean-Noël Avila
2026-02-04 14:20 ` [PATCH 10/11] t1312: create read/write test Derrick Stolee via GitGitGadget
2026-02-04 14:20 ` Derrick Stolee via GitGitGadget [this message]
2026-02-05 17:36 ` [PATCH 11/11] config-batch: add unset v1 command Kristoffer Haugsbakk
2026-02-04 23:04 ` [PATCH 00/11] [RFC] config-batch: a new builtin for tools querying config Junio C Hamano
2026-02-05 14:10 ` Derrick Stolee
2026-02-05 0:04 ` brian m. carlson
2026-02-05 13:52 ` Derrick Stolee
2026-02-10 4:49 ` Derrick Stolee
2026-02-05 14:45 ` Phillip Wood
2026-02-05 17:20 ` Kristoffer Haugsbakk
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=59d19fee5f5bd34c5864bebb8243afdc6bc9ea7a.1770214803.git.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=stolee@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox