Merge branch 'ps/config-subcommands'

The operation mode options (like "--get") the "git config" command
uses have been deprecated and replaced with subcommands (like "git
config get").

* ps/config-subcommands:
  builtin/config: display subcommand help
  builtin/config: introduce "edit" subcommand
  builtin/config: introduce "remove-section" subcommand
  builtin/config: introduce "rename-section" subcommand
  builtin/config: introduce "unset" subcommand
  builtin/config: introduce "set" subcommand
  builtin/config: introduce "get" subcommand
  builtin/config: introduce "list" subcommand
  builtin/config: pull out function to handle `--null`
  builtin/config: pull out function to handle config location
  builtin/config: use `OPT_CMDMODE()` to specify modes
  builtin/config: move "fixed-value" option to correct group
  builtin/config: move option array around
  config: clarify memory ownership when preparing comment strings
maint
Junio C Hamano 2024-05-15 09:52:52 -07:00
commit fe3ccc7aab
6 changed files with 812 additions and 370 deletions

View File

@ -9,21 +9,14 @@ git-config - Get and set repository or global options
SYNOPSIS SYNOPSIS
-------- --------
[verse] [verse]
'git config' [<file-option>] [--type=<type>] [--comment=<message>] [--fixed-value] [--show-origin] [--show-scope] [-z|--null] <name> [<value> [<value-pattern>]] 'git config list' [<file-option>] [<display-option>] [--includes]
'git config' [<file-option>] [--type=<type>] [--comment=<message>] --add <name> <value> 'git config get' [<file-option>] [<display-option>] [--includes] [--all] [--regexp=<regexp>] [--value=<value>] [--fixed-value] [--default=<default>] <name>
'git config' [<file-option>] [--type=<type>] [--comment=<message>] [--fixed-value] --replace-all <name> <value> [<value-pattern>] 'git config set' [<file-option>] [--type=<type>] [--all] [--value=<value>] [--fixed-value] <name> <value>
'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--fixed-value] --get <name> [<value-pattern>] 'git config unset' [<file-option>] [--all] [--value=<value>] [--fixed-value] <name> <value>
'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--fixed-value] --get-all <name> [<value-pattern>] 'git config rename-section' [<file-option>] <old-name> <new-name>
'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--fixed-value] [--name-only] --get-regexp <name-regex> [<value-pattern>] 'git config remove-section' [<file-option>] <name>
'git config' [<file-option>] [--type=<type>] [-z|--null] --get-urlmatch <name> <URL> 'git config edit' [<file-option>]
'git config' [<file-option>] [--fixed-value] --unset <name> [<value-pattern>]
'git config' [<file-option>] [--fixed-value] --unset-all <name> [<value-pattern>]
'git config' [<file-option>] --rename-section <old-name> <new-name>
'git config' [<file-option>] --remove-section <name>
'git config' [<file-option>] [--show-origin] [--show-scope] [-z|--null] [--name-only] -l | --list
'git config' [<file-option>] --get-color <name> [<default>]
'git config' [<file-option>] --get-colorbool <name> [<stdout-is-tty>] 'git config' [<file-option>] --get-colorbool <name> [<stdout-is-tty>]
'git config' [<file-option>] -e | --edit


DESCRIPTION DESCRIPTION
----------- -----------
@ -31,7 +24,7 @@ You can query/set/replace/unset options with this command. The name is
actually the section and the key separated by a dot, and the value will be actually the section and the key separated by a dot, and the value will be
escaped. escaped.


Multiple lines can be added to an option by using the `--add` option. Multiple lines can be added to an option by using the `--append` option.
If you want to update or unset an option which can occur on multiple If you want to update or unset an option which can occur on multiple
lines, a `value-pattern` (which is an extended regular expression, lines, a `value-pattern` (which is an extended regular expression,
unless the `--fixed-value` option is given) needs to be given. Only the unless the `--fixed-value` option is given) needs to be given. Only the
@ -74,6 +67,42 @@ On success, the command returns the exit code 0.
A list of all available configuration variables can be obtained using the A list of all available configuration variables can be obtained using the
`git help --config` command. `git help --config` command.


COMMANDS
--------

list::
List all variables set in config file, along with their values.

get::
Emits the value of the specified key. If key is present multiple times
in the configuration, emits the last value. If `--all` is specified,
emits all values associated with key. Returns error code 1 if key is
not present.

set::
Set value for one or more config options. By default, this command
refuses to write multi-valued config options. Passing `--all` will
replace all multi-valued config options with the new value, whereas
`--value=` will replace all config options whose values match the given
pattern.

unset::
Unset value for one or more config options. By default, this command
refuses to unset multi-valued keys. Passing `--all` will unset all
multi-valued config options, whereas `--value` will unset all config
options whose values match the given pattern.

rename-section::
Rename the given section to a new name.

remove-section::
Remove the given section from the configuration file.

edit::
Opens an editor to modify the specified config file; either
`--system`, `--global`, `--local` (default), `--worktree`, or
`--file <config-file>`.

[[OPTIONS]] [[OPTIONS]]
OPTIONS OPTIONS
------- -------
@ -82,10 +111,9 @@ OPTIONS
Default behavior is to replace at most one line. This replaces Default behavior is to replace at most one line. This replaces
all lines matching the key (and optionally the `value-pattern`). all lines matching the key (and optionally the `value-pattern`).


--add:: --append::
Adds a new line to the option without altering any existing Adds a new line to the option without altering any existing
values. This is the same as providing '^$' as the `value-pattern` values. This is the same as providing '--value=^$' in `set`.
in `--replace-all`.


--comment <message>:: --comment <message>::
Append a comment at the end of new or modified lines. Append a comment at the end of new or modified lines.
@ -99,22 +127,16 @@ OPTIONS
not contain linefeed characters (no multi-line comments are not contain linefeed characters (no multi-line comments are
permitted). permitted).


--get:: --all::
Get the value for a given key (optionally filtered by a regex With `get`, return all values for a multi-valued key.
matching the value). Returns error code 1 if the key was not
found and the last value if multiple key values were found.


--get-all:: ---regexp::
Like get, but returns all values for a multi-valued key. With `get`, interpret the name as a regular expression. Regular
expression matching is currently case-sensitive and done against a
canonicalized version of the key in which section and variable names
are lowercased, but subsection names are not.


--get-regexp:: --url=<URL>::
Like --get-all, but interprets the name as a regular expression and
writes out the key names. Regular expression matching is currently
case-sensitive and done against a canonicalized version of the key
in which section and variable names are lowercased, but subsection
names are not.

--get-urlmatch <name> <URL>::
When given a two-part <name> as <section>.<key>, the value for When given a two-part <name> as <section>.<key>, the value for
<section>.<URL>.<key> whose <URL> part matches the best to the <section>.<URL>.<key> whose <URL> part matches the best to the
given URL is returned (if no such key exists, the value for given URL is returned (if no such key exists, the value for
@ -178,22 +200,6 @@ See also <<FILES>>.
section in linkgit:gitrevisions[7] for a more complete list of section in linkgit:gitrevisions[7] for a more complete list of
ways to spell blob names. ways to spell blob names.


--remove-section::
Remove the given section from the configuration file.

--rename-section::
Rename the given section to a new name.

--unset::
Remove the line matching the key from config file.

--unset-all::
Remove all lines matching the key from config file.

-l::
--list::
List all variables set in config file, along with their values.

--fixed-value:: --fixed-value::
When used with the `value-pattern` argument, treat `value-pattern` as When used with the `value-pattern` argument, treat `value-pattern` as
an exact string instead of a regular expression. This will restrict an exact string instead of a regular expression. This will restrict
@ -248,8 +254,8 @@ Valid `<type>`'s include:
contain line breaks. contain line breaks.


--name-only:: --name-only::
Output only the names of config variables for `--list` or Output only the names of config variables for `list` or
`--get-regexp`. `get`.


--show-origin:: --show-origin::
Augment the output of all queried config options with the Augment the output of all queried config options with the
@ -273,23 +279,6 @@ Valid `<type>`'s include:
When the color setting for `name` is undefined, the command uses When the color setting for `name` is undefined, the command uses
`color.ui` as fallback. `color.ui` as fallback.


--get-color <name> [<default>]::

Find the color configured for `name` (e.g. `color.diff.new`) and
output it as the ANSI color escape sequence to the standard
output. The optional `default` parameter is used instead, if
there is no color configured for `name`.
+
`--type=color [--default=<default>]` is preferred over `--get-color`
(but note that `--get-color` will omit the trailing newline printed by
`--type=color`).

-e::
--edit::
Opens an editor to modify the specified config file; either
`--system`, `--global`, `--local` (default), `--worktree`, or
`--file <config-file>`.

--[no-]includes:: --[no-]includes::
Respect `include.*` directives in config files when looking up Respect `include.*` directives in config files when looking up
values. Defaults to `off` when a specific file is given (e.g., values. Defaults to `off` when a specific file is given (e.g.,
@ -297,14 +286,64 @@ Valid `<type>`'s include:
config files. config files.


--default <value>:: --default <value>::
When using `--get`, and the requested variable is not found, behave as if When using `get`, and the requested variable is not found, behave as if
<value> were the value assigned to that variable. <value> were the value assigned to that variable.


DEPRECATED MODES
----------------

The following modes have been deprecated in favor of subcommands. It is
recommended to migrate to the new syntax.

'git config <name>'::
Replaced by `git config get <name>`.

'git config <name> <value> [<value-pattern>]'::
Replaced by `git config set [--value=<pattern>] <name> <value>`.

-l::
--list::
Replaced by `git config list`.

--get <name> [<value-pattern>]::
Replaced by `git config get [--value=<pattern>] <name>`.

--get-all <name> [<value-pattern>]::
Replaced by `git config get [--value=<pattern>] --all --show-names <name>`.

--get-regexp <name-regexp>::
Replaced by `git config get --all --show-names --regexp <name-regexp>`.

--get-urlmatch <name> <URL>::
Replaced by `git config get --all --show-names --url=<URL> <name>`.

--get-color <name> [<default>]::
Replaced by `git config get --type=color [--default=<default>] <name>`.

--add <name> <value>::
Replaced by `git config set --append <name> <value>`.

--unset <name> [<value-pattern>]::
Replaced by `git config unset [--value=<pattern>] <name>`.

--unset-all <name> [<value-pattern>]::
Replaced by `git config unset [--value=<pattern>] --all <name>`.

--rename-section <old-name> <new-name>::
Replaced by `git config rename-section <old-name> <new-name>`.

--remove-section <name>::
Replaced by `git config remove-section <name>`.

-e::
--edit::
Replaced by `git config edit`.

CONFIGURATION CONFIGURATION
------------- -------------
`pager.config` is only respected when listing configuration, i.e., when `pager.config` is only respected when listing configuration, i.e., when
using `--list` or any of the `--get-*` which may return multiple results. using `list` or `get` which may return multiple results. The default is to use
The default is to use a pager. a pager.


[[FILES]] [[FILES]]
FILES FILES
@ -346,8 +385,8 @@ precedence over values read earlier. When multiple values are taken then all
values of a key from all files will be used. values of a key from all files will be used.


By default, options are only written to the repository specific By default, options are only written to the repository specific
configuration file. Note that this also affects options like `--replace-all` configuration file. Note that this also affects options like `set`
and `--unset`. *'git config' will only ever change one file at a time*. and `unset`. *'git config' will only ever change one file at a time*.


You can limit which configuration sources are read from or written to by You can limit which configuration sources are read from or written to by
specifying the path of a file with the `--file` option, or by specifying a specifying the path of a file with the `--file` option, or by specifying a
@ -482,7 +521,7 @@ Given a .git/config like this:
you can set the filemode to true with you can set the filemode to true with


------------ ------------
% git config core.filemode true % git config set core.filemode true
------------ ------------


The hypothetical proxy command entries actually have a postfix to discern The hypothetical proxy command entries actually have a postfix to discern
@ -490,7 +529,7 @@ what URL they apply to. Here is how to change the entry for kernel.org
to "ssh". to "ssh".


------------ ------------
% git config core.gitproxy '"ssh" for kernel.org' 'for kernel.org$' % git config set --value='for kernel.org$' core.gitproxy '"ssh" for kernel.org'
------------ ------------


This makes sure that only the key/value pair for kernel.org is replaced. This makes sure that only the key/value pair for kernel.org is replaced.
@ -498,7 +537,7 @@ This makes sure that only the key/value pair for kernel.org is replaced.
To delete the entry for renames, do To delete the entry for renames, do


------------ ------------
% git config --unset diff.renames % git config unset diff.renames
------------ ------------


If you want to delete an entry for a multivar (like core.gitproxy above), If you want to delete an entry for a multivar (like core.gitproxy above),
@ -507,51 +546,45 @@ you have to provide a regex matching the value of exactly one line.
To query the value for a given key, do To query the value for a given key, do


------------ ------------
% git config --get core.filemode % git config get core.filemode
------------

or

------------
% git config core.filemode
------------ ------------


or, to query a multivar: or, to query a multivar:


------------ ------------
% git config --get core.gitproxy "for kernel.org$" % git config get --value="for kernel.org$" core.gitproxy
------------ ------------


If you want to know all the values for a multivar, do: If you want to know all the values for a multivar, do:


------------ ------------
% git config --get-all core.gitproxy % git config get --all --show-names core.gitproxy
------------ ------------


If you like to live dangerously, you can replace *all* core.gitproxy by a If you like to live dangerously, you can replace *all* core.gitproxy by a
new one with new one with


------------ ------------
% git config --replace-all core.gitproxy ssh % git config set --all core.gitproxy ssh
------------ ------------


However, if you really only want to replace the line for the default proxy, However, if you really only want to replace the line for the default proxy,
i.e. the one without a "for ..." postfix, do something like this: i.e. the one without a "for ..." postfix, do something like this:


------------ ------------
% git config core.gitproxy ssh '! for ' % git config set --value='! for ' core.gitproxy ssh
------------ ------------


To actually match only values with an exclamation mark, you have to To actually match only values with an exclamation mark, you have to


------------ ------------
% git config section.key value '[!]' % git config set --value='[!]' section.key value
------------ ------------


To add a new proxy, without altering any of the existing ones, use To add a new proxy, without altering any of the existing ones, use


------------ ------------
% git config --add core.gitproxy '"proxy-command" for example.com' % git config set --append core.gitproxy '"proxy-command" for example.com'
------------ ------------


An example to use customized color from the configuration in your An example to use customized color from the configuration in your
@ -559,8 +592,8 @@ script:


------------ ------------
#!/bin/sh #!/bin/sh
WS=$(git config --get-color color.diff.whitespace "blue reverse") WS=$(git config get --type=color --default="blue reverse" color.diff.whitespace)
RESET=$(git config --get-color "" "reset") RESET=$(git config get --type=color --default="reset" "")
echo "${WS}your whitespace color or blue reverse${RESET}" echo "${WS}your whitespace color or blue reverse${RESET}"
------------ ------------


@ -568,11 +601,11 @@ For URLs in `https://weak.example.com`, `http.sslVerify` is set to
false, while it is set to `true` for all others: false, while it is set to `true` for all others:


------------ ------------
% git config --type=bool --get-urlmatch http.sslverify https://good.example.com % git config get --type=bool --url=https://good.example.com http.sslverify
true true
% git config --type=bool --get-urlmatch http.sslverify https://weak.example.com % git config get --type=bool --url=https://weak.example.com http.sslverify
false false
% git config --get-urlmatch http https://weak.example.com % git config get --url=https://weak.example.com http
http.cookieFile /tmp/cookie.txt http.cookieFile /tmp/cookie.txt
http.sslverify false http.sslverify false
------------ ------------

View File

@ -16,7 +16,49 @@
#include "worktree.h" #include "worktree.h"


static const char *const builtin_config_usage[] = { static const char *const builtin_config_usage[] = {
N_("git config [<options>]"), N_("git config list [<file-option>] [<display-option>] [--includes]"),
N_("git config get [<file-option>] [<display-option>] [--includes] [--all] [--regexp=<regexp>] [--value=<value>] [--fixed-value] [--default=<default>] <name>"),
N_("git config set [<file-option>] [--type=<type>] [--all] [--value=<value>] [--fixed-value] <name> <value>"),
N_("git config unset [<file-option>] [--all] [--value=<value>] [--fixed-value] <name> <value>"),
N_("git config rename-section [<file-option>] <old-name> <new-name>"),
N_("git config remove-section [<file-option>] <name>"),
N_("git config edit [<file-option>]"),
N_("git config [<file-option>] --get-colorbool <name> [<stdout-is-tty>]"),
NULL
};

static const char *const builtin_config_list_usage[] = {
N_("git config list [<file-option>] [<display-option>] [--includes]"),
NULL
};

static const char *const builtin_config_get_usage[] = {
N_("git config get [<file-option>] [<display-option>] [--includes] [--all] [--regexp=<regexp>] [--value=<value>] [--fixed-value] [--default=<default>] <name>"),
NULL
};

static const char *const builtin_config_set_usage[] = {
N_("git config set [<file-option>] [--type=<type>] [--comment=<message>] [--all] [--value=<value>] [--fixed-value] <name> <value>"),
NULL
};

static const char *const builtin_config_unset_usage[] = {
N_("git config unset [<file-option>] [--all] [--value=<value>] [--fixed-value] <name> <value>"),
NULL
};

static const char *const builtin_config_rename_section_usage[] = {
N_("git config rename-section [<file-option>] <old-name> <new-name>"),
NULL
};

static const char *const builtin_config_remove_section_usage[] = {
N_("git config remove-section [<file-option>] <name>"),
NULL
};

static const char *const builtin_config_edit_usage[] = {
N_("git config edit [<file-option>]"),
NULL NULL
}; };


@ -33,6 +75,7 @@ static char delim = '=';
static char key_delim = ' '; static char key_delim = ' ';
static char term = '\n'; static char term = '\n';


static parse_opt_subcommand_fn *subcommand;
static int use_global_config, use_system_config, use_local_config; static int use_global_config, use_system_config, use_local_config;
static int use_worktree_config; static int use_worktree_config;
static struct git_config_source given_config_source; static struct git_config_source given_config_source;
@ -44,7 +87,7 @@ static struct config_options config_options;
static int show_origin; static int show_origin;
static int show_scope; static int show_scope;
static int fixed_value; static int fixed_value;
static const char *comment; static const char *comment_arg;


#define ACTION_GET (1<<0) #define ACTION_GET (1<<0)
#define ACTION_GET_ALL (1<<1) #define ACTION_GET_ALL (1<<1)
@ -135,54 +178,6 @@ static int option_parse_type(const struct option *opt, const char *arg,
return 0; return 0;
} }


static struct option builtin_config_options[] = {
OPT_GROUP(N_("Config file location")),
OPT_BOOL(0, "global", &use_global_config, N_("use global config file")),
OPT_BOOL(0, "system", &use_system_config, N_("use system config file")),
OPT_BOOL(0, "local", &use_local_config, N_("use repository config file")),
OPT_BOOL(0, "worktree", &use_worktree_config, N_("use per-worktree config file")),
OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")),
OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")),
OPT_GROUP(N_("Action")),
OPT_BIT(0, "get", &actions, N_("get value: name [value-pattern]"), ACTION_GET),
OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-pattern]"), ACTION_GET_ALL),
OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-pattern]"), ACTION_GET_REGEXP),
OPT_BIT(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH),
OPT_BIT(0, "replace-all", &actions, N_("replace all matching variables: name value [value-pattern]"), ACTION_REPLACE_ALL),
OPT_BIT(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD),
OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-pattern]"), ACTION_UNSET),
OPT_BIT(0, "unset-all", &actions, N_("remove all matches: name [value-pattern]"), ACTION_UNSET_ALL),
OPT_BIT(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION),
OPT_BIT(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION),
OPT_BIT('l', "list", &actions, N_("list all"), ACTION_LIST),
OPT_BOOL(0, "fixed-value", &fixed_value, N_("use string equality when comparing values to 'value-pattern'")),
OPT_BIT('e', "edit", &actions, N_("open an editor"), ACTION_EDIT),
OPT_BIT(0, "get-color", &actions, N_("find the color configured: slot [default]"), ACTION_GET_COLOR),
OPT_BIT(0, "get-colorbool", &actions, N_("find the color setting: slot [stdout-is-tty]"), ACTION_GET_COLORBOOL),
OPT_GROUP(N_("Type")),
OPT_CALLBACK('t', "type", &type, N_("type"), N_("value is given this type"), option_parse_type),
OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL),
OPT_CALLBACK_VALUE(0, "int", &type, N_("value is decimal number"), TYPE_INT),
OPT_CALLBACK_VALUE(0, "bool-or-int", &type, N_("value is --bool or --int"), TYPE_BOOL_OR_INT),
OPT_CALLBACK_VALUE(0, "bool-or-str", &type, N_("value is --bool or string"), TYPE_BOOL_OR_STR),
OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH),
OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE),
OPT_GROUP(N_("Other")),
OPT_BOOL('z', "null", &end_nul, N_("terminate values with NUL byte")),
OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")),
OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")),
OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")),
OPT_BOOL(0, "show-scope", &show_scope, N_("show scope of config (worktree, local, global, system, command)")),
OPT_STRING(0, "default", &default_value, N_("value"), N_("with --get, use default value when missing entry")),
OPT_STRING(0, "comment", &comment, N_("value"), N_("human-readable comment string (# will be prepended as needed)")),
OPT_END(),
};

