builtin/config: introduce "unset" subcommand

Introduce a new "unset" subcommand to git-config(1). Please refer to preceding commits regarding the motivation behind this change. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed May 6, 2024 at 10:56 UTC 95ea69c67b6bef193d813b1dca9801012bc10f3b
3 files changed +84 -26
Documentation/git-config.txt
+16 -11
@@ -11,9 +11,8 @@ SYNOPSIS
11 [verse]
12 'git config list' [<file-option>] [<display-option>] [--includes]
13 'git config get' [<file-option>] [<display-option>] [--includes] [--all] [--regexp=<regexp>] [--value=<value>] [--fixed-value] [--default=<default>] <name>
14 -'git config set' [<file-option>] [--type=<type>] [--comment=<message>] [--all] [--value=<value>] [--fixed-value] <name> <value>
15 -'git config' [<file-option>] [--fixed-value] --unset <name> [<value-pattern>]
16 -'git config' [<file-option>] [--fixed-value] --unset-all <name> [<value-pattern>]
14 +'git config set' [<file-option>] [--type=<type>] [--all] [--value=<value>] [--fixed-value] <name> <value>
15 +'git config unset' [<file-option>] [--all] [--value=<value>] [--fixed-value] <name> <value>
16 'git config' [<file-option>] --rename-section <old-name> <new-name>
17 'git config' [<file-option>] --remove-section <name>
18 'git config' [<file-option>] --get-colorbool <name> [<stdout-is-tty>]
@@ -87,6 +86,12 @@ set::
86 `--value=` will replace all config options whose values match the given
87 pattern.
88
89 +unset::
90 + Unset value for one or more config options. By default, this command
91 + refuses to unset multi-valued keys. Passing `--all` will unset all
92 + multi-valued config options, whereas `--value` will unset all config
93 + options whose values match the given pattern.
94 +
95 [[OPTIONS]]
96 OPTIONS
97 -------
@@ -190,12 +195,6 @@ See also <<FILES>>.
195 --rename-section::
196 Rename the given section to a new name.
197
193 ---unset::
194 - Remove the line matching the key from config file.
195 -
196 ---unset-all::
197 - Remove all lines matching the key from config file.
198 -
198 --fixed-value::
199 When used with the `value-pattern` argument, treat `value-pattern` as
200 an exact string instead of a regular expression. This will restrict
@@ -325,6 +324,12 @@ recommended to migrate to the new syntax.
324 --add <name> <value>::
325 Replaced by `git config set --append <name> <value>`.
326
327 +--unset <name> [<value-pattern>]::
328 + Replaced by `git config unset [--value=<pattern>] <name>`.
329 +
330 +--unset-all <name> [<value-pattern>]::
331 + Replaced by `git config unset [--value=<pattern>] --all <name>`.
332 +
333 CONFIGURATION
334 -------------
335 `pager.config` is only respected when listing configuration, i.e., when
@@ -372,7 +377,7 @@ values of a key from all files will be used.
377
378 By default, options are only written to the repository specific
379 configuration file. Note that this also affects options like `set`
375 -and `--unset`. *'git config' will only ever change one file at a time*.
380 +and `unset`. *'git config' will only ever change one file at a time*.
381
382 You can limit which configuration sources are read from or written to by
383 specifying the path of a file with the `--file` option, or by specifying a
@@ -523,7 +528,7 @@ This makes sure that only the key/value pair for kernel.org is replaced.
528 To delete the entry for renames, do
529
530 ------------
526 -% git config --unset diff.renames
531 +% git config unset diff.renames
532 ------------
533
534 If you want to delete an entry for a multivar (like core.gitproxy above),
builtin/config.c
+39
@@ -19,6 +19,7 @@ static const char *const builtin_config_usage[] = {
19 N_("git config list [<file-option>] [<display-option>] [--includes]"),
20 N_("git config get [<file-option>] [<display-option>] [--includes] [--all] [--regexp=<regexp>] [--value=<value>] [--fixed-value] [--default=<default>] <name>"),
21 N_("git config set [<file-option>] [--type=<type>] [--all] [--value=<value>] [--fixed-value] <name> <value>"),
22 + N_("git config unset [<file-option>] [--all] [--value=<value>] [--fixed-value] <name> <value>"),
23 NULL
24 };
25
@@ -37,6 +38,11 @@ static const char *const builtin_config_set_usage[] = {
38 NULL
39 };
40
41 +static const char *const builtin_config_unset_usage[] = {
42 + N_("git config unset [<file-option>] [--all] [--value=<value>] [--fixed-value] <name> <value>"),
43 + NULL
44 +};
45 +
46 static char *key;
47 static regex_t *key_regexp;
48 static const char *value_pattern;
@@ -911,10 +917,43 @@ static int cmd_config_set(int argc, const char **argv, const char *prefix)
917 return ret;
918 }
919
920 +static int cmd_config_unset(int argc, const char **argv, const char *prefix)
921 +{
922 + const char *value_pattern = NULL;
923 + int flags = 0;
924 + struct option opts[] = {
925 + CONFIG_LOCATION_OPTIONS,
926 + OPT_GROUP(N_("Filter")),
927 + OPT_BIT(0, "all", &flags, N_("replace multi-valued config option with new value"), CONFIG_FLAGS_MULTI_REPLACE),
928 + OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("show config with values matching the pattern")),
929 + OPT_BIT(0, "fixed-value", &flags, N_("use string equality when comparing values to value pattern"), CONFIG_FLAGS_FIXED_VALUE),
930 + OPT_END(),
931 + };
932 +
933 + argc = parse_options(argc, argv, prefix, opts, builtin_config_unset_usage,
934 + PARSE_OPT_STOP_AT_NON_OPTION);
935 + check_write();
936 + check_argc(argc, 1, 1);
937 +
938 + if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern)
939 + die(_("--fixed-value only applies with 'value-pattern'"));
940 +
941 + handle_config_location(prefix);
942 +
943 + if ((flags & CONFIG_FLAGS_MULTI_REPLACE) || value_pattern)
944 + return git_config_set_multivar_in_file_gently(given_config_source.file,
945 + argv[0], NULL, value_pattern,
946 + NULL, flags);
947 + else
948 + return git_config_set_in_file_gently(given_config_source.file, argv[0],
949 + NULL, NULL);
950 +}
951 +
952 static struct option builtin_subcommand_options[] = {
953 OPT_SUBCOMMAND("list", &subcommand, cmd_config_list),
954 OPT_SUBCOMMAND("get", &subcommand, cmd_config_get),
955 OPT_SUBCOMMAND("set", &subcommand, cmd_config_set),
956 + OPT_SUBCOMMAND("unset", &subcommand, cmd_config_unset),
957 OPT_END(),
958 };
959
t/t1300-config.sh
+29 -15
@@ -22,6 +22,8 @@ legacy)
22 mode_get_regexp="--get-regexp"
23 mode_set=""
24 mode_replace_all="--replace-all"
25 + mode_unset="--unset"
26 + mode_unset_all="--unset-all"
27 ;;
28 subcommands)
29 mode_prefix=""
@@ -30,6 +32,8 @@ subcommands)
32 mode_get_regexp="get --regexp --all --show-names"
33 mode_set="set"
34 mode_replace_all="set --all"
35 + mode_unset="unset"
36 + mode_unset_all="unset --all"
37 ;;
38 *)
39 BUG "unknown mode $mode";;
@@ -259,7 +263,7 @@ foo = bar
263 EOF
264
265 test_expect_success 'unset with cont. lines' '
262 - git config --unset beta.baz
266 + git config ${mode_unset} beta.baz
267 '
268
269 cat > expect <<\EOF
@@ -286,7 +290,7 @@ EOF
290 cp .git/config .git/config2
291
292 test_expect_success 'multiple unset' '
289 - git config --unset-all beta.haha
293 + git config ${mode_unset_all} beta.haha
294 '
295
296 cat > expect << EOF
@@ -372,7 +376,7 @@ noIndent= sillyValue ; 'nother silly comment
376 nonewline = wow
377 EOF
378 test_expect_success 'unset' '
375 - git config --unset beta.haha &&
379 + git config ${mode_unset} beta.haha &&
380 test_cmp expect .git/config
381 '
382
@@ -428,11 +432,11 @@ test_expect_success 'multivar replace' '
432 '
433
434 test_expect_success 'ambiguous unset' '
431 - test_must_fail git config --unset nextsection.nonewline
435 + test_must_fail git config ${mode_unset} nextsection.nonewline
436 '
437
438 test_expect_success 'invalid unset' '
435 - test_must_fail git config --unset somesection.nonewline
439 + test_must_fail git config ${mode_unset} somesection.nonewline
440 '
441
442 cat > expect << EOF
@@ -446,7 +450,12 @@ noIndent= sillyValue ; 'nother silly comment
450 EOF
451
452 test_expect_success 'multivar unset' '
449 - git config --unset nextsection.nonewline "wow3$" &&
453 + case "$mode" in
454 + legacy)
455 + git config --unset nextsection.nonewline "wow3$";;
456 + subcommands)
457 + git config unset --value="wow3$" nextsection.nonewline;;
458 + esac &&
459 test_cmp expect .git/config
460 '
461
@@ -2013,7 +2022,7 @@ test_expect_success '--unset last key removes section (except if commented)' '
2022 # please be careful when you update the above variable
2023 EOF
2024
2016 - git config --unset section.key &&
2025 + git config ${mode_unset} section.key &&
2026 test_cmp expect .git/config &&
2027
2028 cat >.git/config <<-\EOF &&
@@ -2026,7 +2035,7 @@ test_expect_success '--unset last key removes section (except if commented)' '
2035 [next-section]
2036 EOF
2037
2029 - git config --unset section.key &&
2038 + git config ${mode_unset} section.key &&
2039 test_cmp expect .git/config &&
2040
2041 q_to_tab >.git/config <<-\EOF &&
@@ -2036,7 +2045,7 @@ test_expect_success '--unset last key removes section (except if commented)' '
2045 [two]
2046 key = true
2047 EOF
2039 - git config --unset two.key &&
2048 + git config ${mode_unset} two.key &&
2049 ! grep two .git/config &&
2050
2051 q_to_tab >.git/config <<-\EOF &&
@@ -2046,7 +2055,7 @@ test_expect_success '--unset last key removes section (except if commented)' '
2055 [one]
2056 key = true
2057 EOF
2049 - git config --unset-all one.key &&
2058 + git config ${mode_unset_all} one.key &&
2059 test_line_count = 0 .git/config &&
2060
2061 q_to_tab >.git/config <<-\EOF &&
@@ -2056,7 +2065,7 @@ test_expect_success '--unset last key removes section (except if commented)' '
2065 [two]
2066 Qkey = true
2067 EOF
2059 - git config --unset two.key &&
2068 + git config ${mode_unset} two.key &&
2069 grep two .git/config &&
2070
2071 q_to_tab >.git/config <<-\EOF &&
@@ -2068,7 +2077,7 @@ test_expect_success '--unset last key removes section (except if commented)' '
2077 [TWO "subsection"]
2078 [one]
2079 EOF
2071 - git config --unset two.subsection.key &&
2080 + git config ${mode_unset} two.subsection.key &&
2081 test "not [two subsection]" = "$(git config ${mode_get} one.key)" &&
2082 test_line_count = 3 .git/config
2083 '
@@ -2080,7 +2089,7 @@ test_expect_success '--unset-all removes section if empty & uncommented' '
2089 key = value2
2090 EOF
2091
2083 - git config --unset-all section.key &&
2092 + git config ${mode_unset_all} section.key &&
2093 test_line_count = 0 .git/config
2094 '
2095
@@ -2604,8 +2613,8 @@ test_expect_success 'refuse --fixed-value for incompatible actions' '
2613 test_must_fail git config ${mode_prefix}get --file=config --fixed-value dev.null &&
2614 test_must_fail git config ${mode_get_all} --file=config --fixed-value dev.null &&
2615 test_must_fail git config ${mode_get_regexp} --file=config --fixed-value "dev.*" &&
2607 - test_must_fail git config --file=config --fixed-value --unset dev.null &&
2608 - test_must_fail git config --file=config --fixed-value --unset-all dev.null
2616 + test_must_fail git config ${mode_unset} --file=config --fixed-value dev.null &&
2617 + test_must_fail git config ${mode_unset_all} --file=config --fixed-value dev.null
2618 '
2619
2620 test_expect_success '--fixed-value uses exact string matching' '
@@ -2635,6 +2644,11 @@ test_expect_success '--fixed-value uses exact string matching' '
2644 git config --file=config --fixed-value --unset fixed.test "$META" &&
2645 test_must_fail git config ${mode_get} --file=config fixed.test &&
2646
2647 + cp initial config &&
2648 + test_must_fail git config unset --file=config --value="$META" fixed.test &&
2649 + git config unset --file=config --fixed-value --value="$META" fixed.test &&
2650 + test_must_fail git config ${mode_get} --file=config fixed.test &&
2651 +
2652 cp initial config &&
2653 test_must_fail git config --file=config --unset-all fixed.test "$META" &&
2654 git config --file=config --fixed-value --unset-all fixed.test "$META" &&