Skip to content

[RFC] config-batch: a new builtin for tools querying config#2033

Open
derrickstolee wants to merge 11 commits intogitgitgadget:masterfrom
derrickstolee:batched-config
Open

[RFC] config-batch: a new builtin for tools querying config#2033
derrickstolee wants to merge 11 commits intogitgitgadget:masterfrom
derrickstolee:batched-config

Conversation

@derrickstolee
Copy link

@derrickstolee derrickstolee commented Jan 17, 2026

This RFC explores a new git config-batch builtin that allows tools to interact with Git's config data with multiple queries using a single process. This is an orthogonal alternative to the effort to create a stable, linkable config API. Both approaches have different strengths.

My main motivation is the performance of git-credential-manager on Windows platforms as it can call git config get dozens of times. At 150-200ms per execution, that adds up significantly, leading to multiple seconds just to load a credential that already exists. I believe that there are other benefits to having this interface available, but I can't recall any specifics at the moment.

This RFC adds git config-batch with a protocol over stdin/stdout for executing multiple config queries. The implementation has a limited set of potential queries, but also creates a model for compatibility for tools to automatically adapt to different Git versions.

I'm submitting this as an RFC before I've polished all of the details because I want to make sure I'm going down a good direction. Please focus feedback in these questions:

  • Is this a worthwhile feature to add to Git?
  • Is this a reasonable protocol for stdin/stdout?
  • How can we structure the code to make it easier to contribute new commands in the future?
  • This seems like a place where parallel contributions can be made once the baseline is implemented. Is there interest in further contributions to expand the commands?

This RFC adds the following commands over stdin:

  1. help lists the available commands, giving the caller an understanding of what is available in this Git version.
  2. get loads a value for a given key within a certain scope, with optional value patterns.
  3. set assigns a key-value pair in a given scope.
  4. unset removes a key-value pair in a given scope with optional value patterns.

Each command has an associated version, in case we need to expand or alter the functionality in the future. This includes the potential to deprecate and remove certain versions that we no longer want to support, such as replacing set version 1 with a version 2 and making version 1 no longer available. I do hope that we will mostly be able to move with new command names, such as a set-all command including the options for git config set --all ... instead of increasing the version of the set command.

There is a -z option that changes the command interface to use NUL-terminated strings. Two NULs specify a command boundary, which promotes compatibility with a caller that sends an unknown command. However, this means that we cannot specify an empty string as a token within a command unless we add more data. This format uses <N>:<string> to provide the integer <N> which specifies the length of <string>. This is a little cumbersome, but the format is intended for tools, not humans.

I have a test integration with git-credential-manager available [1] for testing. This includes a model for interacting with git config-batch in a compatible way that will respond to certain features not being available:

  1. If git config-batch fails immediately, then all queries are handled by git config.
  2. If git config-batch starts without failure, then the first query is for the help command.
  3. As queries come to the config system, the query is checked against the available commands advertised by git config-batch. If the appropriate command is available, then the query is made in that process. If not, then the query uses the existing git config command.

One thing that I think would be valuable to include is a reload command that signals that the git config-batch process should reload the configset into memory due to config manipulations in other processes, especially while git config-batch doesn't have all capabilities from git config. I'll include that in the first version for review, if this RFC leads to positive support.

[1] git-ecosystem/git-credential-manager#2245

I have a few concerns with this implementation that I'd like to improve before submitting a version for full review. I list them here so you can see the flaws that I already see, but also so you can add to this list:

  • We need a reload command (as mentioned above).
  • The tests need to include a submodule and submodule-level config.
  • When specifying the local scope to the get command, the matched value does not include worktree or submodule config in the same way that git config get --local <key> would.
  • The token-parsing API in this helper is still too complicated to use. I should create parsing tooling similar to the parse-opts API so each command could specify its use of positional values and optional arguments.
  • The use of arg:<arg> to specify an optional argument creates the inability to submit a value that starts with arg:. Consider alternative ways to specify arguments or to specify that the remaining data in the command (including spaces) is a final positional argument.
  • In general, I found myself implementing behavior based on the deprecated forms of git config that use the --get or --unset style arguments instead of git config (set|unset|get) subcommands. It's worth making sure that any references to equivalent git config commands use the new modes.
  • I need to add an --[no-]includes option as a command-line argument that signals whether include sections should be followed. I don't believe this should be specified on a per-command basis, but I'm open to suggestions.
  • I have an early draft of a technical document detailing the plan for this builtin. It has some lists of intended future commands that have not been implemented. This would also be a good place to document any parsing APIs built to help contributors adding to this builtin.

Thanks,
-Stolee

cc: gitster@pobox.com
cc: "brian m. carlson" sandals@crustytoothpaste.net
cc: Phillip Wood phillip.wood123@gmail.com
cc: "Kristoffer Haugsbakk" kristofferhaugsbakk@fastmail.com
cc: Jean-Noël Avila jn.avila@free.fr

@derrickstolee derrickstolee force-pushed the batched-config branch 5 times, most recently from f4ae10d to 931fbf4 Compare January 18, 2026 03:40
@derrickstolee derrickstolee changed the title git-config-batch: a new builtin for tools reading config [RFC] git-config-batch: a new builtin for tools reading config Jan 18, 2026
return 0;

if (d->scope != CONFIG_SCOPE_UNKNOWN &&
d->scope != context->kvi->scope)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note to self: --local will get things from worktree config, right? We should check to see if a function is needed here. --system and --global care about only one layer, but --local is more complicated, I think.

Later changes will document, implement, and test this new builtin. For now,
this serves as the latest example of the minimum boilerplate to introduce a
new builtin.

Recently, we updated the comment in builtin.h about how to create a new
builtin, but failed to mention the required change to meson.build files for
some CI builds to pass. Fix that oversight.

Signed-off-by: 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>
The 'get' command for the 'git config-batch' builtin is the first command
and is currently at version 1. It returns at most one value, the same as
'git config --get <key>' with optional value-based filtering.

The documentation and tests detail the specifics of how to format requests
of this format and how to parse the results.

Future versions could consider multi-valued responses or regex-based key
matching.

For the sake of incremental exploration of the potential in the 'git
config-batch' command, this is the only implementation being presented in
the first patch series.

Future extensions could include a '-z' parameter that uses NUL bytes in the
command and output format to allow for spaces or newlines in the input or
newlines in the output.

Signed-off-by: Derrick Stolee <stolee@gmail.com>
Tools that use the 'git config-batch' tool will want to know which commands
are available in the current Git version. Having a 'help' command assists
greatly to give a clear set of available commands and their versions.

Signed-off-by: Derrick Stolee <stolee@gmail.com>
When using automated tools, it is critical to allow for input/output formats
that include special characters such as spaces and newlines. While the
existing protocol for 'git config-batch' is human-readable and has some
capacity for some spaces in certain positions, it is not available for
spaces in the config key or newlines in the config values.

Add the '-z' option to signal the use of NUL-terminated strings. To
understand where commands end regardless of potential future formats, use
two NUL bytes in a row to terminate a command. To allow for empty string
values, each token is provided in a <length>:<value> format, making "0:"
the empty string value.

