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 02/11] config-batch: create parse loop and unknown command
Date: Wed, 04 Feb 2026 14:19:54 +0000	[thread overview]
Message-ID: <ecd26a0f1fad5615aea07a388e34f02e9f33b870.1770214803.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2033.git.1770214803.gitgitgadget@gmail.com>

From: Derrick Stolee <stolee@gmail•com>

As we build new features in the config-batch command, we define the
plaintext protocol with line-by-line output and responses. To think to the
future, we make sure that the protocol has a clear way to respond to an
unknown command or an unknown version of that command.

As some commands will allow the final argument to contain spaces or even be
able to parse "\ " as a non-split token, we only provide the remaining line
as data.

Signed-off-by: Derrick Stolee <stolee@gmail•com>
---
 Documentation/git-config-batch.adoc |  23 ++++-
 builtin/config-batch.c              | 133 +++++++++++++++++++++++++++-
 t/t1312-config-batch.sh             |  19 +++-
 3 files changed, 170 insertions(+), 5 deletions(-)

diff --git a/Documentation/git-config-batch.adoc b/Documentation/git-config-batch.adoc
index dfa0bd83e2..9ca04b0c1e 100644
--- a/Documentation/git-config-batch.adoc
+++ b/Documentation/git-config-batch.adoc
@@ -13,7 +13,28 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-TODO
+Tools frequently need to change their behavior based on values stored in
+Git's configuration files. These files may have complicated conditions
+for including extra files, so it is difficult to produce an independent
+parser. To avoid executing multiple processes to discover or modify
+multiple configuration values, the `git config-batch` command allows a
+single process to handle multiple requests using a machine-parseable
+interface across `stdin` and `stdout`.
+
+PROTOCOL
+--------
+By default, the protocol uses line feeds (`LF`) to signal the end of a
+command over `stdin` or a response over `stdout`.
+
+The protocol will be extended in the future, and consumers should be
+resilient to older Git versions not understanding the latest command
+set. Thus, if the Git version includes the `git config-batch` builtin
+but doesn't understand an input command, it will return a single line
+response:
+
+```
+unknown_command LF
+```
 
 SEE ALSO
 --------
diff --git a/builtin/config-batch.c b/builtin/config-batch.c
index ea4f408ecb..dffedb8ca2 100644
--- a/builtin/config-batch.c
+++ b/builtin/config-batch.c
@@ -3,17 +3,144 @@
 #include "config.h"
 #include "environment.h"
 #include "parse-options.h"
+#include "strbuf.h"
+#include "string-list.h"
 
 static const char *const builtin_config_batch_usage[] = {
 	N_("git config-batch <options>"),
 	NULL
 };
 