static NORETURN void usage_builtin_config(void)
{
usage_with_options(builtin_config_usage, builtin_config_options);
}

static void check_argc(int argc, int min, int max) static void check_argc(int argc, int min, int max)
{ {
if (argc >= min && argc <= max) if (argc >= min && argc <= max)
@ -671,20 +666,8 @@ static char *default_user_config(void)
return strbuf_detach(&buf, NULL); return strbuf_detach(&buf, NULL);
} }


int cmd_config(int argc, const char **argv, const char *prefix) static void handle_config_location(const char *prefix)
{ {
int nongit = !startup_info->have_repository;
char *value = NULL;
int flags = 0;
int ret = 0;
struct key_value_info default_kvi = KVI_INIT;

given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT));

argc = parse_options(argc, argv, prefix, builtin_config_options,
builtin_config_usage,
PARSE_OPT_STOP_AT_NON_OPTION);

if (use_global_config + use_system_config + use_local_config + if (use_global_config + use_system_config + use_local_config +
use_worktree_config + use_worktree_config +
!!given_config_source.file + !!given_config_source.blob > 1) { !!given_config_source.file + !!given_config_source.blob > 1) {
@ -692,14 +675,13 @@ int cmd_config(int argc, const char **argv, const char *prefix)
usage_builtin_config(); usage_builtin_config();
} }