Update the existing 'help' and 'get' commands to match this format. Create
helper methods that make it easy to parse and print in both formats
simultaneously.

Signed-off-by: Derrick Stolee <stolee@gmail.com>
This document will be a place that tracks the future directions of the
'git config-batch' builtin. We plan to remove items as they are
implemented in new commands and documented in the builtin documentation.

Signed-off-by: Derrick Stolee <stolee@gmail.com>
Before reusing these concepts in builtin/config-batch.c, extract the
config_location_options struct from builtin/config.c to config.h with
implementation in config.c.

The only modification in this conversion is the use of a repository
parameter instead of the_repository.

Signed-off-by: Derrick Stolee <stolee@gmail.com>
The 'help' and 'get' commands of 'git config-batch' have not needed the
prefix parameter from the builtin entrance point, but an upcoming
command will need it in order to identify the location of the
appropriate config file. Pass it through the appropriate functions and
function pointers.

Signed-off-by: Derrick Stolee <stolee@gmail.com>
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 <stolee@gmail.com>
This new test will be extended in the future to ensure that multiple
commands that execute in order update the configuration state enough to
reflect new written values as we read them in later commands.

Signed-off-by: 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>
@derrickstolee derrickstolee marked this pull request as ready for review February 4, 2026 14:18
@derrickstolee derrickstolee changed the title [RFC] git-config-batch: a new builtin for tools reading config [RFC] config-batch: a new builtin for tools querying config Feb 4, 2026
@derrickstolee
Copy link
Author

/submit

@gitgitgadget
Copy link

gitgitgadget bot commented Feb 4, 2026

Submitted as pull.2033.git.1770214803.gitgitgadget@gmail.com

To fetch this version into FETCH_HEAD:

git fetch https://github.com/gitgitgadget/git/ pr-2033/derrickstolee/batched-config-v1

To fetch this version to local tag pr-2033/derrickstolee/batched-config-v1:

git fetch --no-tags https://github.com/gitgitgadget/git/ tag pr-2033/derrickstolee/batched-config-v1

@gitgitgadget
Copy link

gitgitgadget bot commented Feb 4, 2026

Junio C Hamano wrote on the Git mailing list (how to reply to this email):

"Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes:

> This RFC explores a new git config-batch builtin that allows tools to
> interact with Git's config data with multiple queries using a single
> process. This is an orthogonal alternative to the effort to create a stable,
> linkable config API. Both approaches have different strengths.

Just a few random thoughts before diving into the patches.

> My main motivation is the performance of git-credential-manager on Windows
> platforms as it can call git config get dozens of times. At 150-200ms per
> execution, that adds up significantly, leading to multiple seconds just to
> load a credential that already exists. I believe that there are other
> benefits to having this interface available, but I can't recall any
> specifics at the moment.

So this would be "credential-manager gets started, and instead of
having to spawn 'git config' many times, spawn a single instance of
'git config --batch' and talk with it".  Would it be beneficial to
further think about a long-running 'git config --server' that can be
contacted by a credential-manager (or other processes) whose lifetime
is totally independent, possibly over local transport mechanisms
like named pipes, or is it a key to keep the mechanism and design
simple to limit the number of customer this service supports to only
one at a time and we would prefer to keep it that way?

> One thing that I think would be valuable to include is a reload command that
> signals that the git config-batch process should reload the configset into
> memory due to config manipulations in other processes, especially while git
> config-batch doesn't have all capabilities from git config. I'll include
> that in the first version for review, if this RFC leads to positive support.

Can "git config --batch" write/modify configuration, and if so, when
does it make its modification available to the outside world?  Would
we have a "flush" command, or it would pretty much be immediate?

Can we do without an explicit "reload" command by noticing when
the configuration files are updated and automatically reload?

I am trying to figure out how more than one "git config --batch"
processes can coordinate with each other with minimum overhead.  It
is not a goal to have multiple such processes, but it would be a
goal to support multiple clients each of which would benefit from
having access to the configuration data service (which is why I
brought up a single and shared long-running daemon as a possible
alternative earlier).

@@ -44,6 +44,7 @@
/git-commit-graph
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Junio C Hamano wrote on the Git mailing list (how to reply to this email):

"Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Derrick Stolee <stolee@gmail.com>
>
> Later changes will document, implement, and test this new builtin. For now,
> this serves as the latest example of the minimum boilerplate to introduce a
> new builtin.
>
> Recently, we updated the comment in builtin.h about how to create a new
> builtin, but failed to mention the required change to meson.build files for
> some CI builds to pass. Fix that oversight.
>
> Signed-off-by: Derrick Stolee <stolee@gmail.com>
> ---

We have had a bad reputation for having too many commands; would it
be better to present it as a new mode of existing "git config"
command at the end-user level, I wonder?

Also after reading patches for a few early steps, I do not quite see
"batch"-ness in this protocol; it is strictly "a single request is
met with a single response".

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Derrick Stolee wrote on the Git mailing list (how to reply to this email):

On 2/4/2026 6:23 PM, Junio C Hamano wrote:
> "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes:
> 
>> From: Derrick Stolee <stolee@gmail.com>
>>
>> Later changes will document, implement, and test this new builtin. For now,
>> this serves as the latest example of the minimum boilerplate to introduce a
>> new builtin.
>>
>> Recently, we updated the comment in builtin.h about how to create a new
>> builtin, but failed to mention the required change to meson.build files for
>> some CI builds to pass. Fix that oversight.
>>
>> Signed-off-by: Derrick Stolee <stolee@gmail.com>
>> ---
> 
> We have had a bad reputation for having too many commands; would it
> be better to present it as a new mode of existing "git config"
> command at the end-user level, I wonder?

Interesting thought. I think we also have a bad reputation of commands
that are overloaded with too many purposes.

In this case, though, I do think that the modern 'git config <subcommand>'
model presents some clear boundaries for how the command should behave
with the 'batch' (or 'server') subcommand. Grouping all config-related
operations in the same builtin may be ideal. 

> Also after reading patches for a few early steps, I do not quite see
> "batch"-ness in this protocol; it is strictly "a single request is
> met with a single response".

The batch-ness is that multiple requests can eventually go to the same
process. The client could collect multiple commands in a batch and send
them all without processing the responses one-by-one. This is how it works
in the tests: a single input file is prepared and all responses are
scanned after-the-fact.

The back-and-forth mechanism is how the git-credential-manager tool would
use it, because it dynamically explores certain config keys. For example:
it checks the deepest possible URL for a specific key then peels away the
last segment of the URL to see if there is a directory-prefix match in a
key. (This is the main reason that there are so many requests in this
application.)

I believe this is similar to how 'git cat-file --batch' or 'git cat-file
--batch-check' work, which was my inspiration for this word. If we regret
those names, then I'm happy to move towards a better name.

Thanks,
-Stolee

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Kristoffer Haugsbakk" wrote on the Git mailing list (how to reply to this email):

