git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 11/11] config-batch: add unset v1 command

From
Derrick Stolee via GitGitGadget <gitgitgadget@gmail.com>
Date
Feb 4, 2026, 14:20 UTC
Message-ID
<59d19fee5f5bd34c5864bebb8243afdc6bc9ea7a.1770214803.git.gitgitgadget@gmail.com>
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
Previous: Derrick Stolee via GitGitGadgetNext: Kristoffer Haugsbakk
Message 32 of 40 in “[RFC] config-batch: a new builtin for tools querying config”
  1. 00/11 [RFC] config-batch: a new builtin for tools querying configDerrick Stolee via GitGitGadget, Feb 4, 2026
  2. 01/11 config-batch: basic boilerplate of new builtinDerrick Stolee via GitGitGadget, Feb 4, 2026
  3. Junio C HamanoFeb 4, 2026
  4. Derrick StoleeFeb 5, 2026
  5. Kristoffer HaugsbakkFeb 5, 2026
  6. Kristoffer HaugsbakkFeb 5, 2026
  7. Jean-Noël AvilaFeb 6, 2026
  8. 02/11 config-batch: create parse loop and unknown commandDerrick Stolee via GitGitGadget, Feb 4, 2026
  9. Junio C HamanoFeb 4, 2026
  10. Kristoffer HaugsbakkFeb 5, 2026
  11. Jean-Noël AvilaFeb 6, 2026
  12. 03/11 config-batch: implement get v1Derrick Stolee via GitGitGadget, Feb 4, 2026
  13. Jean-Noël AvilaFeb 6, 2026
  14. 04/11 config-batch: create 'help' commandDerrick Stolee via GitGitGadget, Feb 4, 2026
  15. Jean-Noël AvilaFeb 6, 2026
  16. Derrick StoleeFeb 10, 2026
  17. 05/11 config-batch: add NUL-terminated I/O formatDerrick Stolee via GitGitGadget, Feb 4, 2026
  18. Kristoffer HaugsbakkFeb 5, 2026
  19. Jean-Noël AvilaFeb 6, 2026
  20. 06/11 docs: add design doc for config-batchDerrick Stolee via GitGitGadget, Feb 4, 2026
  21. Kristoffer HaugsbakkFeb 5, 2026
  22. Derrick StoleeFeb 10, 2026
  23. 07/11 config: extract location structs from builtinDerrick Stolee via GitGitGadget, Feb 4, 2026
  24. 08/11 config-batch: pass prefix through commandsDerrick Stolee via GitGitGadget, Feb 4, 2026
  25. 09/11 config-batch: add 'set' v1 commandDerrick Stolee via GitGitGadget, Feb 4, 2026
  26. Kristoffer HaugsbakkFeb 5, 2026
  27. Kristoffer HaugsbakkFeb 5, 2026
  28. Kristoffer HaugsbakkFeb 5, 2026
  29. Derrick StoleeFeb 10, 2026
  30. Jean-Noël AvilaFeb 6, 2026
  31. 10/11 t1312: create read/write testDerrick Stolee via GitGitGadget, Feb 4, 2026
  32. 11/11 config-batch: add unset v1 commandDerrick Stolee via GitGitGadget, Feb 4, 2026
  33. Kristoffer HaugsbakkFeb 5, 2026
  34. Junio C HamanoFeb 4, 2026
  35. Derrick StoleeFeb 5, 2026
  36. brian m. carlsonFeb 5, 2026
  37. Derrick StoleeFeb 5, 2026
  38. Derrick StoleeFeb 10, 2026
  39. Phillip WoodFeb 5, 2026
  40. Kristoffer HaugsbakkFeb 5, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.