Merge branch 'ds/config-literal-value'

Various subcommands of "git config" that takes value_regex
learn the "--literal-value" option to take the value_regex option
as a literal string.

* ds/config-literal-value:
  config doc: value-pattern is not necessarily a regexp
  config: implement --fixed-value with --get*
  config: plumb --fixed-value into config API
  config: add --fixed-value option, un-implemented
  t1300: add test for --replace-all with value-pattern
  t1300: test "set all" mode with value-pattern
  config: replace 'value_regex' with 'value_pattern'
  config: convert multi_replace to flags
maint
Junio C Hamano 2020-12-08 15:11:19 -08:00
commit a10e7842ab
7 changed files with 312 additions and 76 deletions

View File

@ -9,15 +9,15 @@ git-config - Get and set repository or global options
SYNOPSIS SYNOPSIS
-------- --------
[verse] [verse]
'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] name [value [value_regex]] 'git config' [<file-option>] [--type=<type>] [--fixed-value] [--show-origin] [--show-scope] [-z|--null] name [value [value-pattern]]
'git config' [<file-option>] [--type=<type>] --add name value 'git config' [<file-option>] [--type=<type>] --add name value
'git config' [<file-option>] [--type=<type>] --replace-all name value [value_regex] 'git config' [<file-option>] [--type=<type>] [--fixed-value] --replace-all name value [value-pattern]
'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] --get name [value_regex] 'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--fixed-value] --get name [value-pattern]
'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] --get-all name [value_regex] 'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--fixed-value] --get-all name [value-pattern]
'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--name-only] --get-regexp name_regex [value_regex] 'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--fixed-value] [--name-only] --get-regexp name_regex [value-pattern]
'git config' [<file-option>] [--type=<type>] [-z|--null] --get-urlmatch name URL 'git config' [<file-option>] [--type=<type>] [-z|--null] --get-urlmatch name URL
'git config' [<file-option>] --unset name [value_regex] 'git config' [<file-option>] [--fixed-value] --unset name [value-pattern]
'git config' [<file-option>] --unset-all name [value_regex] '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>] --rename-section old_name new_name
'git config' [<file-option>] --remove-section 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>] [--show-origin] [--show-scope] [-z|--null] [--name-only] -l | --list
@ -33,10 +33,13 @@ 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 `--add` 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 POSIX regexp `value_regex` needs to be given. Only the lines, a `value-pattern` (which is an extended regular expression,
existing values that match the regexp are updated or unset. If unless the `--fixed-value` option is given) needs to be given. Only the
you want to handle the lines that do *not* match the regex, just existing values that match the pattern are updated or unset. If
prepend a single exclamation mark in front (see also <<EXAMPLES>>). you want to handle the lines that do *not* match the pattern, just
prepend a single exclamation mark in front (see also <<EXAMPLES>>),
but note that this only works when the `--fixed-value` option is not
in use.


The `--type=<type>` option instructs 'git config' to ensure that incoming and The `--type=<type>` option instructs 'git config' to ensure that incoming and
outgoing values are canonicalize-able under the given <type>. If no outgoing values are canonicalize-able under the given <type>. If no
@ -73,11 +76,11 @@ OPTIONS


--replace-all:: --replace-all::
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_regex). all lines matching the key (and optionally the `value-pattern`).


--add:: --add::
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_regex values. This is the same as providing '^$' as the `value-pattern`
in `--replace-all`. in `--replace-all`.


--get:: --get::
@ -165,6 +168,12 @@ See also <<FILES>>.
--list:: --list::
List all variables set in config file, along with their values. List all variables set in config file, along with their values.


--fixed-value::
When used with the `value-pattern` argument, treat `value-pattern` as
an exact string instead of a regular expression. This will restrict
the name/value pairs that are matched to only those where the value
is exactly equal to the `value-pattern`.

--type <type>:: --type <type>::
'git config' will ensure that any input or output is valid under the given 'git config' will ensure that any input or output is valid under the given
type constraint(s), and will canonicalize outgoing values in `<type>`'s type constraint(s), and will canonicalize outgoing values in `<type>`'s

View File

@ -829,10 +829,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
die(_("Branch '%s' has no upstream information"), branch->name); die(_("Branch '%s' has no upstream information"), branch->name);