On Thu, Feb 5, 2026, at 15:17, Derrick Stolee wrote:
>>>[snip]
>>
>> We have had a bad reputation for having too many commands; would it
>> be better to present it as a new mode of existing "git config"
>> command at the end-user level, I wonder?
>
> Interesting thought. I think we also have a bad reputation of commands
> that are overloaded with too many purposes.

I had a response to that in my head...

> In this case, though, I do think that the modern 'git config <subcommand>'
> model presents some clear boundaries for how the command should behave
> with the 'batch' (or 'server') subcommand. Grouping all config-related
> operations in the same builtin may be ideal.

Which turned out to be exactly about a subcommand. :)

I find the modern subcommand model very easy to navigate. And with much
less downsides compared to having dozens of options for one command (or: one
particular subcommand to git(1)).

>
>> Also after reading patches for a few early steps, I do not quite see
>> "batch"-ness in this protocol; it is strictly "a single request is
>> met with a single response".
>
> The batch-ness is that multiple requests can eventually go to the same
> process. The client could collect multiple commands in a batch and send
> them all without processing the responses one-by-one. This is how it works
> in the tests: a single input file is prepared and all responses are
> scanned after-the-fact.

As a user that makes sense given the existing `--batch` and
`--stdin` options.

> The back-and-forth mechanism is how the git-credential-manager tool would
> use it, because it dynamically explores certain config keys. For example:
> it checks the deepest possible URL for a specific key then peels away the
> last segment of the URL to see if there is a directory-prefix match in a
> key. (This is the main reason that there are so many requests in this
> application.)
>
> I believe this is similar to how 'git cat-file --batch' or 'git cat-file
> --batch-check' work, which was my inspiration for this word. If we regret
> those names, then I'm happy to move towards a better name.
>
> Thanks,
> -Stolee

--------
[verse]
'git config-batch' <options>

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Junio C Hamano wrote on the Git mailing list (how to reply to this email):

"Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes:

> +static struct command commands[] = {
> +	/* unknown_command must be last. */
> +	{
> +		.name = "",
> +		.fn   = unknown_command,
> +	},
> +};

A useful trick is to deliberately omit the trailing comma after the
element that MUST be last.  You did that for the __NR enum element
in a later step.

> +#define COMMAND_COUNT ((size_t)(sizeof(commands) / sizeof(*commands)))

Isn't this ARRAY_SIZE(commands)?


> +	while (!(res = process_command(repo)));

Please write an empty statement on its own line, i.e.

	while (!(res = process_command(repo)))
		;

@gitgitgadget
Copy link

gitgitgadget bot commented Feb 5, 2026

"brian m. carlson" wrote on the Git mailing list (how to reply to this email):

On 2026-02-04 at 14:19:52, Derrick Stolee via GitGitGadget wrote:
> This RFC explores a new git config-batch builtin that allows tools to
> interact with Git's config data with multiple queries using a single
> process. This is an orthogonal alternative to the effort to create a stable,
> linkable config API. Both approaches have different strengths.
> 
> My main motivation is the performance of git-credential-manager on Windows
> platforms as it can call git config get dozens of times. At 150-200ms per
> execution, that adds up significantly, leading to multiple seconds just to
> load a credential that already exists. I believe that there are other
> benefits to having this interface available, but I can't recall any
> specifics at the moment.
> 
> This RFC adds git config-batch with a protocol over stdin/stdout for
> executing multiple config queries. The implementation has a limited set of
> potential queries, but also creates a model for compatibility for tools to
> automatically adapt to different Git versions.
> 
> I'm submitting this as an RFC before I've polished all of the details
> because I want to make sure I'm going down a good direction. Please focus
> feedback in these questions:
> 
>  * Is this a worthwhile feature to add to Git?

Git LFS has the same needs, but I believe it can use `git config -l -z`
to do that and parse the config options itself.  If this is just config
fetching, I'm not sure of the additional utility that such a feature
would add.  If that interface _almost_ meets your needs, could we add
functionality there instead of a new interface?

If you need to set many keys, I'm curious as to why that is.
-- 
brian m. carlson (they/them)
Toronto, Ontario, CA

@gitgitgadget
Copy link

gitgitgadget bot commented Feb 5, 2026

User "brian m. carlson" <sandals@crustytoothpaste.net> has been added to the cc: list.

@gitgitgadget
Copy link

gitgitgadget bot commented Feb 5, 2026

Derrick Stolee wrote on the Git mailing list (how to reply to this email):

On 2/4/2026 7:04 PM, brian m. carlson wrote:
> On 2026-02-04 at 14:19:52, Derrick Stolee via GitGitGadget wrote:
>> This RFC explores a new git config-batch builtin that allows tools to
>> interact with Git's config data with multiple queries using a single
>> process. This is an orthogonal alternative to the effort to create a stable,
>> linkable config API. Both approaches have different strengths.
>>
>> My main motivation is the performance of git-credential-manager on Windows
>> platforms as it can call git config get dozens of times. At 150-200ms per
>> execution, that adds up significantly, leading to multiple seconds just to
>> load a credential that already exists. I believe that there are other
>> benefits to having this interface available, but I can't recall any
>> specifics at the moment.

>>  * Is this a worthwhile feature to add to Git?
> 
> Git LFS has the same needs, but I believe it can use `git config -l -z`
> to do that and parse the config options itself.  If this is just config
> fetching, I'm not sure of the additional utility that such a feature
> would add.  If that interface _almost_ meets your needs, could we add
> functionality there instead of a new interface?

This is a good suggestion to look into as a potentially-easier solution.

There may be some work required on the consumer to interpret multiple
values and the right inheritance rules. This is relatively minor
compared to attempting a full parser with complicated 'includeIf'
logic.
 > If you need to set many keys, I'm curious as to why that is.

I know that the credential manager does more than just query the config,
but also sets and unsets config. The full interface is here [1]. However,
the performance-critical parts may not require mutating configuration
values, and hence such a 

[1] https://github.com/git-ecosystem/git-credential-manager/blob/main/src/shared/Core/GitConfiguration.cs#L31

Thanks for the pointer to git-lfs as a similar use case. I see that it
has a way to get the full list of config values [2] with '-l' (but not
'-z'). It also has methods for getting values on a per-key (or even
per-file) basis. I have not tracked the uses of config code into its
consumers to know how often one is used over the other.

[2] https://github.com/git-lfs/git-lfs/blob/bb65882304a655ffa8abf2be6922e53ff18af5a5/git/config.go#L208

Thanks,
-Stolee

@gitgitgadget
Copy link

gitgitgadget bot commented Feb 5, 2026

Derrick Stolee wrote on the Git mailing list (how to reply to this email):