if (nongit) { if (!startup_info->have_repository) {
if (use_local_config) if (use_local_config)
die(_("--local can only be used inside a git repository")); die(_("--local can only be used inside a git repository"));
if (given_config_source.blob) if (given_config_source.blob)
die(_("--blob can only be used inside a git repository")); die(_("--blob can only be used inside a git repository"));
if (use_worktree_config) if (use_worktree_config)
die(_("--worktree can only be used inside a git repository")); die(_("--worktree can only be used inside a git repository"));

} }


if (given_config_source.file && if (given_config_source.file &&
@ -753,26 +735,384 @@ int cmd_config(int argc, const char **argv, const char *prefix)
config_options.respect_includes = !given_config_source.file; config_options.respect_includes = !given_config_source.file;
else else
config_options.respect_includes = respect_includes_opt; config_options.respect_includes = respect_includes_opt;
if (!nongit) { if (startup_info->have_repository) {
config_options.commondir = get_git_common_dir(); config_options.commondir = get_git_common_dir();
config_options.git_dir = get_git_dir(); config_options.git_dir = get_git_dir();
} }
}


static void handle_nul(void) {
if (end_nul) { if (end_nul) {
term = '\0'; term = '\0';
delim = '\n'; delim = '\n';
key_delim = '\n'; key_delim = '\n';
} }
}

#define CONFIG_LOCATION_OPTIONS \
OPT_GROUP(N_("Config file location")), \
OPT_BOOL(0, "global", &use_global_config, N_("use global config file")), \
OPT_BOOL(0, "system", &use_system_config, N_("use system config file")), \
OPT_BOOL(0, "local", &use_local_config, N_("use repository config file")), \
OPT_BOOL(0, "worktree", &use_worktree_config, N_("use per-worktree config file")), \
OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")), \
OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object"))