strbuf_addf(&buf, "branch.%s.remote", branch->name); strbuf_addf(&buf, "branch.%s.remote", branch->name);
git_config_set_multivar(buf.buf, NULL, NULL, 1); git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
strbuf_reset(&buf); strbuf_reset(&buf);
strbuf_addf(&buf, "branch.%s.merge", branch->name); strbuf_addf(&buf, "branch.%s.merge", branch->name);
git_config_set_multivar(buf.buf, NULL, NULL, 1); git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
strbuf_release(&buf); strbuf_release(&buf);
} else if (argc > 0 && argc <= 2) { } else if (argc > 0 && argc <= 2) {
if (filter.kind != FILTER_REFS_BRANCHES) if (filter.kind != FILTER_REFS_BRANCHES)

View File

@ -14,6 +14,7 @@ static const char *const builtin_config_usage[] = {


static char *key; static char *key;
static regex_t *key_regexp; static regex_t *key_regexp;
static const char *value_pattern;
static regex_t *regexp; static regex_t *regexp;
static int show_keys; static int show_keys;
static int omit_values; static int omit_values;
@ -34,6 +35,7 @@ static int respect_includes_opt = -1;
static struct config_options config_options; 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;


#define ACTION_GET (1<<0) #define ACTION_GET (1<<0)
#define ACTION_GET_ALL (1<<1) #define ACTION_GET_ALL (1<<1)
@ -133,17 +135,18 @@ static struct option builtin_config_options[] = {
OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given 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_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")),
OPT_GROUP(N_("Action")), OPT_GROUP(N_("Action")),
OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET), 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-regex]"), ACTION_GET_ALL), 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-regex]"), ACTION_GET_REGEXP), 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, "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_regex]"), ACTION_REPLACE_ALL), 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, "add", &actions, N_("add a new variable: name value"), ACTION_ADD),
OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-regex]"), ACTION_UNSET), 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-regex]"), ACTION_UNSET_ALL), 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, "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(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION),
OPT_BIT('l', "list", &actions, N_("list all"), ACTION_LIST), 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('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-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_BIT(0, "get-colorbool", &actions, N_("find the color setting: slot [stdout-is-tty]"), ACTION_GET_COLORBOOL),
@ -296,6 +299,8 @@ static int collect_config(const char *key_, const char *value_, void *cb)
return 0; return 0;
if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0)) if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
return 0; return 0;
if (fixed_value && strcmp(value_pattern, (value_?value_:"")))
return 0;
if (regexp != NULL && if (regexp != NULL &&
(do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0))) (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
return 0; return 0;
@ -306,7 +311,7 @@ static int collect_config(const char *key_, const char *value_, void *cb)
return format_config(&values->items[values->nr++], key_, value_); return format_config(&values->items[values->nr++], key_, value_);
} }


static int get_value(const char *key_, const char *regex_) static int get_value(const char *key_, const char *regex_, unsigned flags)
{ {
int ret = CONFIG_GENERIC_ERROR; int ret = CONFIG_GENERIC_ERROR;
struct strbuf_list values = {NULL}; struct strbuf_list values = {NULL};
@ -343,7 +348,9 @@ static int get_value(const char *key_, const char *regex_)
} }
} }