On 2/4/2026 6:04 PM, Junio C Hamano wrote:
> "Derrick Stolee via GitGitGadget" <gitgitgadget@gmail.com> writes:
> 
>> This RFC explores a new git config-batch builtin that allows tools to
>> interact with Git's config data with multiple queries using a single
>> process. This is an orthogonal alternative to the effort to create a stable,
>> linkable config API. Both approaches have different strengths.
> 
> Just a few random thoughts before diving into the patches.
> 
>> My main motivation is the performance of git-credential-manager on Windows
>> platforms as it can call git config get dozens of times. At 150-200ms per
>> execution, that adds up significantly, leading to multiple seconds just to
>> load a credential that already exists. I believe that there are other
>> benefits to having this interface available, but I can't recall any
>> specifics at the moment.
> 
> So this would be "credential-manager gets started, and instead of
> having to spawn 'git config' many times, spawn a single instance of
> 'git config --batch' and talk with it".  Would it be beneficial to
> further think about a long-running 'git config --server' that can be
> contacted by a credential-manager (or other processes) whose lifetime
> is totally independent, possibly over local transport mechanisms
> like named pipes, or is it a key to keep the mechanism and design
> simple to limit the number of customer this service supports to only
> one at a time and we would prefer to keep it that way?

I could imagine a world where we have this approach, similar to the
fsmonitor server. I should do more research and refresh my memory on that
I/O model, if only to potentially reuse some of the parsing logic.

But this would be an interesting potential direction, saving the process
start-up time entirely.

The one big difficulty that I see is that the config will need to be
refreshed proactively by the server, potentially by watching the config
files themselves (including any files included along the way) and also any
changes to repo state, such as the current branch. Any repo state that
could impact the 'includeIf' logic would need to be checked carefully. 

>> One thing that I think would be valuable to include is a reload command that
>> signals that the git config-batch process should reload the configset into
>> memory due to config manipulations in other processes, especially while git
>> config-batch doesn't have all capabilities from git config. I'll include
>> that in the first version for review, if this RFC leads to positive support.
> 
> Can "git config --batch" write/modify configuration, and if so, when
> does it make its modification available to the outside world?  Would
> we have a "flush" command, or it would pretty much be immediate?

The 'set' command in this series calls methods that reach into
repo_config_set_multivar_in_file_gently() which updates the config file as
part of that call, including using the .lock file technique to avoid
concurrent writes. Looking closely, it appears we do the right thing by
parsing the existing file so we only update the new values while allowing
any concurrent writes to the file to be respected, even if they disagree
with our current view of the config.

Such assignments also update our in-memory view _of those keys_ but it may
be a good time to automatically refresh the entire set of config values.

> Can we do without an explicit "reload" command by noticing when
> the configuration files are updated and automatically reload?

This would be an interesting approach, especially for the server concept.

> I am trying to figure out how more than one "git config --batch"
> processes can coordinate with each other with minimum overhead.  It
> is not a goal to have multiple such processes, but it would be a
> goal to support multiple clients each of which would benefit from
> having access to the configuration data service (which is why I
> brought up a single and shared long-running daemon as a possible
> alternative earlier).

You're right to bring up these concerns. While 'git config-batch' is
intended to be relatively short-lived, users could build tools that keep
it alive for a long time. Thus, it is important to consider these
automatically-refreshing scenarios. And if we are automatically
refreshing, then should we instead consider a client/server model?

I have some things to explore at the highest levels. I will likely start
by exploring brian's 'git config -l -z' suggestion to see if that solves
the short-term need. But I will consider these other ideas to see where
they lead in terms of complexity and potential applications.

Thanks,
-Stolee

@gitgitgadget
Copy link

gitgitgadget bot commented Feb 5, 2026

Phillip Wood wrote on the Git mailing list (how to reply to this email):

Hi Stolee

On 04/02/2026 14:19, Derrick Stolee via GitGitGadget wrote:
> This RFC explores a new git config-batch builtin that allows tools to
> interact with Git's config data with multiple queries using a single
> process. This is an orthogonal alternative to the effort to create a stable,
> linkable config API. Both approaches have different strengths.
> > My main motivation is the performance of git-credential-manager on Windows
> platforms as it can call git config get dozens of times. At 150-200ms per
> execution, that adds up significantly, leading to multiple seconds just to
> load a credential that already exists. I believe that there are other
> benefits to having this interface available, but I can't recall any
> specifics at the moment.

It would be helpful to explain what the advantage of this new command is over using "git config --list -z" or "git config --get-regex '^(some|section|names)\.' -z". I've found those to be effective in programs that read several config keys. Elsewhere brian has mentioned that git-lfs does something similar and I believe git-filter-repo uses "git config --list -z" as well.

One potential advantage would be if this command supported specifying the type of the value. When using "git config --list" it is a pain to have to normalize boolean values and parse color descriptions into terminal escape codes.

Being able to set multiple keys at once would also be an advantage if there is a convincing use case for it.

> This RFC adds git config-batch with a protocol over stdin/stdout for
> executing multiple config queries. The implementation has a limited set of
> potential queries, but also creates a model for compatibility for tools to
> automatically adapt to different Git versions.
> > I'm submitting this as an RFC before I've polished all of the details
> because I want to make sure I'm going down a good direction. Please focus
> feedback in these questions:
> >   * Is this a worthwhile feature to add to Git?

Possibly, if there are clear benefits over "git config --list". I'm not sure it needs to be a separate command though - I agree with Junio that it would be more discoverable if this was a subcommand of "git config"

>   * Is this a reasonable protocol for stdin/stdout?

The protocol sounds quite complicated with capability queries and versioning. At the same time it looks like the line oriented version does not support keys that contain spaces, values that contain newlines or retrieving settings from a file whose path contains a newline. I think it might be better to have a single protocol variant based using NUL delimiters like "git merge-tree --stdin" and "git diff-pairs". Those commands use a simple NUL terminated protocol without the need to specify the length of the input.

> Each command has an associated version, in case we need to expand or alter
> the functionality in the future. This includes the potential to deprecate
> and remove certain versions that we no longer want to support, such as
> replacing set version 1 with a version 2 and making version 1 no longer
> available. I do hope that we will mostly be able to move with new command
> names, such as a set-all command including the options for git config set
> --all ... instead of increasing the version of the set command.

It's good that you're thinking about future functionality but I wonder if we really need to specify the version on a per command basis rather than as a command line option or simply adding commands like "get-v2".

> There is a -z option that changes the command interface to use
> NUL-terminated strings. Two NULs specify a command boundary, which promotes
> compatibility with a caller that sends an unknown command.

That also allows optional fields such as the file to read the config from or the value to match to be NUL delimited while making the end of a record unambiguous.

> However, this
> means that we cannot specify an empty string as a token within a command
> unless we add more data. Is there a need to do that? Can we require key=value pairs or possibly a fixed number of positional parameters if there is a chance the value will be empty?

> This format uses <N>:<string> to provide the
> integer <N> which specifies the length of <string>. This is a little
> cumbersome, but the format is intended for tools, not humans.

It does seem cumbersome. I can see that it might be helpful to have the length of the complete query and response to avoid deadlocks when reading and writing but I'm not sure requiring the length of each field is helpful.

> I have a test integration with git-credential-manager available [1] for
> testing. This includes a model for interacting with git config-batch in a
> compatible way that will respond to certain features not being available:
> >   1. If git config-batch fails immediately, then all queries are handled by
>      git config.
>   2. If git config-batch starts without failure, then the first query is for
>      the help command.
>   3. As queries come to the config system, the query is checked against the
>      available commands advertised by git config-batch. If the appropriate
>      command is available, then the query is made in that process. If not,
>      then the query uses the existing git config command.