#define CONFIG_TYPE_OPTIONS \
OPT_GROUP(N_("Type")), \
OPT_CALLBACK('t', "type", &type, N_("type"), N_("value is given this type"), option_parse_type), \
OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL), \
OPT_CALLBACK_VALUE(0, "int", &type, N_("value is decimal number"), TYPE_INT), \
OPT_CALLBACK_VALUE(0, "bool-or-int", &type, N_("value is --bool or --int"), TYPE_BOOL_OR_INT), \
OPT_CALLBACK_VALUE(0, "bool-or-str", &type, N_("value is --bool or string"), TYPE_BOOL_OR_STR), \
OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH), \
OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE)

#define CONFIG_DISPLAY_OPTIONS \
OPT_GROUP(N_("Display options")), \
OPT_BOOL('z', "null", &end_nul, N_("terminate values with NUL byte")), \
OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")), \
OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")), \
OPT_BOOL(0, "show-scope", &show_scope, N_("show scope of config (worktree, local, global, system, command)"))

static struct option builtin_config_options[] = {
CONFIG_LOCATION_OPTIONS,
OPT_GROUP(N_("Action")),
OPT_CMDMODE(0, "get", &actions, N_("get value: name [<value-pattern>]"), ACTION_GET),
OPT_CMDMODE(0, "get-all", &actions, N_("get all values: key [<value-pattern>]"), ACTION_GET_ALL),
OPT_CMDMODE(0, "get-regexp", &actions, N_("get values for regexp: name-regex [<value-pattern>]"), ACTION_GET_REGEXP),
OPT_CMDMODE(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH),
OPT_CMDMODE(0, "replace-all", &actions, N_("replace all matching variables: name value [<value-pattern>]"), ACTION_REPLACE_ALL),
OPT_CMDMODE(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD),
OPT_CMDMODE(0, "unset", &actions, N_("remove a variable: name [<value-pattern>]"), ACTION_UNSET),
OPT_CMDMODE(0, "unset-all", &actions, N_("remove all matches: name [<value-pattern>]"), ACTION_UNSET_ALL),
OPT_CMDMODE(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION),
OPT_CMDMODE(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION),
OPT_CMDMODE('l', "list", &actions, N_("list all"), ACTION_LIST),
OPT_CMDMODE('e', "edit", &actions, N_("open an editor"), ACTION_EDIT),
OPT_CMDMODE(0, "get-color", &actions, N_("find the color configured: slot [<default>]"), ACTION_GET_COLOR),
OPT_CMDMODE(0, "get-colorbool", &actions, N_("find the color setting: slot [<stdout-is-tty>]"), ACTION_GET_COLORBOOL),
CONFIG_TYPE_OPTIONS,
CONFIG_DISPLAY_OPTIONS,
OPT_GROUP(N_("Other")),
OPT_STRING(0, "default", &default_value, N_("value"), N_("with --get, use default value when missing entry")),
OPT_STRING(0, "comment", &comment_arg, N_("value"), N_("human-readable comment string (# will be prepended as needed)")),
OPT_BOOL(0, "fixed-value", &fixed_value, N_("use string equality when comparing values to 'value-pattern'")),
OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")),
OPT_END(),
};

