From bca83d8ca86ead4df5cd424a8356367915248c43 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 9 Feb 2026 21:53:41 -0500 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 --- builtin/config.c | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 288ebdfdaaab1c..237f7a934d2f12 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 93c94a1b257d26a23942e902630c220e4a4f8eaf Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Fri, 13 Feb 2026 11:20:55 -0500 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 --- builtin/config.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 237f7a934d2f12..b4c4228311dde1 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 6d2a48a3b7f61c068392e66933caaf1d78055857 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 9 Feb 2026 22:47:06 -0500 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. If there is an error in parsing, then the row is not output. 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 --- Documentation/git-config.adoc | 3 ++ builtin/config.c | 35 ++++++++++-------- t/t1300-config.sh | 70 ++++++++++++++++++++++++++++++++++- 3 files changed, 92 insertions(+), 16 deletions(-) diff --git a/Documentation/git-config.adoc b/Documentation/git-config.adoc index ac3b536a155c6e..5300dd4c51257a 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 b4c4228311dde1..4c4c79188382a3 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 9850fcd5b567a5..362e580604c50b 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -2459,9 +2459,13 @@ 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 EOF test_expect_success 'identical modern --type specifiers are allowed' ' @@ -2503,6 +2507,70 @@ 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=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=path shows only canonicalizable path values' ' + 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 + 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 + 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 + 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 2bca4d231686e33ea9d4d85b10fcffd60a63ad46 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Fri, 13 Feb 2026 11:26:26 -0500 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 --- builtin/config.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 4c4c79188382a3..d259a91d53caed 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_, From f8e0b8304fcff1d380029542fe2915c2d67a92e3 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Fri, 13 Feb 2026 11:36:14 -0500 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 --- 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 d259a91d53caed..2c169fc1265d56 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 362e580604c50b..59a82b9aefecc0 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -2514,7 +2514,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=path shows only canonicalizable path values' ' From 0a428d2ffe092c2af6789b5e698dc769b0eb883a Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Fri, 13 Feb 2026 11:41:38 -0500 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 --- builtin/config.c | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 2c169fc1265d56..2c93e1725b91dd 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_); From 3fec3abbd62307bec885cab0198007c13f0f1d8e Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Fri, 13 Feb 2026 11:44:05 -0500 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 --- builtin/config.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 2c93e1725b91dd..0c539ff98e92ca 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 fafafc5465979dc62b7c8253a6d2053bc0aa171a Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Mon, 9 Feb 2026 22:41:55 -0500 Subject: [PATCH 08/13] parse: add git_parse_maybe_pathname() The git_config_pathname() method parses a config value as a path, but always die()s on an error. Move this logic into a gentler parsing algorithm that will return an error value instead of ending the process. Signed-off-by: Derrick Stolee --- config.c | 14 +------------- parse.c | 24 ++++++++++++++++++++++++ parse.h | 2 ++ 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/config.c b/config.c index 7f6d53b4737cd8..83257b7a9798bb 100644 --- a/config.c +++ b/config.c @@ -1278,24 +1278,12 @@ int git_config_string(char **dest, const char *var, const char *value) int git_config_pathname(char **dest, const char *var, const char *value) { - bool is_optional; - char *path; - if (!value) return config_error_nonbool(var); - is_optional = skip_prefix(value, ":(optional)", &value); - path = interpolate_path(value, 0); - if (!path) + if (git_parse_maybe_pathname(value, dest) < 0) die(_("failed to expand user dir in: '%s'"), value); - if (is_optional && is_missing_file(path)) { - free(path); - *dest = NULL; - return 0; - } - - *dest = path; return 0; } diff --git a/parse.c b/parse.c index 48313571aab129..3f37f0b93a14b9 100644 --- a/parse.c +++ b/parse.c @@ -1,6 +1,7 @@ #include "git-compat-util.h" #include "gettext.h" #include "parse.h" +#include "path.h" static uintmax_t get_unit_factor(const char *end) { @@ -209,3 +210,26 @@ unsigned long git_env_ulong(const char *k, unsigned long val) die(_("failed to parse %s"), k); return val; } + +int git_parse_maybe_pathname(const char *value, char **dest) +{ + bool is_optional; + char *path; + + if (!value) + return -1; + + is_optional = skip_prefix(value, ":(optional)", &value); + path = interpolate_path(value, 0); + if (!path) + return -1; + + if (is_optional && is_missing_file(path)) { + free(path); + *dest = NULL; + return 0; + } + + *dest = path; + return 0; +} diff --git a/parse.h b/parse.h index ea32de9a91fbfb..4f97c3727a5e0e 100644 --- a/parse.h +++ b/parse.h @@ -19,4 +19,6 @@ int git_parse_maybe_bool_text(const char *value); int git_env_bool(const char *, int); unsigned long git_env_ulong(const char *, unsigned long); +int git_parse_maybe_pathname(const char *value, char **dest); + #endif /* PARSE_H */ From d1cfa0c5e1295d7b6655a0962bd47f8e89bc4a04 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Fri, 13 Feb 2026 11:47:59 -0500 Subject: [PATCH 09/13] config: format paths gently Move the logic for formatting path config values into a helper method and use gentle parsing when needed. Signed-off-by: Derrick Stolee --- builtin/config.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 0c539ff98e92ca..4664651dd28c94 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -314,6 +314,28 @@ 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 (gently) { + if (git_parse_maybe_pathname(value_, &v) < 0) + return -1; + } else if (git_config_pathname(&v, key_, value_) < 0) { + return -1; + } + + if (v) + strbuf_addstr(buf, v); + else + return 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 +369,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; From 9221ca2352d65f3cbdec00f7577089c734799919 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Fri, 13 Feb 2026 11:50:30 -0500 Subject: [PATCH 10/13] config: format expiry dates gently Move the logic for formatting expiry date config values into a helper method and use gentle parsing when needed. 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 --- builtin/config.c | 27 +++++++++++++++++++++------ t/t1300-config.sh | 9 +-------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 4664651dd28c94..71b685d94380c4 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" @@ -336,6 +337,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 gently) +{ + timestamp_t t; + if (gently) { + 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, @@ -371,12 +389,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 59a82b9aefecc0..c134d85d8afe5b 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -2536,13 +2536,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 - EOF - git config ${mode_prefix}list --type=expiry-date >actual 2>err && # section.number and section.big parse as relative dates that could @@ -2550,7 +2543,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 ddf6131ac9283bfbf4ec9ec000606765d887e6ec Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Fri, 13 Feb 2026 12:21:52 -0500 Subject: [PATCH 11/13] color: add color_parse_gently() 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_gently(). To accomplish this, convert the implementation of color_parse_mem() into a static color_parse_mem_1() helper that adds a 'gently' parameter. The color_parse_gently() 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 --- color.c | 25 ++++++++++++++++++------- color.h | 1 + 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/color.c b/color.c index 07ac8c9d400906..ec8872d2dd0f3a 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 gently) { 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 gently ? -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_gently(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 43e6c9ad0972b3..30c783405d430c 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_gently(const char *value, char *dst); int color_parse_mem(const char *value, int len, char *dst); /* From d14937e6d1e4e33bb7892107061807f2b3bba3d7 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Fri, 13 Feb 2026 11:52:56 -0500 Subject: [PATCH 12/13] config: format colors gently Move the logic for formatting color config value into a helper method and use gentle 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 --- builtin/config.c | 27 +++++++++++++++++++++------ t/t1300-config.sh | 9 +-------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index 71b685d94380c4..e8c02e5f21504c 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -354,6 +354,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_gently(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, @@ -391,12 +409,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 c134d85d8afe5b..79b2ee203c37cf 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -2553,17 +2553,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 - 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 48fc882785013b129fba9b8aada6c1f2e239a4cd Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Fri, 13 Feb 2026 11:56:30 -0500 Subject: [PATCH 13/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 --- builtin/config.c | 59 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/builtin/config.c b/builtin/config.c index e8c02e5f21504c..1de3ce0eaaa5a5 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -393,25 +393,44 @@ 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; + + default: + if (value_) { strbuf_addstr(buf, value_); } else { /* Just show the key name; back out delimiter */ @@ -419,6 +438,8 @@ static int format_config(const struct config_display_options *opts, strbuf_setlen(buf, buf->len - 1); } } + +terminator: strbuf_addch(buf, opts->term); return res; }