This seems like quite a lot of effort just to check a few config settings.

> I have a few concerns with this implementation that I'd like to improve
> before submitting a version for full review. I list them here so you can see
> the flaws that I already see, but also so you can add to this list:
> >   * The use of arg:<arg> to specify an optional argument creates the
>     inability to submit a value that starts with arg:. Consider alternative
>     ways to specify arguments or to specify that the remaining data in the
>     command (including spaces) is a final positional argument.

The protocol should be unambiguous. Requiring key=value pairs for all fields would be one way to achieve that

	set key=my.key scope=global value-regex=my-regex value=new-value

or we could force all optional fields to come first and count the number of fields to figure out whether any optional fields have been passed

	set value-regex=my-regex global my.key new-value

(I've used spaces above to delimit fields but we'd want to use NUL in the protocol)

Thanks

Phillip

>   * In general, I found myself implementing behavior based on the deprecated
>     forms of git config that use the --get or --unset style arguments instead
>     of git config (set|unset|get) subcommands. It's worth making sure that
>     any references to equivalent git config commands use the new modes.
>   * I need to add an --[no-]includes option as a command-line argument that
>     signals whether include sections should be followed. I don't believe this
>     should be specified on a per-command basis, but I'm open to suggestions.
>   * I have an early draft of a technical document detailing the plan for this
>     builtin. It has some lists of intended future commands that have not been
>     implemented. This would also be a good place to document any parsing APIs
>     built to help contributors adding to this builtin.
> > Thanks, -Stolee
> > Derrick Stolee (11):
>    config-batch: basic boilerplate of new builtin
>    config-batch: create parse loop and unknown command
>    config-batch: implement get v1
>    config-batch: create 'help' command
>    config-batch: add NUL-terminated I/O format
>    docs: add design doc for config-batch
>    config: extract location structs from builtin
>    config-batch: pass prefix through commands
>    config-batch: add 'set' v1 command
>    t1312: create read/write test
>    config-batch: add unset v1 command
> >   .gitignore                                |   1 +
>   Documentation/git-config-batch.adoc       | 214 ++++++
>   Documentation/meson.build                 |   1 +
>   Documentation/technical/config-batch.adoc |  70 ++
>   Makefile                                  |   1 +
>   builtin.h                                 |   7 +
>   builtin/config-batch.c                    | 772 ++++++++++++++++++++++
>   builtin/config.c                          | 117 +---
>   command-list.txt                          |   1 +
>   config.c                                  | 116 ++++
>   config.h                                  |  26 +
>   git.c                                     |   1 +
>   meson.build                               |   1 +
>   t/meson.build                             |   1 +
>   t/t1312-config-batch.sh                   | 372 +++++++++++
>   15 files changed, 1592 insertions(+), 109 deletions(-)
>   create mode 100644 Documentation/git-config-batch.adoc
>   create mode 100644 Documentation/technical/config-batch.adoc
>   create mode 100644 builtin/config-batch.c
>   create mode 100755 t/t1312-config-batch.sh
> > > base-commit: 83a69f19359e6d9bc980563caca38b2b5729808c
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2033%2Fderrickstolee%2Fbatched-config-v1
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2033/derrickstolee/batched-config-v1
> Pull-Request: https://github.com/gitgitgadget/git/pull/2033

@gitgitgadget
Copy link

gitgitgadget bot commented Feb 5, 2026

User Phillip Wood <phillip.wood123@gmail.com> has been added to the cc: list.

@gitgitgadget
Copy link

gitgitgadget bot commented Feb 5, 2026

"Kristoffer Haugsbakk" wrote on the Git mailing list (how to reply to this email):

On Wed, Feb 4, 2026, at 15:19, Derrick Stolee via GitGitGadget wrote:
> This RFC explores a new git config-batch builtin that allows tools to
> interact with Git's config data with multiple queries using a single
> process. This is an orthogonal alternative to the effort to create a stable,
> linkable config API. Both approaches have different strengths.
>[snip]

This sounds incredibly useful. Thanks!

@gitgitgadget
Copy link

gitgitgadget bot commented Feb 5, 2026

User "Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> has been added to the cc: list.

get 1 missing <key> [<value-pattern>|<value>]
------------
+
where `<value-pattern>` or `<value>` is only supplied if provided in
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Kristoffer Haugsbakk" wrote on the Git mailing list (how to reply to this email):

On Wed, Feb 4, 2026, at 15:19, Derrick Stolee via GitGitGadget wrote:
>[snip]
> +git-config-batch(1)
> +===================
> +
> +NAME
> +----
> +git-config-batch - Get and set options using machine-parseable
> interface
> +
> +
> +SYNOPSIS
> +--------
> +[verse]

There’s work lead by Jean-Noël Avila to use `[synopsis]` instead of
`[verse]`.[1] Would it make sense to start off with that?

† 1: E.g. acffc5e9 (doc: convert git-remote to synopsis style, 2025-12-20)

> +'git config-batch' <options>
> +
> +DESCRIPTION
> +-----------
>[snip]

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Kristoffer Haugsbakk" wrote on the Git mailing list (how to reply to this email):

On Thu, Feb 5, 2026, at 18:21, Kristoffer Haugsbakk wrote:
> On Wed, Feb 4, 2026, at 15:19, Derrick Stolee via GitGitGadget wrote:
>>[snip]
>> +git-config-batch(1)
>> +===================
>> +
>> +NAME
>> +----
>> +git-config-batch - Get and set options using machine-parseable
>> interface
>> +
>> +
>> +SYNOPSIS
>> +--------
>> +[verse]
>
> There’s work lead by Jean-Noël Avila to use `[synopsis]` instead of
> `[verse]`.[1] Would it make sense to start off with that?
>
> † 1: E.g. acffc5e9 (doc: convert git-remote to synopsis style, 2025-12-20)
>
>> +'git config-batch' <options>
>> +
>> +DESCRIPTION
>> +-----------
>>[snip]

(sorry for replying to the wrong email)

@@ -44,6 +44,7 @@
/git-commit-graph
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Kristoffer Haugsbakk" wrote on the Git mailing list (how to reply to this email):

On Wed, Feb 4, 2026, at 15:19, Derrick Stolee via GitGitGadget wrote:
>[snip]
> +git-config-batch(1)
> +===================
> +
> +NAME
> +----
> +git-config-batch - Get and set options using machine-parseable
> interface
> +
> +
> +SYNOPSIS
> +--------
> +[verse]

There’s work lead by Jean-Noël Avila to use `[synopsis]` instead of
`[verse]`.[1] Would it make sense to start off with that?

† 1: E.g. acffc5e9 (doc: convert git-remote to synopsis style, 2025-12-20)

> +'git config-batch' <options>
> +
> +DESCRIPTION
> +-----------
>[snip]

--------
[verse]
'git config-batch' <options>

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Kristoffer Haugsbakk" wrote on the Git mailing list (how to reply to this email):