+#define UNKNOWN_COMMAND "unknown_command"
+
+static int emit_response(const char *response, ...)
+{
+	va_list params;
+	const char *token;
+
+	printf("%s", response);
+
+	va_start(params, response);
+	while ((token = va_arg(params, const char *)))
+		printf(" %s", token);
+	va_end(params);
+
+	printf("\n");
+	fflush(stdout);
+	return 0;
+}
+
+/**
+ * A function pointer type for defining a command. The function is
+ * responsible for handling different versions of the command name.
+ *
+ * Provides the remaining 'data' for the command, to be parsed by
+ * the function as needed according to its parsing rules.
+ *
+ * These functions should only return a negative value if they result
+ * in such a catastrophic failure that the process should end.
+ *
+ * Return 0 on success.
+ */
+typedef int (*command_fn)(struct repository *repo,
+			  char *data, size_t data_len);
+
+static int unknown_command(struct repository *repo UNUSED,
+			  char *data UNUSED, size_t data_len UNUSED)
+{
+	return emit_response(UNKNOWN_COMMAND, NULL);
+}
+
+struct command {
+	const char *name;
+	command_fn fn;
+	int version;
+};
+
+static struct command commands[] = {
+	/* unknown_command must be last. */
+	{
+		.name = "",
+		.fn   = unknown_command,
+	},
+};
+
+#define COMMAND_COUNT ((size_t)(sizeof(commands) / sizeof(*commands)))
+
+/**
+ * Process a single line from stdin and process the command.
+ *
+ * Returns 0 on successful processing of command, including the
+ * unknown_command output.
+ *
+ * Returns 1 on natural exit due to exist signal of empty line.
+ *
+ * Returns negative value on other catastrophic error.
+ */
+static int process_command(struct repository *repo)
+{
+	static struct strbuf line = STRBUF_INIT;
+	struct string_list tokens = STRING_LIST_INIT_NODUP;
+	const char *command;
+	int version;
+	char *data = NULL;
+	size_t data_len = 0;
+	int res = 0;
+
+	strbuf_getline(&line, stdin);
+
+	if (!line.len)
+		return 1;
+
+	/* Parse out the first two tokens, command and version. */
+	string_list_split_in_place(&tokens, line.buf, " ", 2);
+
+	if (tokens.nr < 2) {
+		res = error(_("expected at least 2 tokens, got %"PRIu32),
+			    (uint32_t)tokens.nr);
+		goto cleanup;
+	}
+
+	command = tokens.items[0].string;
+
+	if (!git_parse_int(tokens.items[1].string, &version)) {
+		res = error(_("unable to parse '%s' to integer"),
+			    tokens.items[1].string);
+		goto cleanup;
+	}
+
+	if (tokens.nr >= 3) {
+		data = tokens.items[2].string;
+		data_len = strlen(tokens.items[2].string);
+	}
+
+	for (size_t i = 0; i < COMMAND_COUNT; i++) {
+		/*
+		 * Run the ith command if we have hit the unknown
+		 * command or if the name and version match.
+		 */
+		if (!commands[i].name[0] ||
+		    (!strcmp(command, commands[i].name) &&
+		     commands[i].version == version)) {
+			res = commands[i].fn(repo, data, data_len);
+			goto cleanup;
+		}
+	}
+
+	BUG(_("scanned to end of command list, including 'unknown_command'"));
+
+cleanup:
+	strbuf_reset(&line);
+	string_list_clear(&tokens, 0);
+	return res;
+}
+
 int cmd_config_batch(int argc,
 		     const char **argv,
 		     const char *prefix,
 		     struct repository *repo)
 {
+	int res = 0;
 	struct option options[] = {
 		OPT_END(),
 	};
@@ -26,5 +153,9 @@ int cmd_config_batch(int argc,
 
 	repo_config(repo, git_default_config, NULL);
 
-	return 0;
+	while (!(res = process_command(repo)));
+
+	if (res == 1)
+		return 0;
+	die(_("an unrecoverable error occurred during command execution"));
 }
diff --git a/t/t1312-config-batch.sh b/t/t1312-config-batch.sh
index f59ba4a0f3..f60ef35e38 100755
--- a/t/t1312-config-batch.sh
+++ b/t/t1312-config-batch.sh
@@ -4,9 +4,22 @@ test_description='Test git config-batch'
 
 . ./test-lib.sh
 
-test_expect_success 'help text' '
-	test_must_fail git config-batch -h >out &&
-	grep usage out
+test_expect_success 'no commands' '
+	echo | git config-batch >out &&
+	test_must_be_empty out
+'
+
+test_expect_success 'unknown_command' '
+	echo unknown_command >expect &&
+	echo "bogus 1 line of tokens" >in &&
+	git config-batch >out <in &&
+	test_cmp expect out
+'
+
+test_expect_success 'failed to parse version' '
+	echo "bogus BAD_VERSION line of tokens" >in &&
+	test_must_fail git config-batch 2>err <in &&
+	test_grep BAD_VERSION err
 '
 
 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 ` Derrick Stolee via GitGitGadget [this message]
2026-02-04 23:26   ` [PATCH 02/11] config-batch: create parse loop and unknown command 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 ` [PATCH 11/11] config-batch: add unset v1 command Derrick Stolee via GitGitGadget
2026-02-05 17:36   ` 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=ecd26a0f1fad5615aea07a388e34f02e9f33b870.1770214803.git.gitgitgadget@gmail.com \
    --to=gitgitgadget@gmail$(echo .)com \
    --cc=git@vger$(echo .)kernel.org \
    --cc=gitster@pobox$(echo .)com \
    --cc=stolee@gmail$(echo .)com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html
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