static NORETURN void usage_builtin_config(void)
{
usage_with_options(builtin_config_usage, builtin_config_options);
}

static int cmd_config_list(int argc, const char **argv, const char *prefix)
{
struct option opts[] = {
CONFIG_LOCATION_OPTIONS,
CONFIG_DISPLAY_OPTIONS,
OPT_GROUP(N_("Other")),
OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")),
OPT_END(),
};

argc = parse_options(argc, argv, prefix, opts, builtin_config_list_usage, 0);
check_argc(argc, 0, 0);

handle_config_location(prefix);
handle_nul();

setup_auto_pager("config", 1);

if (config_with_options(show_all_config, NULL,
&given_config_source, the_repository,
&config_options) < 0) {
if (given_config_source.file)
die_errno(_("unable to read config file '%s'"),
given_config_source.file);
else
die(_("error processing config file(s)"));
}

return 0;
}

static int cmd_config_get(int argc, const char **argv, const char *prefix)
{
const char *value_pattern = NULL, *url = NULL;
int flags = 0;
struct option opts[] = {
CONFIG_LOCATION_OPTIONS,
CONFIG_TYPE_OPTIONS,
OPT_GROUP(N_("Filter options")),
OPT_BOOL(0, "all", &do_all, N_("return all values for multi-valued config options")),
OPT_BOOL(0, "regexp", &use_key_regexp, N_("interpret the name as a regular expression")),
OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("show config with values matching the pattern")),
OPT_BIT(0, "fixed-value", &flags, N_("use string equality when comparing values to value pattern"), CONFIG_FLAGS_FIXED_VALUE),
OPT_STRING(0, "url", &url, N_("URL"), N_("show config matching the given URL")),
CONFIG_DISPLAY_OPTIONS,
OPT_BOOL(0, "show-names", &show_keys, N_("show config keys in addition to their values")),
OPT_GROUP(N_("Other")),
OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")),
OPT_STRING(0, "default", &default_value, N_("value"), N_("use default value when missing entry")),
OPT_END(),
};