On Wed, Feb 4, 2026, at 15:19, Derrick Stolee via GitGitGadget wrote:
>[snip]
>  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`.

I really like that the doc itself motivates the command. Many man pages
on git(1) just tells you what it does as if you would already know why
you need it.

> +
>[snip]

+
------------
set 1 success <scope> <key> <value>
set 1 failed <scope> <key> <value>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Kristoffer Haugsbakk" wrote on the Git mailing list (how to reply to this email):

On Wed, Feb 4, 2026, at 15:20, Derrick Stolee via GitGitGadget wrote:
> 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.

`git config --unset` is deprecated in favor of `git config unset`.

>
> 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>`

Same as above.

> +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;

I think this should get braces since it has many lines? Or maybe
multi-line conditionals are excempt.

> +
> +	if (!parse_token(&data, &data_len, &key, &err) || err)
> +		goto parse_error;
>[snip]

@@ -0,0 +1,70 @@
Git Config-Batch Design Notes
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Kristoffer Haugsbakk" wrote on the Git mailing list (how to reply to this email):

On Wed, Feb 4, 2026, at 15:19, Derrick Stolee via GitGitGadget wrote:
>[snip]
> +Current commands
> +----------------
> +
> +See the documentation in linkgit::config-batch[1] for the latest set of

s/linkgit::config-batch[1]/linkgit:git-config-batch[1]/

> +available commands and their protocols.
> +
> +Future commands
> +---------------
> +
> +The following modes of `git config` are not currently available as
> commands
> +in `git config-batch`, but are planned for future integration:
> +
> +`git config list [--<scope>]`::
> +	Getting all values, regardless of config key, would require a
> +	multi-valued output similar to the `help` command. This tool will
> +	likely assume advanced options such as `--show-origin`.

What does it mean to assume options?

> +
> +`git config set [--<scope>] <key> <value>`::
> +	It will be desirable to set a config key at a given scope as a
> +	single value, replacing the current value at that scope, if it
> +	exists and is a single value. A `set` command could satisfy this
> +	purpose.
> +
> +`git config set --all [<value-pattern>|--fixed-value=<fixedvalue>]
> <key> <value>`::
> +	When replacing multiple values, it may be necessary to have a
> different
> +	output describing the places those values were set, so it may need to
> +	be implemented via a `set-all` command to differentiate from a `set`
> +	command.
> +
> +`git config unset <key>`::
> +
> +`git config unset --all [<value-pattern>|--fixed-value=<fixedvalue>]
> <key>`::
> +
> +`git config get --all --rexexp <key-pattern> [<value-options>]`::
> +
> +`--replace-all` option::
> +
> +`--type=<type>` option::
> --
> gitgitgadget

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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Kristoffer Haugsbakk" wrote on the Git mailing list (how to reply to this email):

On Wed, Feb 4, 2026, at 15:19, Derrick Stolee via GitGitGadget wrote:
> From: Derrick Stolee <stolee@gmail.com>
>[snip]
> +OPTIONS
> +-------
> +
> +`-z`::
> +	If specified, then use the NUL-terminated input and output

It seems to me that using the imperative mood for options might be
preferred now. Like:

    Use NUL-terminated input and output...

See: https://lore.kernel.org/git/bcd6fcd1190fe21c667b5253a4a33b833e658609.1769462744.git.gitgitgadget@gmail.com/

>[snip]
> -	line provides the count of possible commands via `help count <N>`.
> -	The next `<N>` lines are of the form `help <command> <version>`
> +	line provides the count of possible commands via `help 1 count <N>`.
> +	The next `<N>` lines are of the form `help 1 <command> <version>`
>  	to state that this Git version supports that `<command>` at
>  	version `<version>`. Note that the same command may have multiple
>  	available versions.
>  +
> -Here is the currentl output of the help text at the latest version:
> +Here is the current output of the help text at the latest version:

Innocent intra-series typofix.

>  +
>  ------------
>  help 1 count 2
> @@ -102,6 +111,48 @@ get 1 missing <key> [<value-pattern>|<value>]
>  where `<value-pattern>` or `<value>` is only supplied if provided in
>  the command.
>
> +NUL-Terminated Format
> +~~~~~~~~~~~~~~~~~~~~~
> +
> +When `-z` is given, the protocol changes in some structural ways.

It might flow better with “Option `-z` changes the protocol...” ?

I don’t know how usual it is to say “Option <x>”.

>[snip]
> +static void print_word(const char *word, int start)
> +{
> +	if (zformat) {
> +		printf("%"PRIu32":%s", (uint32_t)strlen(word), word);
> +		fputc(0, stdout);
> +	} else if (start)

All of the arms should get braces here.

> +		printf("%s", word);
> +	else
> +		printf(" %s", word);
> +}
> +
>[snip]

get 1 missing <key> [<value-pattern>|<value>]
------------
+
where `<value-pattern>` or `<value>` is only supplied if provided in
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Kristoffer Haugsbakk" wrote on the Git mailing list (how to reply to this email):

On Wed, Feb 4, 2026, at 15:20, Derrick Stolee via GitGitGadget wrote:
>[snip]
> +`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>

`--set` doesn’t exist. I think you meant `set`.

>[snip]
> +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;
> +	}

Is there support for a user-provided file? (`git config --file=...`)

> +
> +	return 0;
> +}
> +
>[snip]

@@ -44,6 +44,7 @@
/git-commit-graph
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jean-Noël Avila wrote on the Git mailing list (how to reply to this email):

Le 04/02/2026 à 15:19, Derrick Stolee via GitGitGadget a écrit :
> From: Derrick Stolee <stolee@gmail.com>
> 
> Later changes will document, implement, and test this new builtin. For now,
> this serves as the latest example of the minimum boilerplate to introduce a
> new builtin.
> 
> Recently, we updated the comment in builtin.h about how to create a new
> builtin, but failed to mention the required change to meson.build files for
> some CI builds to pass. Fix that oversight.
> 
> Signed-off-by: Derrick Stolee <stolee@gmail.com>
> ---
>  .gitignore                          |  1 +
>  Documentation/git-config-batch.adoc | 24 +++++++++++++++++++++++
>  Documentation/meson.build           |  1 +
>  Makefile                            |  1 +
>  builtin.h                           |  7 +++++++
>  builtin/config-batch.c              | 30 +++++++++++++++++++++++++++++
>  command-list.txt                    |  1 +
>  git.c                               |  1 +
>  meson.build                         |  1 +
>  t/meson.build                       |  1 +
>  t/t1312-config-batch.sh             | 12 ++++++++++++
>  11 files changed, 80 insertions(+)
>  create mode 100644 Documentation/git-config-batch.adoc
>  create mode 100644 builtin/config-batch.c
>  create mode 100755 t/t1312-config-batch.sh
> 
> diff --git a/.gitignore b/.gitignore
> index 78a45cb5be..42640b5e24 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -44,6 +44,7 @@
>  /git-commit-graph
>  /git-commit-tree
>  /git-config
> +/git-config-batch
>  /git-count-objects
>  /git-credential
>  /git-credential-cache
> diff --git a/Documentation/git-config-batch.adoc b/Documentation/git-config-batch.adoc
> new file mode 100644
> index 0000000000..dfa0bd83e2
> --- /dev/null
> +++ b/Documentation/git-config-batch.adoc
> @@ -0,0 +1,24 @@
> +git-config-batch(1)
> +===================
> +
> +NAME
> +----
> +git-config-batch - Get and set options using machine-parseable interface
> +
> +
> +SYNOPSIS
> +--------
> +[verse]
> +'git config-batch' <options>

For this new manual page, please use the synopsis style:

[synopsis]
git config-batch <options>

Thanks

@gitgitgadget
Copy link

gitgitgadget bot commented Feb 6, 2026

User Jean-Noël Avila <jn.avila@free.fr> has been added to the cc: list.

--------
[verse]
'git config-batch' <options>

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jean-Noël Avila wrote on the Git mailing list (how to reply to this email):

Le 04/02/2026 à 15:19, Derrick Stolee via GitGitGadget a écrit :
> 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

Characters are typefaced as placeholders: _LF_

> +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> +```
>  
This is Markdown. For Asciidoc, use code block:

----
unknown_command LF
----




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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jean-Noël Avila wrote on the Git mailing list (how to reply to this email):

Le 04/02/2026 à 15:19, Derrick Stolee via GitGitGadget a écrit :
> From: Derrick Stolee <stolee@gmail.com>
> 
> The 'get' command for the 'git config-batch' builtin is the first command
> and is currently at version 1. It returns at most one value, the same as
> 'git config --get <key>' with optional value-based filtering.
> 
> The documentation and tests detail the specifics of how to format requests
> of this format and how to parse the results.
> 
> Future versions could consider multi-valued responses or regex-based key
> matching.
> 
> For the sake of incremental exploration of the potential in the 'git
> config-batch' command, this is the only implementation being presented in
> the first patch series.
> 
> Future extensions could include a '-z' parameter that uses NUL bytes in the
> command and output format to allow for spaces or newlines in the input or
> newlines in the output.
> 
> Signed-off-by: Derrick Stolee <stolee@gmail.com>
> ---
>  Documentation/git-config-batch.adoc |  53 +++++-
>  builtin/config-batch.c              | 251 +++++++++++++++++++++++++++-
>  config.h                            |   3 +
>  t/t1312-config-batch.sh             | 101 +++++++++++
>  4 files changed, 405 insertions(+), 3 deletions(-)
> 
> diff --git a/Documentation/git-config-batch.adoc b/Documentation/git-config-batch.adoc
> index 9ca04b0c1e..31dd42f481 100644
> --- a/Documentation/git-config-batch.adoc
> +++ b/Documentation/git-config-batch.adoc
> @@ -32,9 +32,58 @@ 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
> -```
> +------------
> +

OK, the change to Asciidoc code block is done here. Would it be possible
to push it up at the introduction of these lines?

> +These are the commands that are currently understood:
> +
> +`get` version 1::
> +	The `get` command searches the config key-value pairs within a
> +	given `<scope>` for values that match the fixed `<key>` and

The rendering of these is correct due to the synopsis formatter, but we
usually prefer to use the direct formatting for placeholders: _<scope>_,
_<key>_,…

> +	filters the resulting value based on an optional `<value-filter>`.
> +	This can either be a regex or a fixed value. The command format
> +	is one of the following formats:
> ++
> +------------
> +get 1 <scope> <key>
> +get 1 <scope> <key> arg:regex <value-pattern>
> +get 1 <scope> <key> arg:fixed-value <value>
> +------------
> ++

If you are using synopsis style in the block, with the upcoming change
of synopsis style block[1], you can format it:

[synopsis]
------------
get 1 <scope> <key>
get 1 <scope> <key> arg:regex <value-pattern>
get 1 <scope> <key> arg:fixed-value <value>
------------

> +The `<scope>` value can be one of `inherited`, `system`, `global`,
> +`local`, `worktree`, `submodule`, or `command`. If `inherited`, then all
> +config key-value pairs will be considered regardless of scope. Otherwise,
> +only the given scope will be considered.
> ++
> +If no optional arguments are given, then the value will not be filtered
> +by any pattern matching. If `arg:regex` is specified, then the rest of
> +the line is considered a single string, `<value-pattern>`, and is
> +interpreted as a regular expression for matching against stored values,
> +similar to specifying a value to `get config --get <key> "<value-pattern>"`.
> +If `arg:fixed-value` is specified, then the rest of the line is
> +considered a single string, `<value>`, and is checked for an exact
> +match against the key-value pairs, simmilar to `git config --get <key>

similar

