From 25ede48b5406524d1a6b900e400f593c0b9a34dd Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 23 Feb 2026 12:26:43 +0000 Subject: [PATCH 01/13] config: move show_all_config() In anticipation of using format_config() in this method, move show_all_config() lower in the file without changes. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- builtin/config.c | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 288ebdfdaa..237f7a934d 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -231,30 +231,6 @@ static void show_config_scope(const struct config_display_options *opts, strbuf_addch(buf, term); } -static int show_all_config(const char *key_, const char *value_, - const struct config_context *ctx, - void *cb) -{ - const struct config_display_options *opts = cb; - const struct key_value_info *kvi = ctx->kvi; - - if (opts->show_origin || opts->show_scope) { - struct strbuf buf = STRBUF_INIT; - if (opts->show_scope) - show_config_scope(opts, kvi, &buf); - if (opts->show_origin) - show_config_origin(opts, kvi, &buf); - /* Use fwrite as "buf" can contain \0's if "end_null" is set. */ - fwrite(buf.buf, 1, buf.len, stdout); - strbuf_release(&buf); - } - if (!opts->omit_values && value_) - printf("%s%c%s%c", key_, opts->delim, value_, opts->term); - else - printf("%s%c", key_, opts->term); - return 0; -} - struct strbuf_list { struct strbuf *items; int nr; @@ -332,6 +308,30 @@ static int format_config(const struct config_display_options *opts, return 0; } +static int show_all_config(const char *key_, const char *value_, + const struct config_context *ctx, + void *cb) +{ + const struct config_display_options *opts = cb; + const struct key_value_info *kvi = ctx->kvi; + + if (opts->show_origin || opts->show_scope) { + struct strbuf buf = STRBUF_INIT; + if (opts->show_scope) + show_config_scope(opts, kvi, &buf); + if (opts->show_origin) + show_config_origin(opts, kvi, &buf); + /* Use fwrite as "buf" can contain \0's if "end_null" is set. */ + fwrite(buf.buf, 1, buf.len, stdout); + strbuf_release(&buf); + } + if (!opts->omit_values && value_) + printf("%s%c%s%c", key_, opts->delim, value_, opts->term); + else + printf("%s%c", key_, opts->term); + return 0; +} + #define GET_VALUE_ALL (1 << 0) #define GET_VALUE_KEY_REGEXP (1 << 1) From 12210d034635e6e558f23505ef3edaedd870097e Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 23 Feb 2026 12:26:44 +0000 Subject: [PATCH 02/13] config: add 'gently' parameter to format_config() This parameter is set to 0 for all current callers and is UNUSED. However, we will start using this option in future changes and in a critical change that requires gentle parsing (not using die()) to try parsing all values in a list. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- builtin/config.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 237f7a934d..b4c4228311 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -242,10 +242,14 @@ struct strbuf_list { * append it into strbuf `buf`. Returns a negative value on failure, * 0 on success, 1 on a missing optional value (i.e., telling the * caller to pretend that did not exist). + * + * Note: 'gently' is currently ignored, but will be implemented in + * a future change. */ static int format_config(const struct config_display_options *opts, struct strbuf *buf, const char *key_, - const char *value_, const struct key_value_info *kvi) + const char *value_, const struct key_value_info *kvi, + int gently UNUSED) { if (opts->show_scope) show_config_scope(opts, kvi, buf); @@ -372,7 +376,7 @@ static int collect_config(const char *key_, const char *value_, strbuf_init(&values->items[values->nr], 0); status = format_config(data->display_opts, &values->items[values->nr++], - key_, value_, kvi); + key_, value_, kvi, 0); if (status < 0) return status; if (status) { @@ -463,7 +467,7 @@ static int get_value(const struct config_location_options *opts, strbuf_init(item, 0); status = format_config(display_opts, item, key_, - display_opts->default_value, &kvi); + display_opts->default_value, &kvi, 0); if (status < 0) die(_("failed to format default config value: %s"), display_opts->default_value); @@ -743,7 +747,7 @@ static int get_urlmatch(const struct config_location_options *opts, status = format_config(&display_opts, &buf, item->string, matched->value_is_null ? NULL : matched->value.buf, - &matched->kvi); + &matched->kvi, 0); if (!status) fwrite(buf.buf, 1, buf.len, stdout); strbuf_release(&buf); From 1ef1f9d53a1607dd8fd38e0dbae67e405c3b3563 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 23 Feb 2026 12:26:45 +0000 Subject: [PATCH 03/13] config: make 'git config list --type=' work Previously, the --type= argument to 'git config list' was ignored and did nothing. Now, we add the use of format_config() to the show_all_config() function so each key-value pair is attempted to be parsed. This is our first use of the 'gently' parameter with a nonzero value. When listing multiple values, our initial settings for the output format is different. Add a new init helper to specify the fact that keys should be shown and also add the default delimiters as they were unset in some cases. Our intention is that if there is an error in parsing, then the row is not output. This is necessary to avoid the caller needing to build their own validator to understand the difference between valid, canonicalized types and other raw string values. The raw values will always be available to the user if they do not specify the --type= option. The current behavior is more complicated, including error messages on bad parsing or potentially complete failure of the command. We add tests at this point that demonstrate the current behavior so we can witness the fix in future changes that parse these values quietly and gently. This is a change in behavior! We are starting to respect an option that was previously ignored, leading to potential user confusion. This is probably still a good option, since the --type argument did not change behavior at all previously, so users can get the behavior they expect by removing the --type argument or adding the --no-type argument. t1300-config.sh is updated with the current behavior of this formatting logic to justify the upcoming refactoring of format_config() that will incrementally fix some of these cases to be more user-friendly. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- Documentation/git-config.adoc | 3 ++ builtin/config.c | 35 +++++++------ t/t1300-config.sh | 97 ++++++++++++++++++++++++++++++++++- 3 files changed, 119 insertions(+), 16 deletions(-) diff --git a/Documentation/git-config.adoc b/Documentation/git-config.adoc index ac3b536a15..5300dd4c51 100644 --- a/Documentation/git-config.adoc +++ b/Documentation/git-config.adoc @@ -240,6 +240,9 @@ Valid ``'s include: that the given value is canonicalize-able as an ANSI color, but it is written as-is. + +If the command is in `list` mode, then the `--type ` argument will apply +to each listed config value. If the value does not successfully parse in that +format, then it will be omitted from the list. --bool:: --int:: diff --git a/builtin/config.c b/builtin/config.c index b4c4228311..4c4c791883 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -318,21 +318,12 @@ static int show_all_config(const char *key_, const char *value_, { const struct config_display_options *opts = cb; const struct key_value_info *kvi = ctx->kvi; + struct strbuf formatted = STRBUF_INIT; - if (opts->show_origin || opts->show_scope) { - struct strbuf buf = STRBUF_INIT; - if (opts->show_scope) - show_config_scope(opts, kvi, &buf); - if (opts->show_origin) - show_config_origin(opts, kvi, &buf); - /* Use fwrite as "buf" can contain \0's if "end_null" is set. */ - fwrite(buf.buf, 1, buf.len, stdout); - strbuf_release(&buf); - } - if (!opts->omit_values && value_) - printf("%s%c%s%c", key_, opts->delim, value_, opts->term); - else - printf("%s%c", key_, opts->term); + if (format_config(opts, &formatted, key_, value_, kvi, 1) >= 0) + fwrite(formatted.buf, 1, formatted.len, stdout); + + strbuf_release(&formatted); return 0; } @@ -872,6 +863,19 @@ static void display_options_init(struct config_display_options *opts) } } +static void display_options_init_list(struct config_display_options *opts) +{ + opts->show_keys = 1; + + if (opts->end_nul) { + display_options_init(opts); + } else { + opts->term = '\n'; + opts->delim = ' '; + opts->key_delim = '='; + } +} + static int cmd_config_list(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) { @@ -890,7 +894,7 @@ static int cmd_config_list(int argc, const char **argv, const char *prefix, check_argc(argc, 0, 0); location_options_init(&location_opts, prefix); - display_options_init(&display_opts); + display_options_init_list(&display_opts); setup_auto_pager("config", 1); @@ -1321,6 +1325,7 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix) if (actions == ACTION_LIST) { check_argc(argc, 0, 0); + display_options_init_list(&display_opts); if (config_with_options(show_all_config, &display_opts, &location_opts.source, the_repository, &location_opts.options) < 0) { diff --git a/t/t1300-config.sh b/t/t1300-config.sh index 9850fcd5b5..dc744c0bae 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -2459,9 +2459,15 @@ done cat >.git/config <<-\EOF && [section] -foo = true +foo = True number = 10 big = 1M +path = ~/dir +red = red +blue = Blue +date = Fri Jun 4 15:46:55 2010 +missing=:(optional)no-such-path +exists=:(optional)expect EOF test_expect_success 'identical modern --type specifiers are allowed' ' @@ -2503,6 +2509,95 @@ test_expect_success 'unset type specifiers may be reset to conflicting ones' ' test_cmp_config 1048576 --type=bool --no-type --type=int section.big ' +test_expect_success 'list --type=int shows only canonicalizable int values' ' + cat >expect <<-EOF && + section.number=10 + section.big=1048576 + EOF + + test_must_fail git config ${mode_prefix}list --type=int +' + +test_expect_success 'list --type=bool shows only canonicalizable bool values' ' + cat >expect <<-EOF && + section.foo=true + section.number=true + section.big=true + EOF + + test_must_fail git config ${mode_prefix}list --type=bool +' + +test_expect_success 'list --type=bool-or-int shows only canonicalizable values' ' + cat >expect <<-EOF && + section.foo=true + section.number=10 + section.big=1048576 + EOF + + test_must_fail git config ${mode_prefix}list --type=bool-or-int +' + +test_expect_success 'list --type=path shows only canonicalizable path values' ' + # TODO: handling of missing path is incorrect here. + cat >expect <<-EOF && + section.foo=True + section.number=10 + section.big=1M + section.path=$HOME/dir + section.red=red + section.blue=Blue + section.date=Fri Jun 4 15:46:55 2010 + section.missing=section.exists=expect + EOF + + git config ${mode_prefix}list --type=path >actual 2>err && + test_cmp expect actual && + test_must_be_empty err +' + +test_expect_success 'list --type=expiry-date shows only canonicalizable dates' ' + cat >expecterr <<-EOF && + error: '\''True'\'' for '\''section.foo'\'' is not a valid timestamp + error: '\''~/dir'\'' for '\''section.path'\'' is not a valid timestamp + error: '\''red'\'' for '\''section.red'\'' is not a valid timestamp + error: '\''Blue'\'' for '\''section.blue'\'' is not a valid timestamp + error: '\'':(optional)no-such-path'\'' for '\''section.missing'\'' is not a valid timestamp + error: '\'':(optional)expect'\'' for '\''section.exists'\'' is not a valid timestamp + EOF + + git config ${mode_prefix}list --type=expiry-date >actual 2>err && + + # section.number and section.big parse as relative dates that could + # have clock skew in their results. + test_grep section.big actual && + test_grep section.number actual && + test_grep "section.date=$(git config --type=expiry-date section.$key)" actual && + test_cmp expecterr err +' + +test_expect_success 'list --type=color shows only canonicalizable color values' ' + cat >expect <<-EOF && + section.number=<> + section.red= + section.blue= + EOF + + cat >expecterr <<-EOF && + error: invalid color value: True + error: invalid color value: 1M + error: invalid color value: ~/dir + error: invalid color value: Fri Jun 4 15:46:55 2010 + error: invalid color value: :(optional)no-such-path + error: invalid color value: :(optional)expect + EOF + + git config ${mode_prefix}list --type=color >actual.raw 2>err && + test_decode_color actual && + test_cmp expect actual && + test_cmp expecterr err +' + test_expect_success '--type rejects unknown specifiers' ' test_must_fail git config --type=nonsense section.foo 2>error && test_grep "unrecognized --type argument" error From d744923fefb294c835d18883bac62f85ff55fc9f Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 23 Feb 2026 12:26:46 +0000 Subject: [PATCH 04/13] config: format int64s gently Move the logic for formatting int64 config values into a helper method and use gentle parsing when needed. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- builtin/config.c | 27 +++++++++++++++++++++++---- t/t1300-config.sh | 4 +++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 4c4c791883..448b148563 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -237,6 +237,25 @@ struct strbuf_list { int alloc; }; +static int format_config_int64(struct strbuf *buf, + const char *key_, + const char *value_, + const struct key_value_info *kvi, + int gently) +{ + int64_t v = 0; + if (gently) { + if (!git_parse_int64(value_, &v)) + return -1; + } else { + /* may die() */ + v = git_config_int64(key_, value_ ? value_ : "", kvi); + } + + strbuf_addf(buf, "%"PRId64, v); + return 0; +} + /* * Format the configuration key-value pair (`key_`, `value_`) and * append it into strbuf `buf`. Returns a negative value on failure, @@ -249,8 +268,9 @@ struct strbuf_list { static int format_config(const struct config_display_options *opts, struct strbuf *buf, const char *key_, const char *value_, const struct key_value_info *kvi, - int gently UNUSED) + int gently) { + int res = 0; if (opts->show_scope) show_config_scope(opts, kvi, buf); if (opts->show_origin) @@ -262,8 +282,7 @@ static int format_config(const struct config_display_options *opts, strbuf_addch(buf, opts->key_delim); if (opts->type == TYPE_INT) - strbuf_addf(buf, "%"PRId64, - git_config_int64(key_, value_ ? value_ : "", kvi)); + res = format_config_int64(buf, key_, value_, kvi, gently); else if (opts->type == TYPE_BOOL) strbuf_addstr(buf, git_config_bool(key_, value_) ? "true" : "false"); @@ -309,7 +328,7 @@ static int format_config(const struct config_display_options *opts, } } strbuf_addch(buf, opts->term); - return 0; + return res; } static int show_all_config(const char *key_, const char *value_, diff --git a/t/t1300-config.sh b/t/t1300-config.sh index dc744c0bae..05a812fd6d 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -2515,7 +2515,9 @@ test_expect_success 'list --type=int shows only canonicalizable int values' ' section.big=1048576 EOF - test_must_fail git config ${mode_prefix}list --type=int + git config ${mode_prefix}list --type=int >actual 2>err && + test_cmp expect actual && + test_must_be_empty err ' test_expect_success 'list --type=bool shows only canonicalizable bool values' ' From 53959a8ba22d80f78daa693dfc2f76fd3afe80e2 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 23 Feb 2026 12:26:47 +0000 Subject: [PATCH 05/13] config: format bools gently Move the logic for formatting bool config values into a helper method and use gentle parsing when needed. This makes 'git config list --type=bool' not fail when coming across a non-boolean value. Such unparseable values are filtered out quietly. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- builtin/config.c | 21 +++++++++++++++++++-- t/t1300-config.sh | 4 +++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 448b148563..d8b38c51d3 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -256,6 +256,24 @@ static int format_config_int64(struct strbuf *buf, return 0; } +static int format_config_bool(struct strbuf *buf, + const char *key_, + const char *value_, + int gently) +{ + int v = 0; + if (gently) { + if ((v = git_parse_maybe_bool(value_)) < 0) + return -1; + } else { + /* may die() */ + v = git_config_bool(key_, value_); + } + + strbuf_addstr(buf, v ? "true" : "false"); + return 0; +} + /* * Format the configuration key-value pair (`key_`, `value_`) and * append it into strbuf `buf`. Returns a negative value on failure, @@ -284,8 +302,7 @@ static int format_config(const struct config_display_options *opts, if (opts->type == TYPE_INT) res = format_config_int64(buf, key_, value_, kvi, gently); else if (opts->type == TYPE_BOOL) - strbuf_addstr(buf, git_config_bool(key_, value_) ? - "true" : "false"); + res = format_config_bool(buf, key_, value_, gently); else if (opts->type == TYPE_BOOL_OR_INT) { int is_bool, v; v = git_config_bool_or_int(key_, value_, kvi, diff --git a/t/t1300-config.sh b/t/t1300-config.sh index 05a812fd6d..568cfaa3c5 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -2527,7 +2527,9 @@ test_expect_success 'list --type=bool shows only canonicalizable bool values' ' section.big=true EOF - test_must_fail git config ${mode_prefix}list --type=bool + git config ${mode_prefix}list --type=bool >actual 2>err && + test_cmp expect actual && + test_must_be_empty err ' test_expect_success 'list --type=bool-or-int shows only canonicalizable values' ' From 5fb7bdcca98e63fedee22f16a34ab5fadbee54e0 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 23 Feb 2026 12:26:48 +0000 Subject: [PATCH 06/13] config: format bools or ints gently Move the logic for formatting bool-or-int config values into a helper method and use gentle parsing when needed. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- builtin/config.c | 40 +++++++++++++++++++++++++++++++--------- t/t1300-config.sh | 4 +++- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index d8b38c51d3..491a880e56 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -274,6 +274,34 @@ static int format_config_bool(struct strbuf *buf, return 0; } +static int format_config_bool_or_int(struct strbuf *buf, + const char *key_, + const char *value_, + const struct key_value_info *kvi, + int gently) +{ + int v, is_bool = 0; + + if (gently) { + v = git_parse_maybe_bool_text(value_); + + if (v >= 0) + is_bool = 1; + else if (!git_parse_int(value_, &v)) + return -1; + } else { + v = git_config_bool_or_int(key_, value_, kvi, + &is_bool); + } + + if (is_bool) + strbuf_addstr(buf, v ? "true" : "false"); + else + strbuf_addf(buf, "%d", v); + + return 0; +} + /* * Format the configuration key-value pair (`key_`, `value_`) and * append it into strbuf `buf`. Returns a negative value on failure, @@ -303,15 +331,9 @@ static int format_config(const struct config_display_options *opts, res = format_config_int64(buf, key_, value_, kvi, gently); else if (opts->type == TYPE_BOOL) res = format_config_bool(buf, key_, value_, gently); - else if (opts->type == TYPE_BOOL_OR_INT) { - int is_bool, v; - v = git_config_bool_or_int(key_, value_, kvi, - &is_bool); - if (is_bool) - strbuf_addstr(buf, v ? "true" : "false"); - else - strbuf_addf(buf, "%d", v); - } else if (opts->type == TYPE_BOOL_OR_STR) { + else if (opts->type == TYPE_BOOL_OR_INT) + res = format_config_bool_or_int(buf, key_, value_, kvi, gently); + else if (opts->type == TYPE_BOOL_OR_STR) { int v = git_parse_maybe_bool(value_); if (v < 0) strbuf_addstr(buf, value_); diff --git a/t/t1300-config.sh b/t/t1300-config.sh index 568cfaa3c5..1fc8e788ee 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -2539,7 +2539,9 @@ test_expect_success 'list --type=bool-or-int shows only canonicalizable values' section.big=1048576 EOF - test_must_fail git config ${mode_prefix}list --type=bool-or-int + git config ${mode_prefix}list --type=bool-or-int >actual 2>err && + test_cmp expect actual && + test_must_be_empty err ' test_expect_success 'list --type=path shows only canonicalizable path values' ' From 9c7fc23c24cc0cfeaf5fe32a96fbfe2709a3f93d Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 23 Feb 2026 12:26:49 +0000 Subject: [PATCH 07/13] config: format bools or strings in helper Move the logic for formatting bool-or-string config values into a helper. This parsing has always been gentle, so this is not unlocking new behavior. This extraction is only to match the formatting of the other cases that do need a behavior change. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- builtin/config.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 491a880e56..79c139c5b0 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -302,6 +302,18 @@ static int format_config_bool_or_int(struct strbuf *buf, return 0; } +/* This mode is always gentle. */ +static int format_config_bool_or_str(struct strbuf *buf, + const char *value_) +{ + int v = git_parse_maybe_bool(value_); + if (v < 0) + strbuf_addstr(buf, value_); + else + strbuf_addstr(buf, v ? "true" : "false"); + return 0; +} + /* * Format the configuration key-value pair (`key_`, `value_`) and * append it into strbuf `buf`. Returns a negative value on failure, @@ -333,13 +345,9 @@ static int format_config(const struct config_display_options *opts, res = format_config_bool(buf, key_, value_, gently); else if (opts->type == TYPE_BOOL_OR_INT) res = format_config_bool_or_int(buf, key_, value_, kvi, gently); - else if (opts->type == TYPE_BOOL_OR_STR) { - int v = git_parse_maybe_bool(value_); - if (v < 0) - strbuf_addstr(buf, value_); - else - strbuf_addstr(buf, v ? "true" : "false"); - } else if (opts->type == TYPE_PATH) { + else if (opts->type == TYPE_BOOL_OR_STR) + res = format_config_bool_or_str(buf, value_); + else if (opts->type == TYPE_PATH) { char *v; if (git_config_pathname(&v, key_, value_) < 0) return -1; From bcfb9128c9ce87dfeacaffe051257f7a5fc866e9 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 23 Feb 2026 12:26:50 +0000 Subject: [PATCH 08/13] config: format paths gently Move the logic for formatting path config values into a helper method and use gentle parsing when needed. We need to be careful about how to handle the ':(optional)' macro, which as tested in t1311-config-optional.sh must allow for ignoring a missing path when other multiple values exist, but cause 'git config get' to fail if it is the only possible value and thus no result is output. In the case of our list, we need to omit those values silently. This necessitates the use of the 'gently' parameter here. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- builtin/config.c | 32 ++++++++++++++++++++++---------- t/t1300-config.sh | 3 +-- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 79c139c5b0..2828b6dcf1 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -314,6 +314,25 @@ static int format_config_bool_or_str(struct strbuf *buf, return 0; } +static int format_config_path(struct strbuf *buf, + const char *key_, + const char *value_, + int gently) +{ + char *v; + + if (git_config_pathname(&v, key_, value_) < 0) + return -1; + + if (v) + strbuf_addstr(buf, v); + else + return gently ? -1 : 1; /* :(optional)no-such-file */ + + free(v); + return 0; +} + /* * Format the configuration key-value pair (`key_`, `value_`) and * append it into strbuf `buf`. Returns a negative value on failure, @@ -347,16 +366,9 @@ static int format_config(const struct config_display_options *opts, res = format_config_bool_or_int(buf, key_, value_, kvi, gently); else if (opts->type == TYPE_BOOL_OR_STR) res = format_config_bool_or_str(buf, value_); - else if (opts->type == TYPE_PATH) { - char *v; - if (git_config_pathname(&v, key_, value_) < 0) - return -1; - if (v) - strbuf_addstr(buf, v); - else - return 1; /* :(optional)no-such-file */ - free((char *)v); - } else if (opts->type == TYPE_EXPIRY_DATE) { + else if (opts->type == TYPE_PATH) + res = format_config_path(buf, key_, value_, gently); + else if (opts->type == TYPE_EXPIRY_DATE) { timestamp_t t; if (git_config_expiry_date(&t, key_, value_) < 0) return -1; diff --git a/t/t1300-config.sh b/t/t1300-config.sh index 1fc8e788ee..48d9c554d8 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -2545,7 +2545,6 @@ test_expect_success 'list --type=bool-or-int shows only canonicalizable values' ' test_expect_success 'list --type=path shows only canonicalizable path values' ' - # TODO: handling of missing path is incorrect here. cat >expect <<-EOF && section.foo=True section.number=10 @@ -2554,7 +2553,7 @@ test_expect_success 'list --type=path shows only canonicalizable path values' ' section.red=red section.blue=Blue section.date=Fri Jun 4 15:46:55 2010 - section.missing=section.exists=expect + section.exists=expect EOF git config ${mode_prefix}list --type=path >actual 2>err && From 9cb4a5e1ba3e586e77b1c026d509f284b4c55764 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 23 Feb 2026 12:26:51 +0000 Subject: [PATCH 09/13] config: format expiry dates quietly Move the logic for formatting expiry date config values into a helper method and use quiet parsing when needed. Note that git_config_expiry_date() will show an error on a bad parse and not die() like most other git_config...() parsers. Thus, we use 'quietly' here instead of 'gently'. There is an unfortunate asymmetry in these two parsing methods, but we need to treat a positive response from parse_expiry_date() as an error or we will get incorrect values. This updates the behavior of 'git config list --type=expiry-date' to be quiet when attempting parsing on non-date values. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- builtin/config.c | 27 +++++++++++++++++++++------ t/t1300-config.sh | 11 +---------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 2828b6dcf1..ee77ddc87c 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -3,6 +3,7 @@ #include "abspath.h" #include "config.h" #include "color.h" +#include "date.h" #include "editor.h" #include "environment.h" #include "gettext.h" @@ -333,6 +334,23 @@ static int format_config_path(struct strbuf *buf, return 0; } +static int format_config_expiry_date(struct strbuf *buf, + const char *key_, + const char *value_, + int quietly) +{ + timestamp_t t; + if (quietly) { + if (parse_expiry_date(value_, &t)) + return -1; + } else if (git_config_expiry_date(&t, key_, value_) < 0) { + return -1; + } + + strbuf_addf(buf, "%"PRItime, t); + return 0; +} + /* * Format the configuration key-value pair (`key_`, `value_`) and * append it into strbuf `buf`. Returns a negative value on failure, @@ -368,12 +386,9 @@ static int format_config(const struct config_display_options *opts, res = format_config_bool_or_str(buf, value_); else if (opts->type == TYPE_PATH) res = format_config_path(buf, key_, value_, gently); - else if (opts->type == TYPE_EXPIRY_DATE) { - timestamp_t t; - if (git_config_expiry_date(&t, key_, value_) < 0) - return -1; - strbuf_addf(buf, "%"PRItime, t); - } else if (opts->type == TYPE_COLOR) { + else if (opts->type == TYPE_EXPIRY_DATE) + res = format_config_expiry_date(buf, key_, value_, gently); + else if (opts->type == TYPE_COLOR) { char v[COLOR_MAXLEN]; if (git_config_color(v, key_, value_) < 0) return -1; diff --git a/t/t1300-config.sh b/t/t1300-config.sh index 48d9c554d8..72bdd6ab03 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -2562,15 +2562,6 @@ test_expect_success 'list --type=path shows only canonicalizable path values' ' ' test_expect_success 'list --type=expiry-date shows only canonicalizable dates' ' - cat >expecterr <<-EOF && - error: '\''True'\'' for '\''section.foo'\'' is not a valid timestamp - error: '\''~/dir'\'' for '\''section.path'\'' is not a valid timestamp - error: '\''red'\'' for '\''section.red'\'' is not a valid timestamp - error: '\''Blue'\'' for '\''section.blue'\'' is not a valid timestamp - error: '\'':(optional)no-such-path'\'' for '\''section.missing'\'' is not a valid timestamp - error: '\'':(optional)expect'\'' for '\''section.exists'\'' is not a valid timestamp - EOF - git config ${mode_prefix}list --type=expiry-date >actual 2>err && # section.number and section.big parse as relative dates that could @@ -2578,7 +2569,7 @@ test_expect_success 'list --type=expiry-date shows only canonicalizable dates' ' test_grep section.big actual && test_grep section.number actual && test_grep "section.date=$(git config --type=expiry-date section.$key)" actual && - test_cmp expecterr err + test_must_be_empty err ' test_expect_success 'list --type=color shows only canonicalizable color values' ' From db45e4908d6711ddc3094cb079c85278691c8267 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 23 Feb 2026 12:26:52 +0000 Subject: [PATCH 10/13] color: add color_parse_quietly() When parsing colors, a failed parse leads to an error message due to the result returning error(). To allow for quiet parsing, create color_parse_quietly(). This is in contrast to an ..._gently() version because the original does not die(), so both options are technically 'gentle'. To accomplish this, convert the implementation of color_parse_mem() into a static color_parse_mem_1() helper that adds a 'quiet' parameter. The color_parse_quietly() method can then use this. Since it is a near equivalent to color_parse(), move that method down in the file so they can be nearby while also appearing after color_parse_mem_1(). Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- color.c | 25 ++++++++++++++++++------- color.h | 1 + 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/color.c b/color.c index 07ac8c9d40..00b53f97ac 100644 --- a/color.c +++ b/color.c @@ -223,11 +223,6 @@ static int parse_attr(const char *name, size_t len) return -1; } -int color_parse(const char *value, char *dst) -{ - return color_parse_mem(value, strlen(value), dst); -} - /* * Write the ANSI color codes for "c" to "out"; the string should * already have the ANSI escape code in it. "out" should have enough @@ -264,7 +259,8 @@ static int color_empty(const struct color *c) return c->type <= COLOR_NORMAL; } -int color_parse_mem(const char *value, int value_len, char *dst) +static int color_parse_mem_1(const char *value, int value_len, + char *dst, int quiet) { const char *ptr = value; int len = value_len; @@ -365,10 +361,25 @@ int color_parse_mem(const char *value, int value_len, char *dst) OUT(0); return 0; bad: - return error(_("invalid color value: %.*s"), value_len, value); + return quiet ? -1 : error(_("invalid color value: %.*s"), value_len, value); #undef OUT } +int color_parse_mem(const char *value, int value_len, char *dst) +{ + return color_parse_mem_1(value, value_len, dst, 0); +} + +int color_parse(const char *value, char *dst) +{ + return color_parse_mem(value, strlen(value), dst); +} + +int color_parse_quietly(const char *value, char *dst) +{ + return color_parse_mem_1(value, strlen(value), dst, 1); +} + enum git_colorbool git_config_colorbool(const char *var, const char *value) { if (value) { diff --git a/color.h b/color.h index 43e6c9ad09..0d72540300 100644 --- a/color.h +++ b/color.h @@ -118,6 +118,7 @@ bool want_color_fd(int fd, enum git_colorbool var); * terminal. */ int color_parse(const char *value, char *dst); +int color_parse_quietly(const char *value, char *dst); int color_parse_mem(const char *value, int len, char *dst); /* From 2d4ab5a885f365cb66156ff4553f88c000dfa307 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 23 Feb 2026 12:26:53 +0000 Subject: [PATCH 11/13] config: format colors quietly Move the logic for formatting color config value into a helper method and use quiet parsing when needed. This removes error messages when parsing a list of config values that do not match color formats. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- builtin/config.c | 27 +++++++++++++++++++++------ t/t1300-config.sh | 11 +---------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index ee77ddc87c..45304076dc 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -351,6 +351,24 @@ static int format_config_expiry_date(struct strbuf *buf, return 0; } +static int format_config_color(struct strbuf *buf, + const char *key_, + const char *value_, + int gently) +{ + char v[COLOR_MAXLEN]; + + if (gently) { + if (color_parse_quietly(value_, v) < 0) + return -1; + } else if (git_config_color(v, key_, value_) < 0) { + return -1; + } + + strbuf_addstr(buf, v); + return 0; +} + /* * Format the configuration key-value pair (`key_`, `value_`) and * append it into strbuf `buf`. Returns a negative value on failure, @@ -388,12 +406,9 @@ static int format_config(const struct config_display_options *opts, res = format_config_path(buf, key_, value_, gently); else if (opts->type == TYPE_EXPIRY_DATE) res = format_config_expiry_date(buf, key_, value_, gently); - else if (opts->type == TYPE_COLOR) { - char v[COLOR_MAXLEN]; - if (git_config_color(v, key_, value_) < 0) - return -1; - strbuf_addstr(buf, v); - } else if (value_) { + else if (opts->type == TYPE_COLOR) + res = format_config_color(buf, key_, value_, gently); + else if (value_) { strbuf_addstr(buf, value_); } else { /* Just show the key name; back out delimiter */ diff --git a/t/t1300-config.sh b/t/t1300-config.sh index 72bdd6ab03..128971ee12 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -2579,19 +2579,10 @@ test_expect_success 'list --type=color shows only canonicalizable color values' section.blue= EOF - cat >expecterr <<-EOF && - error: invalid color value: True - error: invalid color value: 1M - error: invalid color value: ~/dir - error: invalid color value: Fri Jun 4 15:46:55 2010 - error: invalid color value: :(optional)no-such-path - error: invalid color value: :(optional)expect - EOF - git config ${mode_prefix}list --type=color >actual.raw 2>err && test_decode_color actual && test_cmp expect actual && - test_cmp expecterr err + test_must_be_empty err ' test_expect_success '--type rejects unknown specifiers' ' From 645f92a3e9179ebf1ed42dc4fa05cc8dd71e3e9c Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 23 Feb 2026 12:26:54 +0000 Subject: [PATCH 12/13] config: restructure format_config() The recent changes have replaced the bodies of most if/else-if cases with simple helper method calls. This makes it easy to adapt the structure into a clearer switch statement, leaving a simple if/else in the default case. Make things a little simpler to read by reducing the nesting depth via a new goto statement when we want to skip values. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- builtin/config.c | 62 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 45304076dc..2e8bc6590c 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -124,6 +124,7 @@ struct config_display_options { .key_delim = ' ', \ } +#define TYPE_NONE 0 #define TYPE_BOOL 1 #define TYPE_INT 2 #define TYPE_BOOL_OR_INT 3 @@ -390,32 +391,57 @@ static int format_config(const struct config_display_options *opts, show_config_origin(opts, kvi, buf); if (opts->show_keys) strbuf_addstr(buf, key_); - if (!opts->omit_values) { - if (opts->show_keys) - strbuf_addch(buf, opts->key_delim); - if (opts->type == TYPE_INT) - res = format_config_int64(buf, key_, value_, kvi, gently); - else if (opts->type == TYPE_BOOL) - res = format_config_bool(buf, key_, value_, gently); - else if (opts->type == TYPE_BOOL_OR_INT) - res = format_config_bool_or_int(buf, key_, value_, kvi, gently); - else if (opts->type == TYPE_BOOL_OR_STR) - res = format_config_bool_or_str(buf, value_); - else if (opts->type == TYPE_PATH) - res = format_config_path(buf, key_, value_, gently); - else if (opts->type == TYPE_EXPIRY_DATE) - res = format_config_expiry_date(buf, key_, value_, gently); - else if (opts->type == TYPE_COLOR) - res = format_config_color(buf, key_, value_, gently); - else if (value_) { + if (opts->omit_values) + goto terminator; + + if (opts->show_keys) + strbuf_addch(buf, opts->key_delim); + + switch (opts->type) { + case TYPE_INT: + res = format_config_int64(buf, key_, value_, kvi, gently); + break; + + case TYPE_BOOL: + res = format_config_bool(buf, key_, value_, gently); + break; + + case TYPE_BOOL_OR_INT: + res = format_config_bool_or_int(buf, key_, value_, kvi, gently); + break; + + case TYPE_BOOL_OR_STR: + res = format_config_bool_or_str(buf, value_); + break; + + case TYPE_PATH: + res = format_config_path(buf, key_, value_, gently); + break; + + case TYPE_EXPIRY_DATE: + res = format_config_expiry_date(buf, key_, value_, gently); + break; + + case TYPE_COLOR: + res = format_config_color(buf, key_, value_, gently); + break; + + case TYPE_NONE: + if (value_) { strbuf_addstr(buf, value_); } else { /* Just show the key name; back out delimiter */ if (opts->show_keys) strbuf_setlen(buf, buf->len - 1); } + break; + + default: + BUG("undefined type %d", opts->type); } + +terminator: strbuf_addch(buf, opts->term); return res; } From 096aa6099834ce00401a369b34cbff4868ea5704 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 23 Feb 2026 12:26:55 +0000 Subject: [PATCH 13/13] config: use an enum for type The --type= option for 'git config' has previously been defined using macros, but using a typed enum is better for tracking the possible values. Move the definition up to make sure it is defined before a macro uses some of its terms. Update the initializer for config_display_options to explicitly set 'type' to TYPE_NONE even though this is implied by a zero value. This assists in knowing that the switch statement added in the previous change has a complete set of cases for a properly-valued enum. Signed-off-by: Derrick Stolee Signed-off-by: Junio C Hamano --- builtin/config.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 2e8bc6590c..7c4857be62 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -86,6 +86,17 @@ struct config_location_options { .respect_includes_opt = -1, \ } +enum config_type { + TYPE_NONE = 0, + TYPE_BOOL, + TYPE_INT, + TYPE_BOOL_OR_INT, + TYPE_PATH, + TYPE_EXPIRY_DATE, + TYPE_COLOR, + TYPE_BOOL_OR_STR, +}; + #define CONFIG_TYPE_OPTIONS(type) \ OPT_GROUP(N_("Type")), \ OPT_CALLBACK('t', "type", &type, N_("type"), N_("value is given this type"), option_parse_type), \ @@ -111,7 +122,7 @@ struct config_display_options { int show_origin; int show_scope; int show_keys; - int type; + enum config_type type; char *default_value; /* Populated via `display_options_init()`. */ int term; @@ -122,17 +133,9 @@ struct config_display_options { .term = '\n', \ .delim = '=', \ .key_delim = ' ', \ + .type = TYPE_NONE, \ } -#define TYPE_NONE 0 -#define TYPE_BOOL 1 -#define TYPE_INT 2 -#define TYPE_BOOL_OR_INT 3 -#define TYPE_PATH 4 -#define TYPE_EXPIRY_DATE 5 -#define TYPE_COLOR 6 -#define TYPE_BOOL_OR_STR 7 - #define OPT_CALLBACK_VALUE(s, l, v, h, i) { \ .type = OPTION_CALLBACK, \ .short_name = (s), \