public inbox for git@vger.kernel.org
 help / color / mirror / Atom feed
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

  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