if (regex_) { if (regex_ && (flags & CONFIG_FLAGS_FIXED_VALUE))
value_pattern = regex_;
else if (regex_) {
if (regex_[0] == '!') { if (regex_[0] == '!') {
do_not_match = 1; do_not_match = 1;
regex_++; regex_++;
@ -631,6 +638,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
{ {
int nongit = !startup_info->have_repository; int nongit = !startup_info->have_repository;
char *value; char *value;
int flags = 0;


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


@ -766,6 +774,42 @@ int cmd_config(int argc, const char **argv, const char *prefix)
usage_builtin_config(); usage_builtin_config();
} }


/* check usage of --fixed-value */
if (fixed_value) {
int allowed_usage = 0;

switch (actions) {
/* git config --get <name> <value-pattern> */
case ACTION_GET:
/* git config --get-all <name> <value-pattern> */
case ACTION_GET_ALL:
/* git config --get-regexp <name-pattern> <value-pattern> */
case ACTION_GET_REGEXP:
/* git config --unset <name> <value-pattern> */
case ACTION_UNSET:
/* git config --unset-all <name> <value-pattern> */
case ACTION_UNSET_ALL:
allowed_usage = argc > 1 && !!argv[1];
break;

/* git config <name> <value> <value-pattern> */
case ACTION_SET_ALL:
/* git config --replace-all <name> <value> <value-pattern> */
case ACTION_REPLACE_ALL:
allowed_usage = argc > 2 && !!argv[2];
break;

/* other options don't allow --fixed-value */
}

if (!allowed_usage) {
error(_("--fixed-value only applies with 'value-pattern'"));
usage_builtin_config();
}

flags |= CONFIG_FLAGS_FIXED_VALUE;
}

if (actions & PAGING_ACTIONS) if (actions & PAGING_ACTIONS)
setup_auto_pager("config", 1); setup_auto_pager("config", 1);


@ -827,7 +871,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
value = normalize_value(argv[0], argv[1]); value = normalize_value(argv[0], argv[1]);
UNLEAK(value); UNLEAK(value);
return git_config_set_multivar_in_file_gently(given_config_source.file, return git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], value, argv[2], 0); argv[0], value, argv[2],
flags);
} }
else if (actions == ACTION_ADD) { else if (actions == ACTION_ADD) {
check_write(); check_write();
@ -836,7 +881,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
UNLEAK(value); UNLEAK(value);
return git_config_set_multivar_in_file_gently(given_config_source.file, return git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], value, argv[0], value,
CONFIG_REGEX_NONE, 0); CONFIG_REGEX_NONE,
flags);
} }
else if (actions == ACTION_REPLACE_ALL) { else if (actions == ACTION_REPLACE_ALL) {
check_write(); check_write();
@ -844,23 +890,24 @@ int cmd_config(int argc, const char **argv, const char *prefix)
value = normalize_value(argv[0], argv[1]); value = normalize_value(argv[0], argv[1]);
UNLEAK(value); UNLEAK(value);
return git_config_set_multivar_in_file_gently(given_config_source.file, return git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], value, argv[2], 1); argv[0], value, argv[2],
flags | CONFIG_FLAGS_MULTI_REPLACE);
} }
else if (actions == ACTION_GET) { else if (actions == ACTION_GET) {
check_argc(argc, 1, 2); check_argc(argc, 1, 2);
return get_value(argv[0], argv[1]); return get_value(argv[0], argv[1], flags);
} }
else if (actions == ACTION_GET_ALL) { else if (actions == ACTION_GET_ALL) {
do_all = 1; do_all = 1;
check_argc(argc, 1, 2); check_argc(argc, 1, 2);
return get_value(argv[0], argv[1]); return get_value(argv[0], argv[1], flags);
} }
else if (actions == ACTION_GET_REGEXP) { else if (actions == ACTION_GET_REGEXP) {
show_keys = 1; show_keys = 1;
use_key_regexp = 1; use_key_regexp = 1;
do_all = 1; do_all = 1;
check_argc(argc, 1, 2); check_argc(argc, 1, 2);
return get_value(argv[0], argv[1]); return get_value(argv[0], argv[1], flags);
} }
else if (actions == ACTION_GET_URLMATCH) { else if (actions == ACTION_GET_URLMATCH) {
check_argc(argc, 2, 2); check_argc(argc, 2, 2);
@ -871,7 +918,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
check_argc(argc, 1, 2); check_argc(argc, 1, 2);
if (argc == 2) if (argc == 2)
return git_config_set_multivar_in_file_gently(given_config_source.file, return git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], NULL, argv[1], 0); argv[0], NULL, argv[1],
flags);
else else
return git_config_set_in_file_gently(given_config_source.file, return git_config_set_in_file_gently(given_config_source.file,
argv[0], NULL); argv[0], NULL);
@ -880,7 +928,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
check_write(); check_write();
check_argc(argc, 1, 2); check_argc(argc, 1, 2);
return git_config_set_multivar_in_file_gently(given_config_source.file, return git_config_set_multivar_in_file_gently(given_config_source.file,
argv[0], NULL, argv[1], 1); argv[0], NULL, argv[1],
flags | CONFIG_FLAGS_MULTI_REPLACE);
} }
else if (actions == ACTION_RENAME_SECTION) { else if (actions == ACTION_RENAME_SECTION) {
int ret; int ret;

View File

@ -712,7 +712,7 @@ static int mv(int argc, const char **argv)


strbuf_reset(&buf); strbuf_reset(&buf);
strbuf_addf(&buf, "remote.%s.fetch", rename.new_name); strbuf_addf(&buf, "remote.%s.fetch", rename.new_name);
git_config_set_multivar(buf.buf, NULL, NULL, 1); git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old_name); strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old_name);
for (i = 0; i < oldremote->fetch.raw_nr; i++) { for (i = 0; i < oldremote->fetch.raw_nr; i++) {
char *ptr; char *ptr;
@ -1491,7 +1491,8 @@ static int update(int argc, const char **argv)


static int remove_all_fetch_refspecs(const char *key) static int remove_all_fetch_refspecs(const char *key)
{ {
return git_config_set_multivar_gently(key, NULL, NULL, 1); return git_config_set_multivar_gently(key, NULL, NULL,
CONFIG_FLAGS_MULTI_REPLACE);
} }


static void add_branches(struct remote *remote, const char **branches, static void add_branches(struct remote *remote, const char **branches,
@ -1686,7 +1687,8 @@ static int set_url(int argc, const char **argv)
if (!delete_mode) if (!delete_mode)
git_config_set_multivar(name_buf.buf, newurl, oldurl, 0); git_config_set_multivar(name_buf.buf, newurl, oldurl, 0);
else else
git_config_set_multivar(name_buf.buf, NULL, oldurl, 1); git_config_set_multivar(name_buf.buf, NULL, oldurl,
CONFIG_FLAGS_MULTI_REPLACE);
out: out:
strbuf_release(&name_buf); strbuf_release(&name_buf);
return 0; return 0;

View File

@ -2415,7 +2415,8 @@ struct config_store_data {
size_t baselen; size_t baselen;
char *key; char *key;
int do_not_match; int do_not_match;
regex_t *value_regex; const char *fixed_value;
regex_t *value_pattern;
int multi_replace; int multi_replace;
struct { struct {
size_t begin, end; size_t begin, end;
@ -2429,10 +2430,10 @@ struct config_store_data {
static void config_store_data_clear(struct config_store_data *store) static void config_store_data_clear(struct config_store_data *store)
{ {
free(store->key); free(store->key);
if (store->value_regex != NULL && if (store->value_pattern != NULL &&
store->value_regex != CONFIG_REGEX_NONE) { store->value_pattern != CONFIG_REGEX_NONE) {
regfree(store->value_regex); regfree(store->value_pattern);
free(store->value_regex); free(store->value_pattern);
} }
free(store->parsed); free(store->parsed);
free(store->seen); free(store->seen);
@ -2444,13 +2445,15 @@ static int matches(const char *key, const char *value,
{ {
if (strcmp(key, store->key)) if (strcmp(key, store->key))
return 0; /* not ours */ return 0; /* not ours */
if (!store->value_regex) if (store->fixed_value)
return !strcmp(store->fixed_value, value);
if (!store->value_pattern)
return 1; /* always matches */ return 1; /* always matches */
if (store->value_regex == CONFIG_REGEX_NONE) if (store->value_pattern == CONFIG_REGEX_NONE)
return 0; /* never matches */ return 0; /* never matches */


return store->do_not_match ^ return store->do_not_match ^
(value && !regexec(store->value_regex, value, 0, NULL, 0)); (value && !regexec(store->value_pattern, value, 0, NULL, 0));
} }


static int store_aux_event(enum config_event_t type, static int store_aux_event(enum config_event_t type,
@ -2726,12 +2729,12 @@ void git_config_set(const char *key, const char *value)


/* /*
* If value==NULL, unset in (remove from) config, * If value==NULL, unset in (remove from) config,
* if value_regex!=NULL, disregard key/value pairs where value does not match. * if value_pattern!=NULL, disregard key/value pairs where value does not match.
* if value_regex==CONFIG_REGEX_NONE, do not match any existing values * if value_pattern==CONFIG_REGEX_NONE, do not match any existing values
* (only add a new one) * (only add a new one)
* if multi_replace==0, nothing, or only one matching key/value is replaced, * if flags contains the CONFIG_FLAGS_MULTI_REPLACE flag, all matching
* else all matching key/values (regardless how many) are removed, * key/values are removed before a single new pair is written. If the
* before the new pair is written. * flag is not present, then replace only the first match.
* *
* Returns 0 on success. * Returns 0 on success.
* *
@ -2751,8 +2754,8 @@ void git_config_set(const char *key, const char *value)
*/ */
int git_config_set_multivar_in_file_gently(const char *config_filename, int git_config_set_multivar_in_file_gently(const char *config_filename,
const char *key, const char *value, const char *key, const char *value,
const char *value_regex, const char *value_pattern,
int multi_replace) unsigned flags)
{ {
int fd = -1, in_fd = -1; int fd = -1, in_fd = -1;
int ret; int ret;
@ -2769,7 +2772,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
if (ret) if (ret)
goto out_free; goto out_free;


store.multi_replace = multi_replace; store.multi_replace = (flags & CONFIG_FLAGS_MULTI_REPLACE) != 0;


if (!config_filename) if (!config_filename)
config_filename = filename_buf = git_pathdup("config"); config_filename = filename_buf = git_pathdup("config");
@ -2812,22 +2815,24 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
int i, new_line = 0; int i, new_line = 0;
struct config_options opts; struct config_options opts;


if (value_regex == NULL) if (value_pattern == NULL)
store.value_regex = NULL; store.value_pattern = NULL;
else if (value_regex == CONFIG_REGEX_NONE) else if (value_pattern == CONFIG_REGEX_NONE)
store.value_regex = CONFIG_REGEX_NONE; store.value_pattern = CONFIG_REGEX_NONE;
else if (flags & CONFIG_FLAGS_FIXED_VALUE)
store.fixed_value = value_pattern;
else { else {
if (value_regex[0] == '!') { if (value_pattern[0] == '!') {
store.do_not_match = 1; store.do_not_match = 1;
value_regex++; value_pattern++;
} else } else
store.do_not_match = 0; store.do_not_match = 0;


store.value_regex = (regex_t*)xmalloc(sizeof(regex_t)); store.value_pattern = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(store.value_regex, value_regex, if (regcomp(store.value_pattern, value_pattern,
REG_EXTENDED)) { REG_EXTENDED)) {
error(_("invalid pattern: %s"), value_regex); error(_("invalid pattern: %s"), value_pattern);
FREE_AND_NULL(store.value_regex); FREE_AND_NULL(store.value_pattern);
ret = CONFIG_INVALID_PATTERN; ret = CONFIG_INVALID_PATTERN;
goto out_free; goto out_free;
} }
@ -2858,7 +2863,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,


/* if nothing to unset, or too many matches, error out */ /* if nothing to unset, or too many matches, error out */
if ((store.seen_nr == 0 && value == NULL) || if ((store.seen_nr == 0 && value == NULL) ||
(store.seen_nr > 1 && multi_replace == 0)) { (store.seen_nr > 1 && !store.multi_replace)) {
ret = CONFIG_NOTHING_SET; ret = CONFIG_NOTHING_SET;
goto out_free; goto out_free;
} }
@ -2997,10 +3002,10 @@ write_err_out:


void git_config_set_multivar_in_file(const char *config_filename, void git_config_set_multivar_in_file(const char *config_filename,
const char *key, const char *value, const char *key, const char *value,
const char *value_regex, int multi_replace) const char *value_pattern, unsigned flags)
{ {
if (!git_config_set_multivar_in_file_gently(config_filename, key, value, if (!git_config_set_multivar_in_file_gently(config_filename, key, value,
value_regex, multi_replace)) value_pattern, flags))
return; return;
if (value) if (value)
die(_("could not set '%s' to '%s'"), key, value); die(_("could not set '%s' to '%s'"), key, value);
@ -3009,17 +3014,17 @@ void git_config_set_multivar_in_file(const char *config_filename,
} }


int git_config_set_multivar_gently(const char *key, const char *value, int git_config_set_multivar_gently(const char *key, const char *value,
const char *value_regex, int multi_replace) const char *value_pattern, unsigned flags)
{ {
return git_config_set_multivar_in_file_gently(NULL, key, value, value_regex, return git_config_set_multivar_in_file_gently(NULL, key, value, value_pattern,
multi_replace); flags);
} }


void git_config_set_multivar(const char *key, const char *value, void git_config_set_multivar(const char *key, const char *value,
const char *value_regex, int multi_replace) const char *value_pattern, unsigned flags)
{ {
git_config_set_multivar_in_file(NULL, key, value, value_regex, git_config_set_multivar_in_file(NULL, key, value, value_pattern,
multi_replace); flags);
} }


static int section_name_match (const char *buf, const char *name) static int section_name_match (const char *buf, const char *name)

View File

@ -256,9 +256,29 @@ void git_config_set(const char *, const char *);


int git_config_parse_key(const char *, char **, size_t *); int git_config_parse_key(const char *, char **, size_t *);
int git_config_key_is_valid(const char *key); int git_config_key_is_valid(const char *key);
int git_config_set_multivar_gently(const char *, const char *, const char *, int);
void git_config_set_multivar(const char *, const char *, const char *, int); /*
int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, int); * The following macros specify flag bits that alter the behavior
* of the git_config_set_multivar*() methods.
*/

/*
* When CONFIG_FLAGS_MULTI_REPLACE is specified, all matching key/values
* are removed before a single new pair is written. If the flag is not
* present, then set operations replace only the first match.
*/
#define CONFIG_FLAGS_MULTI_REPLACE (1 << 0)

/*
* When CONFIG_FLAGS_FIXED_VALUE is specified, match key/value pairs
* by string comparison (not regex match) to the provided value_pattern
* parameter.
*/
#define CONFIG_FLAGS_FIXED_VALUE (1 << 1)

int git_config_set_multivar_gently(const char *, const char *, const char *, unsigned);
void git_config_set_multivar(const char *, const char *, const char *, unsigned);
int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, unsigned);


/** /**
* takes four parameters: * takes four parameters:
@ -276,13 +296,15 @@ int git_config_set_multivar_in_file_gently(const char *, const char *, const cha
* - the value regex, as a string. It will disregard key/value pairs where value * - the value regex, as a string. It will disregard key/value pairs where value
* does not match. * does not match.
* *
* - a multi_replace value, as an int. If value is equal to zero, nothing or only * - a flags value with bits corresponding to the CONFIG_FLAG_* macros.
* one matching key/value is replaced, else all matching key/values (regardless
* how many) are removed, before the new pair is written.
* *
* It returns 0 on success. * It returns 0 on success.
*/ */
void git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int); void git_config_set_multivar_in_file(const char *config_filename,
const char *key,
const char *value,
const char *value_pattern,
unsigned flags);


/** /**
* rename or remove sections in the config file * rename or remove sections in the config file

View File

@ -1917,4 +1917,153 @@ test_expect_success '--replace-all does not invent newlines' '
test_cmp expect .git/config test_cmp expect .git/config
' '


test_expect_success 'set all config with value-pattern' '
test_when_finished rm -f config initial &&
git config --file=initial abc.key one &&

# no match => add new entry
cp initial config &&
git config --file=config abc.key two a+ &&
git config --file=config --list >actual &&
cat >expect <<-\EOF &&
abc.key=one
abc.key=two
EOF
test_cmp expect actual &&

# multiple matches => failure
test_must_fail git config --file=config abc.key three o+ 2>err &&
test_i18ngrep "has multiple values" err &&

# multiple values, no match => add
git config --file=config abc.key three a+ &&
git config --file=config --list >actual &&
cat >expect <<-\EOF &&
abc.key=one
abc.key=two
abc.key=three
EOF
test_cmp expect actual &&

# single match => replace
git config --file=config abc.key four h+ &&
git config --file=config --list >actual &&
cat >expect <<-\EOF &&
abc.key=one
abc.key=two
abc.key=four
EOF
test_cmp expect actual
'

test_expect_success '--replace-all and value-pattern' '
test_when_finished rm -f config &&
git config --file=config --add abc.key one &&
git config --file=config --add abc.key two &&
git config --file=config --add abc.key three &&
git config --file=config --replace-all abc.key four "o+" &&
git config --file=config --list >actual &&
cat >expect <<-\EOF &&
abc.key=four
abc.key=three
EOF
test_cmp expect actual
'

test_expect_success 'refuse --fixed-value for incompatible actions' '
test_when_finished rm -f config &&
git config --file=config dev.null bogus &&

# These modes do not allow --fixed-value at all
test_must_fail git config --file=config --fixed-value --add dev.null bogus &&
test_must_fail git config --file=config --fixed-value --get-urlmatch dev.null bogus &&
test_must_fail git config --file=config --fixed-value --get-urlmatch dev.null bogus &&
test_must_fail git config --file=config --fixed-value --rename-section dev null &&
test_must_fail git config --file=config --fixed-value --remove-section dev &&
test_must_fail git config --file=config --fixed-value --list &&
test_must_fail git config --file=config --fixed-value --get-color dev.null &&
test_must_fail git config --file=config --fixed-value --get-colorbool dev.null &&

# These modes complain when --fixed-value has no value-pattern
test_must_fail git config --file=config --fixed-value dev.null bogus &&
test_must_fail git config --file=config --fixed-value --replace-all dev.null bogus &&
test_must_fail git config --file=config --fixed-value --get dev.null &&
test_must_fail git config --file=config --fixed-value --get-all dev.null &&
test_must_fail git config --file=config --fixed-value --get-regexp "dev.*" &&
test_must_fail git config --file=config --fixed-value --unset dev.null &&
test_must_fail git config --file=config --fixed-value --unset-all dev.null
'

test_expect_success '--fixed-value uses exact string matching' '
test_when_finished rm -f config initial &&
META="a+b*c?d[e]f.g" &&
git config --file=initial fixed.test "$META" &&

cp initial config &&
git config --file=config fixed.test bogus "$META" &&
git config --file=config --list >actual &&
cat >expect <<-EOF &&
fixed.test=$META
fixed.test=bogus
EOF
test_cmp expect actual &&

cp initial config &&
git config --file=config --fixed-value fixed.test bogus "$META" &&
git config --file=config --list >actual &&
cat >expect <<-\EOF &&
fixed.test=bogus
EOF
test_cmp expect actual &&

cp initial config &&
test_must_fail git config --file=config --unset fixed.test "$META" &&
git config --file=config --fixed-value --unset fixed.test "$META" &&
test_must_fail git config --file=config fixed.test &&

cp initial config &&
test_must_fail git config --file=config --unset-all fixed.test "$META" &&
git config --file=config --fixed-value --unset-all fixed.test "$META" &&
test_must_fail git config --file=config fixed.test &&

cp initial config &&
git config --file=config --replace-all fixed.test bogus "$META" &&
git config --file=config --list >actual &&
cat >expect <<-EOF &&
fixed.test=$META
fixed.test=bogus
EOF
test_cmp expect actual &&

git config --file=config --fixed-value --replace-all fixed.test bogus "$META" &&
git config --file=config --list >actual &&
cat >expect <<-EOF &&
fixed.test=bogus
fixed.test=bogus
EOF
test_cmp expect actual
'

test_expect_success '--get and --get-all with --fixed-value' '
test_when_finished rm -f config &&
META="a+b*c?d[e]f.g" &&
git config --file=config fixed.test bogus &&
git config --file=config --add fixed.test "$META" &&

git config --file=config --get fixed.test bogus &&
test_must_fail git config --file=config --get fixed.test "$META" &&
git config --file=config --get --fixed-value fixed.test "$META" &&
test_must_fail git config --file=config --get --fixed-value fixed.test non-existent &&

git config --file=config --get-all fixed.test bogus &&
test_must_fail git config --file=config --get-all fixed.test "$META" &&
git config --file=config --get-all --fixed-value fixed.test "$META" &&
test_must_fail git config --file=config --get-all --fixed-value fixed.test non-existent &&

git config --file=config --get-regexp fixed+ bogus &&
test_must_fail git config --file=config --get-regexp fixed+ "$META" &&
git config --file=config --get-regexp --fixed-value fixed+ "$META" &&
test_must_fail git config --file=config --get-regexp --fixed-value fixed+ non-existent
'

test_done test_done