argc = parse_options(argc, argv, prefix, opts, builtin_config_get_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
check_argc(argc, 1, 1);

if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern)
die(_("--fixed-value only applies with 'value-pattern'"));
if (default_value && (do_all || url))
die(_("--default= cannot be used with --all or --url="));
if (url && (do_all || use_key_regexp || value_pattern))
die(_("--url= cannot be used with --all, --regexp or --value"));

handle_config_location(prefix);
handle_nul();

setup_auto_pager("config", 1);

if (url)
return get_urlmatch(argv[0], url);
return get_value(argv[0], value_pattern, flags);
}

static int cmd_config_set(int argc, const char **argv, const char *prefix)
{
const char *value_pattern = NULL, *comment_arg = NULL;
char *comment = NULL;
int flags = 0, append = 0;
struct option opts[] = {
CONFIG_LOCATION_OPTIONS,
CONFIG_TYPE_OPTIONS,
OPT_GROUP(N_("Filter")),
OPT_BIT(0, "all", &flags, N_("replace multi-valued config option with new value"), CONFIG_FLAGS_MULTI_REPLACE),
OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("show config with values matching the pattern")),
OPT_BIT(0, "fixed-value", &flags, N_("use string equality when comparing values to value pattern"), CONFIG_FLAGS_FIXED_VALUE),
OPT_GROUP(N_("Other")),
OPT_STRING(0, "comment", &comment_arg, N_("value"), N_("human-readable comment string (# will be prepended as needed)")),
OPT_BOOL(0, "append", &append, N_("add a new line without altering any existing values")),
OPT_END(),
};
struct key_value_info default_kvi = KVI_INIT;
char *value;
int ret;

argc = parse_options(argc, argv, prefix, opts, builtin_config_set_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
check_write();
check_argc(argc, 2, 2);

if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern)
die(_("--fixed-value only applies with --value=<pattern>"));
if (append && value_pattern)
die(_("--append cannot be used with --value=<pattern>"));
if (append)
value_pattern = CONFIG_REGEX_NONE;

comment = git_config_prepare_comment_string(comment_arg);

handle_config_location(prefix);

value = normalize_value(argv[0], argv[1], &default_kvi);

if ((flags & CONFIG_FLAGS_MULTI_REPLACE) || value_pattern) {
ret = git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], value, value_pattern,
comment, flags);
} else {
ret = git_config_set_in_file_gently(given_config_source.file,
argv[0], comment, value);
if (ret == CONFIG_NOTHING_SET)
error(_("cannot overwrite multiple values with a single value\n"
" Use a regexp, --add or --replace-all to change %s."), argv[0]);
}

free(comment);
free(value);
return ret;
}

static int cmd_config_unset(int argc, const char **argv, const char *prefix)
{
const char *value_pattern = NULL;
int flags = 0;
struct option opts[] = {
CONFIG_LOCATION_OPTIONS,
OPT_GROUP(N_("Filter")),
OPT_BIT(0, "all", &flags, N_("replace multi-valued config option with new value"), CONFIG_FLAGS_MULTI_REPLACE),
OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("show config with values matching the pattern")),
OPT_BIT(0, "fixed-value", &flags, N_("use string equality when comparing values to value pattern"), CONFIG_FLAGS_FIXED_VALUE),
OPT_END(),
};

argc = parse_options(argc, argv, prefix, opts, builtin_config_unset_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
check_write();
check_argc(argc, 1, 1);

if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern)
die(_("--fixed-value only applies with 'value-pattern'"));

handle_config_location(prefix);

if ((flags & CONFIG_FLAGS_MULTI_REPLACE) || value_pattern)
return git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], NULL, value_pattern,
NULL, flags);
else
return git_config_set_in_file_gently(given_config_source.file, argv[0],
NULL, NULL);
}

static int cmd_config_rename_section(int argc, const char **argv, const char *prefix)
{
struct option opts[] = {
CONFIG_LOCATION_OPTIONS,
OPT_END(),
};
int ret;

argc = parse_options(argc, argv, prefix, opts, builtin_config_rename_section_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
check_write();
check_argc(argc, 2, 2);

handle_config_location(prefix);

ret = git_config_rename_section_in_file(given_config_source.file,
argv[0], argv[1]);
if (ret < 0)
return ret;
else if (!ret)
die(_("no such section: %s"), argv[0]);

return 0;
}

static int cmd_config_remove_section(int argc, const char **argv, const char *prefix)
{
struct option opts[] = {
CONFIG_LOCATION_OPTIONS,
OPT_END(),
};
int ret;

argc = parse_options(argc, argv, prefix, opts, builtin_config_remove_section_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
check_write();
check_argc(argc, 1, 1);

handle_config_location(prefix);

ret = git_config_rename_section_in_file(given_config_source.file,
argv[0], NULL);
if (ret < 0)
return ret;
else if (!ret)
die(_("no such section: %s"), argv[0]);

return 0;
}

static int show_editor(void)
{
char *config_file;

if (!given_config_source.file && !startup_info->have_repository)
die(_("not in a git directory"));
if (given_config_source.use_stdin)
die(_("editing stdin is not supported"));
if (given_config_source.blob)
die(_("editing blobs is not supported"));
git_config(git_default_config, NULL);
config_file = given_config_source.file ?
xstrdup(given_config_source.file) :
git_pathdup("config");
if (use_global_config) {
int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666);
if (fd >= 0) {
char *content = default_user_config();
write_str_in_full(fd, content);
free(content);
close(fd);
}
else if (errno != EEXIST)
die_errno(_("cannot create configuration file %s"), config_file);
}
launch_editor(config_file, NULL, NULL);
free(config_file);

return 0;
}

static int cmd_config_edit(int argc, const char **argv, const char *prefix)
{
struct option opts[] = {
CONFIG_LOCATION_OPTIONS,
OPT_END(),
};

argc = parse_options(argc, argv, prefix, opts, builtin_config_edit_usage, 0);
check_write();
check_argc(argc, 0, 0);

handle_config_location(prefix);

return show_editor();
}

static struct option builtin_subcommand_options[] = {
OPT_SUBCOMMAND("list", &subcommand, cmd_config_list),
OPT_SUBCOMMAND("get", &subcommand, cmd_config_get),
OPT_SUBCOMMAND("set", &subcommand, cmd_config_set),
OPT_SUBCOMMAND("unset", &subcommand, cmd_config_unset),
OPT_SUBCOMMAND("rename-section", &subcommand, cmd_config_rename_section),
OPT_SUBCOMMAND("remove-section", &subcommand, cmd_config_remove_section),
OPT_SUBCOMMAND("edit", &subcommand, cmd_config_edit),
OPT_END(),
};

int cmd_config(int argc, const char **argv, const char *prefix)
{
char *value = NULL, *comment = NULL;
int flags = 0;
int ret = 0;
struct key_value_info default_kvi = KVI_INIT;

given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT));

/*
* This is somewhat hacky: we first parse the command line while
* keeping all args intact in order to determine whether a subcommand
* has been specified. If so, we re-parse it a second time, but this
* time we drop KEEP_ARGV0. This is so that we don't munge the command
* line in case no subcommand was given, which would otherwise confuse
* us when parsing the legacy-style modes that don't use subcommands.
*/
argc = parse_options(argc, argv, prefix, builtin_subcommand_options, builtin_config_usage,
PARSE_OPT_SUBCOMMAND_OPTIONAL|PARSE_OPT_KEEP_ARGV0|PARSE_OPT_KEEP_UNKNOWN_OPT);
if (subcommand) {
argc = parse_options(argc, argv, prefix, builtin_subcommand_options, builtin_config_usage,
PARSE_OPT_SUBCOMMAND_OPTIONAL|PARSE_OPT_KEEP_UNKNOWN_OPT);
return subcommand(argc, argv, prefix);
}

argc = parse_options(argc, argv, prefix, builtin_config_options,
builtin_config_usage,
PARSE_OPT_STOP_AT_NON_OPTION);

handle_config_location(prefix);
handle_nul();


if ((actions & (ACTION_GET_COLOR|ACTION_GET_COLORBOOL)) && type) { if ((actions & (ACTION_GET_COLOR|ACTION_GET_COLORBOOL)) && type) {
error(_("--get-color and variable type are incoherent")); error(_("--get-color and variable type are incoherent"));
usage_builtin_config(); usage_builtin_config();
} }


if (HAS_MULTI_BITS(actions)) {
error(_("only one action at a time"));
usage_builtin_config();
}
if (actions == 0) if (actions == 0)
switch (argc) { switch (argc) {
case 1: actions = ACTION_GET; break; case 1: actions = ACTION_GET; break;
@ -799,7 +1139,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
usage_builtin_config(); usage_builtin_config();
} }


if (comment && if (comment_arg &&
!(actions & (ACTION_ADD|ACTION_SET|ACTION_SET_ALL|ACTION_REPLACE_ALL))) { !(actions & (ACTION_ADD|ACTION_SET|ACTION_SET_ALL|ACTION_REPLACE_ALL))) {
error(_("--comment is only applicable to add/set/replace operations")); error(_("--comment is only applicable to add/set/replace operations"));
usage_builtin_config(); usage_builtin_config();
@ -841,7 +1181,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
flags |= CONFIG_FLAGS_FIXED_VALUE; flags |= CONFIG_FLAGS_FIXED_VALUE;
} }


comment = git_config_prepare_comment_string(comment); comment = git_config_prepare_comment_string(comment_arg);


if (actions & PAGING_ACTIONS) if (actions & PAGING_ACTIONS)
setup_auto_pager("config", 1); setup_auto_pager("config", 1);
@ -859,32 +1199,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
} }
} }
else if (actions == ACTION_EDIT) { else if (actions == ACTION_EDIT) {
char *config_file; ret = show_editor();

check_argc(argc, 0, 0);
if (!given_config_source.file && nongit)
die(_("not in a git directory"));
if (given_config_source.use_stdin)
die(_("editing stdin is not supported"));
if (given_config_source.blob)
die(_("editing blobs is not supported"));
git_config(git_default_config, NULL);
config_file = given_config_source.file ?
xstrdup(given_config_source.file) :
git_pathdup("config");
if (use_global_config) {
int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666);
if (fd >= 0) {
char *content = default_user_config();
write_str_in_full(fd, content);
free(content);
close(fd);
}
else if (errno != EEXIST)
die_errno(_("cannot create configuration file %s"), config_file);
}
launch_editor(config_file, NULL, NULL);
free(config_file);
} }
else if (actions == ACTION_SET) { else if (actions == ACTION_SET) {
check_write(); check_write();
@ -993,6 +1308,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
return get_colorbool(argv[0], argc == 2); return get_colorbool(argv[0], argc == 2);
} }


free(comment);
free(value); free(value);
return ret; return ret;
} }

View File

@ -3193,14 +3193,10 @@ void git_config_set(const char *key, const char *value)
trace2_cmd_set_config(key, value); trace2_cmd_set_config(key, value);
} }


/* char *git_config_prepare_comment_string(const char *comment)
* The ownership rule is that the caller will own the string
* if it receives a piece of memory different from what it passed
* as the parameter.
*/
const char *git_config_prepare_comment_string(const char *comment)
{ {
size_t leading_blanks; size_t leading_blanks;
char *prepared;


if (!comment) if (!comment)
return NULL; return NULL;
@ -3221,13 +3217,13 @@ const char *git_config_prepare_comment_string(const char *comment)


leading_blanks = strspn(comment, " \t"); leading_blanks = strspn(comment, " \t");
if (leading_blanks && comment[leading_blanks] == '#') if (leading_blanks && comment[leading_blanks] == '#')
; /* use it as-is */ prepared = xstrdup(comment); /* use it as-is */
else if (comment[0] == '#') else if (comment[0] == '#')
comment = xstrfmt(" %s", comment); prepared = xstrfmt(" %s", comment);
else else
comment = xstrfmt(" # %s", comment); prepared = xstrfmt(" # %s", comment);


return comment; return prepared;
} }


static void validate_comment_string(const char *comment) static void validate_comment_string(const char *comment)

View File

@ -338,7 +338,7 @@ void git_config_set_multivar(const char *, const char *, const char *, unsigned)
int repo_config_set_multivar_gently(struct repository *, const char *, const char *, const char *, unsigned); int repo_config_set_multivar_gently(struct repository *, const char *, const char *, const char *, unsigned);
int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, const char *, unsigned); int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, const char *, unsigned);


const char *git_config_prepare_comment_string(const char *); char *git_config_prepare_comment_string(const char *);


/** /**
* takes four parameters: * takes four parameters:

View File

@ -10,7 +10,6 @@ checkout
checkout-index checkout-index
clone clone
column column
config
credential credential
credential-cache credential-cache
credential-store credential-store

File diff suppressed because it is too large Load Diff