> +--fixed-value "<value>"`.
> ++

Here I would use a sub definition list for each matching type, instead
of long running description paragraph.

optional arguments can be specified:

no optional arguments;;
the value will not be filteredby any pattern matching.
`arg:regex <value-pattern>`;;
`<value-pattern>` is interpreted as a regular expression for matching
against stored values, similar to specifying a value to `get config
--get <key> "<value-pattern>"`.
`arg:fixed-value <value>`;;
`<value>` is checked for an exact match against the key-value pairs,
similar to `git config --get <key>`.

> +At mmost one key-value pair is returned, that being the last key-value

At most

> +pair in the standard config order by scope and sequence within each scope.
> ++
> +If a key-value pair is found, then the following output is given:
> ++
> +------------
> +get 1 found <key> <scope> <value>
> +------------
> ++
> +If no matching key-value pair is found, then the following output is
> +given:
> ++
> +------------
> +get 1 missing <key> [<value-pattern>|<value>]
> +------------
> ++

Please also apply synopsis block style.

> +where `<value-pattern>` or `<value>` is only supplied if provided in
> +the command.
>  
>  SEE ALSO
>  --------

[1]:
https://lore.kernel.org/git/6a2b94e720862fa07fe9463ebf7f7beaa9a1ccd4.1770351146.git.gitgitgadget@gmail.com/T/#u

------------
unknown_command LF
------------

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jean-Noël Avila wrote on the Git mailing list (how to reply to this email):

Le 04/02/2026 à 15:19, Derrick Stolee via GitGitGadget a écrit :
> From: Derrick Stolee <stolee@gmail.com>
> 
> Tools that use the 'git config-batch' tool will want to know which commands
> are available in the current Git version. Having a 'help' command assists
> greatly to give a clear set of available commands and their versions.
> 
> Signed-off-by: Derrick Stolee <stolee@gmail.com>
> ---
>  Documentation/git-config-batch.adoc | 17 +++++++++++++++
>  builtin/config-batch.c              | 32 +++++++++++++++++++++++++++++
>  t/t1312-config-batch.sh             | 13 ++++++++++++
>  3 files changed, 62 insertions(+)
> 
> diff --git a/Documentation/git-config-batch.adoc b/Documentation/git-config-batch.adoc
> index 31dd42f481..1fff68a13c 100644
> --- a/Documentation/git-config-batch.adoc
> +++ b/Documentation/git-config-batch.adoc
> @@ -38,6 +38,23 @@ unknown_command LF
>  
>  These are the commands that are currently understood:
>  
> +`help` version 1::
> +	The `help` command lists the currently-available commands in

The boilerplat text "The `help` command" is not very useful to the
reader. The new usage is to directly state the command in imperative mood:

List the currently...

> +	this version of Git. The output is multi-line, but the first
> +	line provides the count of possible commands via `help count <N>`.
> +	The next `<N>` lines are of the form `help <command> <version>`
> +	to state that this Git version supports that `<command>` at
> +	version `<version>`. Note that the same command may have multiple
> +	available versions.

Placeholder punning to keep a consistency between the command and its
description. Good!

> ++
> +Here is the currentl output of the help text at the latest version:

current

It may not be wise to talk about the "latest version". If the manpages
and the git command are out of sync (the user compiles her own git
version, but does not update the man pages), this may be confusing.

Is this specification of version critical to the understanding?


> ++
> +------------
> +help 1 count 2
> +help 1 help 1
> +help 1 get 1
> +------------
> +
>  `get` version 1::
>  	The `get` command searches the config key-value pairs within a
>  	given `<scope>` for values that match the fixed `<key>` and

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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jean-Noël Avila wrote on the Git mailing list (how to reply to this email):

Le 04/02/2026 à 15:19, Derrick Stolee via GitGitGadget a écrit :
> From: Derrick Stolee <stolee@gmail.com>
> 
> When using automated tools, it is critical to allow for input/output formats
> that include special characters such as spaces and newlines. While the
> existing protocol for 'git config-batch' is human-readable and has some
> capacity for some spaces in certain positions, it is not available for
> spaces in the config key or newlines in the config values.
> 
> Add the '-z' option to signal the use of NUL-terminated strings. To
> understand where commands end regardless of potential future formats, use
> two NUL bytes in a row to terminate a command. To allow for empty string
> values, each token is provided in a <length>:<value> format, making "0:"
> the empty string value.
> 
> Update the existing 'help' and 'get' commands to match this format. Create
> helper methods that make it easy to parse and print in both formats
> simultaneously.
> 
> Signed-off-by: Derrick Stolee <stolee@gmail.com>
> ---
>  Documentation/git-config-batch.adoc |  57 ++++++++-
>  builtin/config-batch.c              | 188 +++++++++++++++++++++++++---
>  t/t1312-config-batch.sh             |  69 ++++++++++
>  3 files changed, 293 insertions(+), 21 deletions(-)
> 
> diff --git a/Documentation/git-config-batch.adoc b/Documentation/git-config-batch.adoc
> index 1fff68a13c..3c9a3bb763 100644
> --- a/Documentation/git-config-batch.adoc
> +++ b/Documentation/git-config-batch.adoc
> @@ -21,6 +21,15 @@ 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`.
>  
> +OPTIONS
> +-------
> +
> +`-z`::
> +	If specified, then use the NUL-terminated input and output

This boilerplate preliminary does not convey information, it is simpler
to just jump to the action performed by the option:

Use the _NUL_-terminated input and output…

> +	format instead of the space and newline format. This format is
> +	useful when the strings involved may include spaces or newlines.
> +	See PROTOCOL for more details.
> +
>  PROTOCOL
>  --------
>  By default, the protocol uses line feeds (`LF`) to signal the end of a
> @@ -41,13 +50,13 @@ These are the commands that are currently understood:
>  `help` version 1::
>  	The `help` command lists the currently-available commands in
>  	this version of Git. The output is multi-line, but the first
> -	line provides the count of possible commands via `help count <N>`.
> -	The next `<N>` lines are of the form `help <command> <version>`
> +	line provides the count of possible commands via `help 1 count <N>`.
> +	The next `<N>` lines are of the form `help 1 <command> <version>`
>  	to state that this Git version supports that `<command>` at
>  	version `<version>`. Note that the same command may have multiple
>  	available versions.
>  +
> -Here is the currentl output of the help text at the latest version:
> +Here is the current output of the help text at the latest version:

OK, the typo was fixed here.

>  +
>  ------------
>  help 1 count 2
> @@ -102,6 +111,48 @@ get 1 missing <key> [<value-pattern>|<value>]
>  where `<value-pattern>` or `<value>` is only supplied if provided in
>  the command.
>  
> +NUL-Terminated Format
> +~~~~~~~~~~~~~~~~~~~~~
> +
> +When `-z` is given, the protocol changes in some structural ways.
> +
> +First, each command is terminated with two NUL bytes, providing a clear
> +boundary between commands regardless of future possibilities of new
> +command formats.
> +
> +Second, any time that a space _would_ be used to partition tokens in a
> +command, a NUL byte is used instead. Further, each token is prefixed
> +with `<N>:` where `<N>` is a decimal representation of the length of
> +the string between the `:` and the next NUL byte. Any disagreement in
> +these lengths is treated as a parsing error. This use of a length does

I thought this length encoding was used to allow _NUL_ in the config
values. But here it is considered a parse error.

> +imply that "`0:`" is the representation of an empty string, if relevant.
> +
> +The decimal representation must have at most five numerals, thus the
> +maximum length of a string token can have 99999 characters.
> +
> +For example, the `get` command, version 1, could have any of the
> +following forms:
> +
> +------------
> +3:get NUL 1:1 NUL 5:local NUL 14:key.with space NUL NUL
> +3:get NUL 1:1 NUL 9:inherit NUL 8:test.key NUL 9:arg:regex NUL 6:.*\ .* NUL NUL
> +3:get NUL 1:1 NUL 6:global NUL 8:test.key NUL 15:arg:fixed-value NUL 3:a b NUL NUL
> +------------
> +
> +The output is modified similarly, such as the following output examples,
> +as if the input has a parse error, a valid `help` command, a `get`
> +command that had a match, and a `get` command that did not match.
> +
> +------------
> +15:unknown_command NUL NUL
> +4:help NUL 1:1 NUL 5:count NUL 1:2 NUL NUL
> +4:help NUL 1:1 NUL 4:help NUL 1:1 NUL NUL
> +4:help NUL 1:1 NUL 3:get NUL 1:1 NUL NUL
> +3:get NUL 1:1 NUL 5:found NUL 8:test.key NUL 5:value NUL NUL
> +3:get NUL 1:1 NUL 7:missing NUL 8:test.key NUL NUL
> +------------
> +
> +
>  SEE ALSO
>  --------
>  linkgit:git-config[1]

get 1 missing <key> [<value-pattern>|<value>]
------------
+
where `<value-pattern>` or `<value>` is only supplied if provided in
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jean-Noël Avila wrote on the Git mailing list (how to reply to this email):

Le 04/02/2026 à 15:20, Derrick Stolee via GitGitGadget a écrit :
> From: Derrick Stolee <stolee@gmail.com>
> 
> 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 <stolee@gmail.com>
> ---
>  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

Please use direct imperative form.

> +	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>

This "--<scope>" form is new in the synopsis grammar. Would we just cite
all alternatives or use a "normal" placeholder _<scope>_ ?

> +<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>
> +------------
> +

Please use synopsis style block for these too.

>  NUL-Terminated Format
>  ~~~~~~~~~~~~~~~~~~~~~
>  

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant