{"thread":{"id":"64966","subject":"[PATCH 0/5] [RFC] Make 'git config list --type=' parse and filter types","startedAt":"2026-02-10T04:43:03Z","lastAt":"2026-02-23T12:27:16Z","messageCount":58,"participants":["Derrick Stolee via GitGitGadget","Junio C Hamano","Derrick Stolee","Patrick Steinhardt"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"535640","messageId":"pull.2044.git.1770698579.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":null,"subject":"[PATCH 0/5] [RFC] Make 'git config list --type=' parse and filter types","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-10T04:42:54Z","receivedAt":"2026-02-10T04:43:03Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"I started down this road based on feedback on my 'git config-batch' RFC [1].\n\n[1]\nhttps://lore.kernel.org/git/pull.2033.git.1770214803.gitgitgadget@gmail.com/\n\nI had described my intention to use 'git config-batch' as a single process\nto load multiple config values one-by-one. Brian mentioned that 'git config\nlist -z' would probably suffice, so I started experimenting in that\ndirection [2].\n\n[2]\nhttps://github.com/git-ecosystem/git-credential-manager/compare/main...derrickstolee:config-list\n\nHowever, I ran into a problem: the most critical performance bottleneck is\nrelated to path-formatted config values that are queried with 'git config\nget --type=path -z'. It wasn't hard to update things to lazily load the full\nlist of config values by type [3], but I then noticed a big problem!\n\n[3]\nhttps://github.com/git-ecosystem/git-credential-manager/commit/d403c8e24ce6f37da920cce23842dd5a6cf6481d\n\nProblem: 'git config list' doesn't respect --type=<X>!\n\nThis boils down to the fact that the iterator function show_all_config()\ndoesn't call format_config(), which includes the type-parsing code.\n\nThis wasn't super trivial to update:\n\n 1. format_config() uses git_config_parse_*() methods, which die() on a bad\n    parse.\n 2. The path parsing code didn't have a gentle version.\n 3. The two paths ('git config list' and 'git config --list') needed to\n    standardize their display options to work with format_config().\n 4. Finally, we need to filter out key-value pairs that don't match the\n    given type.\n\nThis is marked as an RFC because I need to add some more tests and because\nthis is a behavior change! If there are any tools currently passing the\n--type=<X> argument to git config list then they will have a change of\nbehavior with this series. It's an easy workaround: drop the --type argument\nor add --no-type to go back to the previous behavior.\n\nThanks for any and all feedback, -Stolee\n\nDerrick Stolee (5):\n  config: move show_all_config()\n  parse: add git_parse_maybe_pathname()\n  config: allow format_config() to filter\n  config: create special init for list mode\n  config: make 'git config list --type=<X>' work\n\n Documentation/git-config.adoc |   3 +\n builtin/config.c              | 130 ++++++++++++++++++++++++----------\n config.c                      |  14 +---\n parse.c                       |  24 +++++++\n parse.h                       |   2 +\n t/t1300-config.sh             |  26 ++++++-\n 6 files changed, 147 insertions(+), 52 deletions(-)\n\n\nbase-commit: 67ad42147a7acc2af6074753ebd03d904476118f\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2044%2Fderrickstolee%2Fconfig-list-type-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2044/derrickstolee/config-list-type-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/2044\n-- \ngitgitgadget\n"},{"id":"535641","messageId":"bca83d8ca86ead4df5cd424a8356367915248c43.1770698579.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.git.1770698579.gitgitgadget@gmail.com","subject":"[PATCH 1/5] config: move show_all_config()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-10T04:42:55Z","receivedAt":"2026-02-10T04:43:06Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nIn anticipation of using format_config() in this method, move\nshow_all_config() lower in the file without changes.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c | 48 ++++++++++++++++++++++++------------------------\n 1 file changed, 24 insertions(+), 24 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 288ebdfdaa..237f7a934d 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -231,30 +231,6 @@ static void show_config_scope(const struct config_display_options *opts,\n \tstrbuf_addch(buf, term);\n }\n \n-static int show_all_config(const char *key_, const char *value_,\n-\t\t\t   const struct config_context *ctx,\n-\t\t\t   void *cb)\n-{\n-\tconst struct config_display_options *opts = cb;\n-\tconst struct key_value_info *kvi = ctx->kvi;\n-\n-\tif (opts->show_origin || opts->show_scope) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tif (opts->show_scope)\n-\t\t\tshow_config_scope(opts, kvi, &buf);\n-\t\tif (opts->show_origin)\n-\t\t\tshow_config_origin(opts, kvi, &buf);\n-\t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n-\t\tfwrite(buf.buf, 1, buf.len, stdout);\n-\t\tstrbuf_release(&buf);\n-\t}\n-\tif (!opts->omit_values && value_)\n-\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n-\telse\n-\t\tprintf(\"%s%c\", key_, opts->term);\n-\treturn 0;\n-}\n-\n struct strbuf_list {\n \tstruct strbuf *items;\n \tint nr;\n@@ -332,6 +308,30 @@ static int format_config(const struct config_display_options *opts,\n \treturn 0;\n }\n \n+static int show_all_config(const char *key_, const char *value_,\n+\t\t\t   const struct config_context *ctx,\n+\t\t\t   void *cb)\n+{\n+\tconst struct config_display_options *opts = cb;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n+\n+\tif (opts->show_origin || opts->show_scope) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tif (opts->show_scope)\n+\t\t\tshow_config_scope(opts, kvi, &buf);\n+\t\tif (opts->show_origin)\n+\t\t\tshow_config_origin(opts, kvi, &buf);\n+\t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n+\t\tfwrite(buf.buf, 1, buf.len, stdout);\n+\t\tstrbuf_release(&buf);\n+\t}\n+\tif (!opts->omit_values && value_)\n+\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n+\telse\n+\t\tprintf(\"%s%c\", key_, opts->term);\n+\treturn 0;\n+}\n+\n #define GET_VALUE_ALL        (1 << 0)\n #define GET_VALUE_KEY_REGEXP (1 << 1)\n \n-- \ngitgitgadget\n\n"},{"id":"535642","messageId":"8d3a6a8265714c5e4bae0f2e5a587ea46a6adddc.1770698579.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.git.1770698579.gitgitgadget@gmail.com","subject":"[PATCH 2/5] parse: add git_parse_maybe_pathname()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-10T04:42:56Z","receivedAt":"2026-02-10T04:43:08Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nThis extraction of logic from config.c's git_config_pathname() allows\nfor parsing a fully-qualified path from a relative path along with\nvalidation of the existence of the path without failing with a die().\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n config.c | 14 +-------------\n parse.c  | 24 ++++++++++++++++++++++++\n parse.h  |  2 ++\n 3 files changed, 27 insertions(+), 13 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 7f6d53b473..83257b7a97 100644\n--- a/config.c\n+++ b/config.c\n@@ -1278,24 +1278,12 @@ int git_config_string(char **dest, const char *var, const char *value)\n \n int git_config_pathname(char **dest, const char *var, const char *value)\n {\n-\tbool is_optional;\n-\tchar *path;\n-\n \tif (!value)\n \t\treturn config_error_nonbool(var);\n \n-\tis_optional = skip_prefix(value, \":(optional)\", &value);\n-\tpath = interpolate_path(value, 0);\n-\tif (!path)\n+\tif (git_parse_maybe_pathname(value, dest) < 0)\n \t\tdie(_(\"failed to expand user dir in: '%s'\"), value);\n \n-\tif (is_optional && is_missing_file(path)) {\n-\t\tfree(path);\n-\t\t*dest = NULL;\n-\t\treturn 0;\n-\t}\n-\n-\t*dest = path;\n \treturn 0;\n }\n \ndiff --git a/parse.c b/parse.c\nindex 48313571aa..3f37f0b93a 100644\n--- a/parse.c\n+++ b/parse.c\n@@ -1,6 +1,7 @@\n #include \"git-compat-util.h\"\n #include \"gettext.h\"\n #include \"parse.h\"\n+#include \"path.h\"\n \n static uintmax_t get_unit_factor(const char *end)\n {\n@@ -209,3 +210,26 @@ unsigned long git_env_ulong(const char *k, unsigned long val)\n \t\tdie(_(\"failed to parse %s\"), k);\n \treturn val;\n }\n+\n+int git_parse_maybe_pathname(const char *value, char **dest)\n+{\n+\tbool is_optional;\n+\tchar *path;\n+\n+\tif (!value)\n+\t\treturn -1;\n+\n+\tis_optional = skip_prefix(value, \":(optional)\", &value);\n+\tpath = interpolate_path(value, 0);\n+\tif (!path)\n+\t\treturn -1;\n+\n+\tif (is_optional && is_missing_file(path)) {\n+\t\tfree(path);\n+\t\t*dest = NULL;\n+\t\treturn 0;\n+\t}\n+\n+\t*dest = path;\n+\treturn 0;\n+}\ndiff --git a/parse.h b/parse.h\nindex ea32de9a91..4f97c3727a 100644\n--- a/parse.h\n+++ b/parse.h\n@@ -19,4 +19,6 @@ int git_parse_maybe_bool_text(const char *value);\n int git_env_bool(const char *, int);\n unsigned long git_env_ulong(const char *, unsigned long);\n \n+int git_parse_maybe_pathname(const char *value, char **dest);\n+\n #endif /* PARSE_H */\n-- \ngitgitgadget\n\n"},{"id":"535643","messageId":"d9e04240100afb4188d32c5ce784f189f59348b4.1770698579.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.git.1770698579.gitgitgadget@gmail.com","subject":"[PATCH 3/5] config: allow format_config() to filter","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-10T04:42:57Z","receivedAt":"2026-02-10T04:43:11Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nThe format_config() method in builtin/config.c currently only uses\ngit_config_*() methods for parsing. This allows parsing errors to result\nin die() messages appropriate with keys in the error message.\n\nIn a future change we will want to use format_config() within 'git\nconfig list' to help format the output, including when --type=<X>\narguments are provided. When the parsing fails in that case, that\nkey-value pair should be omitted instead of causing a failure across the\nentire command.\n\nThis change is formatted in such a way that the if/else-if structure\nallows the default die_on_error version to appear first and then be\nfollowed by the gentle parsing mode immediately afterwards.\n\nThe only callers right now have die_on_parse set to 1.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c | 75 +++++++++++++++++++++++++++++++++++++++---------\n 1 file changed, 62 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 237f7a934d..5146abcfa3 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -3,6 +3,7 @@\n #include \"abspath.h\"\n #include \"config.h\"\n #include \"color.h\"\n+#include \"date.h\"\n #include \"editor.h\"\n #include \"environment.h\"\n #include \"gettext.h\"\n@@ -245,7 +246,8 @@ struct strbuf_list {\n  */\n static int format_config(const struct config_display_options *opts,\n \t\t\t struct strbuf *buf, const char *key_,\n-\t\t\t const char *value_, const struct key_value_info *kvi)\n+\t\t\t const char *value_, const struct key_value_info *kvi,\n+\t\t\t int die_on_parse)\n {\n \tif (opts->show_scope)\n \t\tshow_config_scope(opts, kvi, buf);\n@@ -257,27 +259,55 @@ static int format_config(const struct config_display_options *opts,\n \t\tif (opts->show_keys)\n \t\t\tstrbuf_addch(buf, opts->key_delim);\n \n-\t\tif (opts->type == TYPE_INT)\n+\t\tif (opts->type == TYPE_INT && die_on_parse) {\n \t\t\tstrbuf_addf(buf, \"%\"PRId64,\n \t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\", kvi));\n-\t\telse if (opts->type == TYPE_BOOL)\n+\t\t} else if (opts->type == TYPE_INT) {\n+\t\t\tint64_t v;\n+\t\t\tint ret = git_parse_int64(value_, &v);\n+\n+\t\t\tif (ret)\n+\t\t\t\treturn -1;\n+\n+\t\t\tstrbuf_addf(buf, \"%\"PRId64, v);\n+\t\t}\n+\t\telse if (opts->type == TYPE_BOOL && die_on_parse) {\n \t\t\tstrbuf_addstr(buf, git_config_bool(key_, value_) ?\n \t\t\t\t      \"true\" : \"false\");\n-\t\telse if (opts->type == TYPE_BOOL_OR_INT) {\n-\t\t\tint is_bool, v;\n-\t\t\tv = git_config_bool_or_int(key_, value_, kvi,\n-\t\t\t\t\t\t   &is_bool);\n+\t\t} else if (opts->type == TYPE_BOOL) {\n+\t\t\tint value = git_parse_maybe_bool(value_);\n+\n+\t\t\tif (value < 0)\n+\t\t\t\treturn -1;\n+\n+\t\t\tstrbuf_addstr(buf, value ? \"true\" : \"false\");\n+\t\t} else if (opts->type == TYPE_BOOL_OR_INT && die_on_parse) {\n+\t\t\tint is_bool = 0;\n+\t\t\tint v = git_config_bool_or_int(key_, value_, kvi,\n+\t\t\t\t\t\t       &is_bool);\n+\t\t\tif (is_bool)\n+\t\t\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n+\t\t\telse\n+\t\t\t\tstrbuf_addf(buf, \"%d\", v);\n+\t\t} else if (opts->type == TYPE_BOOL_OR_INT) {\n+\t\t\tint is_bool = 0;\n+\t\t\tint v = git_parse_maybe_bool_text(value_);\n+\n+\t\t\tif (v < 0)\n+\t\t\t\treturn -1;\n+\n \t\t\tif (is_bool)\n \t\t\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n \t\t\telse\n \t\t\t\tstrbuf_addf(buf, \"%d\", v);\n \t\t} else if (opts->type == TYPE_BOOL_OR_STR) {\n+\t\t\t/* Note: this can't fail to parse! */\n \t\t\tint v = git_parse_maybe_bool(value_);\n \t\t\tif (v < 0)\n \t\t\t\tstrbuf_addstr(buf, value_);\n \t\t\telse\n \t\t\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n-\t\t} else if (opts->type == TYPE_PATH) {\n+\t\t} else if (opts->type == TYPE_PATH && die_on_parse) {\n \t\t\tchar *v;\n \t\t\tif (git_config_pathname(&v, key_, value_) < 0)\n \t\t\t\treturn -1;\n@@ -286,16 +316,35 @@ static int format_config(const struct config_display_options *opts,\n \t\t\telse\n \t\t\t\treturn 1; /* :(optional)no-such-file */\n \t\t\tfree((char *)v);\n-\t\t} else if (opts->type == TYPE_EXPIRY_DATE) {\n+\t\t} else if (opts->type == TYPE_PATH) {\n+\t\t\tchar *v;\n+\t\t\tif (git_parse_maybe_pathname(value_, &v) < 0)\n+\t\t\t\treturn -1;\n+\t\t\tif (v)\n+\t\t\t\tstrbuf_addstr(buf, v);\n+\t\t\telse\n+\t\t\t\treturn 1; /* :(optional)no-such-file */\n+\t\t\tfree((char *)v);\n+\t\t} else if (opts->type == TYPE_EXPIRY_DATE && die_on_parse) {\n \t\t\ttimestamp_t t;\n \t\t\tif (git_config_expiry_date(&t, key_, value_) < 0)\n \t\t\t\treturn -1;\n \t\t\tstrbuf_addf(buf, \"%\"PRItime, t);\n-\t\t} else if (opts->type == TYPE_COLOR) {\n+\t\t} else if (opts->type == TYPE_EXPIRY_DATE) {\n+\t\t\ttimestamp_t t;\n+\t\t\tif (parse_expiry_date(value_, &t) < 0)\n+\t\t\t\treturn -1;\n+\t\t\tstrbuf_addf(buf, \"%\"PRItime, t);\n+\t\t} else if (opts->type == TYPE_COLOR && die_on_parse) {\n \t\t\tchar v[COLOR_MAXLEN];\n \t\t\tif (git_config_color(v, key_, value_) < 0)\n \t\t\t\treturn -1;\n \t\t\tstrbuf_addstr(buf, v);\n+\t\t} else if (opts->type == TYPE_COLOR) {\n+\t\t\tchar v[COLOR_MAXLEN];\n+\t\t\tif (color_parse(value_, v) < 0)\n+\t\t\t\treturn -1;\n+\t\t\tstrbuf_addstr(buf, v);\n \t\t} else if (value_) {\n \t\t\tstrbuf_addstr(buf, value_);\n \t\t} else {\n@@ -372,7 +421,7 @@ static int collect_config(const char *key_, const char *value_,\n \tstrbuf_init(&values->items[values->nr], 0);\n \n \tstatus = format_config(data->display_opts, &values->items[values->nr++],\n-\t\t\t       key_, value_, kvi);\n+\t\t\t       key_, value_, kvi, 1);\n \tif (status < 0)\n \t\treturn status;\n \tif (status) {\n@@ -463,7 +512,7 @@ static int get_value(const struct config_location_options *opts,\n \t\tstrbuf_init(item, 0);\n \n \t\tstatus = format_config(display_opts, item, key_,\n-\t\t\t\t       display_opts->default_value, &kvi);\n+\t\t\t\t       display_opts->default_value, &kvi, 1);\n \t\tif (status < 0)\n \t\t\tdie(_(\"failed to format default config value: %s\"),\n \t\t\t    display_opts->default_value);\n@@ -743,7 +792,7 @@ static int get_urlmatch(const struct config_location_options *opts,\n \n \t\tstatus = format_config(&display_opts, &buf, item->string,\n \t\t\t\t       matched->value_is_null ? NULL : matched->value.buf,\n-\t\t\t\t       &matched->kvi);\n+\t\t\t\t       &matched->kvi, 1);\n \t\tif (!status)\n \t\t\tfwrite(buf.buf, 1, buf.len, stdout);\n \t\tstrbuf_release(&buf);\n-- \ngitgitgadget\n\n"},{"id":"535644","messageId":"5601a5a84f4c67b3dfb751c2b93d1c300909b9e3.1770698579.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.git.1770698579.gitgitgadget@gmail.com","subject":"[PATCH 4/5] config: create special init for list mode","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-10T04:42:58Z","receivedAt":"2026-02-10T04:43:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nWhen listing multiple values, our initial settings for the output format\nis different. Add a new init helper to specify the fact that keys should\nbe shown and also add the default delimiters as they were unset in some\ncases.\n\nThere are two places, differing by the 'git config list' and 'git config\n--list' modes.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c | 16 +++++++++++++++-\n 1 file changed, 15 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 5146abcfa3..e69b26af6a 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -917,6 +917,19 @@ static void display_options_init(struct config_display_options *opts)\n \t}\n }\n \n+static void display_options_init_list(struct config_display_options *opts)\n+{\n+\topts->show_keys = 1;\n+\n+\tif (opts->end_nul) {\n+\t\tdisplay_options_init(opts);\n+\t} else {\n+\t\topts->term = '\\n';\n+\t\topts->delim = ' ';\n+\t\topts->key_delim = '=';\n+\t}\n+}\n+\n static int cmd_config_list(int argc, const char **argv, const char *prefix,\n \t\t\t   struct repository *repo UNUSED)\n {\n@@ -935,7 +948,7 @@ static int cmd_config_list(int argc, const char **argv, const char *prefix,\n \tcheck_argc(argc, 0, 0);\n \n \tlocation_options_init(&location_opts, prefix);\n-\tdisplay_options_init(&display_opts);\n+\tdisplay_options_init_list(&display_opts);\n \n \tsetup_auto_pager(\"config\", 1);\n \n@@ -1366,6 +1379,7 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix)\n \n \tif (actions == ACTION_LIST) {\n \t\tcheck_argc(argc, 0, 0);\n+\t\tdisplay_options_init_list(&display_opts);\n \t\tif (config_with_options(show_all_config, &display_opts,\n \t\t\t\t\t&location_opts.source, the_repository,\n \t\t\t\t\t&location_opts.options) < 0) {\n-- \ngitgitgadget\n\n"},{"id":"535645","messageId":"e27d52c4a5970f774e324b0a9c0badb0532bf3d4.1770698579.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.git.1770698579.gitgitgadget@gmail.com","subject":"[PATCH 5/5] config: make 'git config list --type=<X>' work","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-10T04:42:59Z","receivedAt":"2026-02-10T04:43:16Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nPreviously, the --type=<X> argument to 'git config list' was ignored and\ndid nothing. Now, we add the use of format_config() to the\nshow_all_config() function so each key-value pair is attempted to be\nparsed.\n\nIf there is an error in parsing, then the row is not output.\n\nThis is a change in behavior! We are starting to respect an option that\nwas previously ignored, leading to potential user confusion. This is\nprobably still a good option, since the --type argument did not change\nbehavior at all previously, so users can get the behavior they expect by\nremoving the --type argument or adding the --no-type argument.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n Documentation/git-config.adoc |  3 +++\n builtin/config.c              | 19 +++++--------------\n t/t1300-config.sh             | 26 +++++++++++++++++++++++++-\n 3 files changed, 33 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/git-config.adoc b/Documentation/git-config.adoc\nindex ac3b536a15..5300dd4c51 100644\n--- a/Documentation/git-config.adoc\n+++ b/Documentation/git-config.adoc\n@@ -240,6 +240,9 @@ Valid `<type>`'s include:\n   that the given value is canonicalize-able as an ANSI color, but it is written\n   as-is.\n +\n+If the command is in `list` mode, then the `--type <type>` argument will apply\n+to each listed config value. If the value does not successfully parse in that\n+format, then it will be omitted from the list.\n \n --bool::\n --int::\ndiff --git a/builtin/config.c b/builtin/config.c\nindex e69b26af6a..c83514b4ff 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -363,21 +363,12 @@ static int show_all_config(const char *key_, const char *value_,\n {\n \tconst struct config_display_options *opts = cb;\n \tconst struct key_value_info *kvi = ctx->kvi;\n+\tstruct strbuf formatted = STRBUF_INIT;\n \n-\tif (opts->show_origin || opts->show_scope) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tif (opts->show_scope)\n-\t\t\tshow_config_scope(opts, kvi, &buf);\n-\t\tif (opts->show_origin)\n-\t\t\tshow_config_origin(opts, kvi, &buf);\n-\t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n-\t\tfwrite(buf.buf, 1, buf.len, stdout);\n-\t\tstrbuf_release(&buf);\n-\t}\n-\tif (!opts->omit_values && value_)\n-\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n-\telse\n-\t\tprintf(\"%s%c\", key_, opts->term);\n+\tif (format_config(opts, &formatted, key_, value_, kvi, 0) >= 0)\n+\t\tfwrite(formatted.buf, 1, formatted.len, stdout);\n+\n+\tstrbuf_release(&formatted);\n \treturn 0;\n }\n \ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 9850fcd5b5..b5ce900126 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2459,9 +2459,10 @@ done\n \n cat >.git/config <<-\\EOF &&\n [section]\n-foo = true\n+foo = True\n number = 10\n big = 1M\n+path = ~/dir\n EOF\n \n test_expect_success 'identical modern --type specifiers are allowed' '\n@@ -2503,6 +2504,29 @@ test_expect_success 'unset type specifiers may be reset to conflicting ones' '\n \ttest_cmp_config 1048576 --type=bool --no-type --type=int section.big\n '\n \n+test_expect_success 'list --type=bool shows only canonicalizable bool values' '\n+\tcat >expect <<-EOF &&\n+\tsection.foo=true\n+\tsection.number=true\n+\tsection.big=true\n+\tEOF\n+\n+\tgit config ${mode_prefix}list --type=bool >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'list --type=path shows only canonicalizable path values' '\n+\tcat >expect <<-EOF &&\n+\tsection.foo=True\n+\tsection.number=10\n+\tsection.big=1M\n+\tsection.path=$HOME/dir\n+\tEOF\n+\n+\tgit config ${mode_prefix}list --type=path >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success '--type rejects unknown specifiers' '\n \ttest_must_fail git config --type=nonsense section.foo 2>error &&\n \ttest_grep \"unrecognized --type argument\" error\n-- \ngitgitgadget\n"},{"id":"535647","messageId":"xmqqldh119e7.fsf@gitster.g","threadId":"64966","inReplyTo":"pull.2044.git.1770698579.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/5] [RFC] Make 'git config list --type=' parse and filter types","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-10T04:59:28Z","receivedAt":"2026-02-10T04:59:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Problem: 'git config list' doesn't respect --type=<X>!\n\n;-).  \n\nAs there is no \"inherent\" type associated with each configuration\nvariable (in other words, type of a particular configuration\nvariable is something determined by the caller that wants the value\nof that variable), \"git config list/get --type=auto\" would not work,\nbut it would not be too bad to allow \"git config list --type=path\"\nto treat everything as if it is a path and having to filter nonsense\nout of the result (like \"core.bare = true/false\" or even \"core.bare\"\nwithout value that means true, which may make the \"*force*\ninterpreting it as path\" approach to barf), which is an inevitable\nconsequence.\n\n> This boils down to the fact that the iterator function show_all_config()\n> doesn't call format_config(), which includes the type-parsing code.\n>\n> This wasn't super trivial to update:\n>\n>  1. format_config() uses git_config_parse_*() methods, which die() on a bad\n>     parse.\n>  2. The path parsing code didn't have a gentle version.\n>  3. The two paths ('git config list' and 'git config --list') needed to\n>     standardize their display options to work with format_config().\n\nThanks for dealing with them.  These are what I would have expected\nas part of the \"inevitable consequence\".\n\n>  4. Finally, we need to filter out key-value pairs that don't match the\n>     given type.\n\nThis one, however, I need to see the actual code before commenting,\nas I do not think key-value pairs have inherent types.  The _only_\nspecial case where you can tell what type the thing is is the\nvalueless true, which we can safely say is inherently boolean.\nEverything else is text string, sometimes interpreted as boolean,\nsometimes number, sometimes human-scaled number, sometimes path\n(with possible tilde expansion), etc.\n\n> This is marked as an RFC because I need to add some more tests and because\n> this is a behavior change! If there are any tools currently passing the\n> --type=<X> argument to git config list then they will have a change of\n> behavior with this series. It's an easy workaround: drop the --type argument\n> or add --no-type to go back to the previous behavior.\n>\n> Thanks for any and all feedback, -Stolee\n>\n> Derrick Stolee (5):\n>   config: move show_all_config()\n>   parse: add git_parse_maybe_pathname()\n>   config: allow format_config() to filter\n>   config: create special init for list mode\n>   config: make 'git config list --type=<X>' work\n>\n>  Documentation/git-config.adoc |   3 +\n>  builtin/config.c              | 130 ++++++++++++++++++++++++----------\n>  config.c                      |  14 +---\n>  parse.c                       |  24 +++++++\n>  parse.h                       |   2 +\n>  t/t1300-config.sh             |  26 ++++++-\n>  6 files changed, 147 insertions(+), 52 deletions(-)\n>\n>\n> base-commit: 67ad42147a7acc2af6074753ebd03d904476118f\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2044%2Fderrickstolee%2Fconfig-list-type-v1\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2044/derrickstolee/config-list-type-v1\n> Pull-Request: https://github.com/gitgitgadget/git/pull/2044\n"},{"id":"535648","messageId":"xmqqfr791957.fsf@gitster.g","threadId":"64966","inReplyTo":"d9e04240100afb4188d32c5ce784f189f59348b4.1770698579.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 3/5] config: allow format_config() to filter","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-10T05:04:52Z","receivedAt":"2026-02-10T05:04:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Derrick Stolee <stolee@gmail.com>\n>\n> The format_config() method in builtin/config.c currently only uses\n> git_config_*() methods for parsing. This allows parsing errors to result\n> in die() messages appropriate with keys in the error message.\n>\n> In a future change we will want to use format_config() within 'git\n> config list' to help format the output, including when --type=<X>\n> arguments are provided. When the parsing fails in that case, that\n> key-value pair should be omitted instead of causing a failure across the\n> entire command.\n>\n> This change is formatted in such a way that the if/else-if structure\n> allows the default die_on_error version to appear first and then be\n> followed by the gentle parsing mode immediately afterwards.\n>\n> The only callers right now have die_on_parse set to 1.\n\nCertainly you meant die-on-parse-errors, not unconditionally die\nwhen asked to parse ;-).\n\nI wonder if a \"bool gently\" like everybody else takes would be\neasier to understand by more developers and readers, though.\n\n\n\n> +\t\tif (opts->type == TYPE_INT && die_on_parse) {\n>  \t\t\tstrbuf_addf(buf, \"%\"PRId64,\n>  \t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\", kvi));\n> +\t\t} else if (opts->type == TYPE_INT) {\n> +\t\t\tint64_t v;\n> +\t\t\tint ret = git_parse_int64(value_, &v);\n> +\n> +\t\t\tif (ret)\n> +\t\t\t\treturn -1;\n> +\n> +\t\t\tstrbuf_addf(buf, \"%\"PRId64, v);\n> +\t\t}\n\nSo, this follows the typical layout that was described in the\nproposed log message.  I wonder if it is too much to break the set\nof helper functions further down so that this part of the caller can\nsay something like:\n\n\tswitch (opts->type) {\n\tcase TYPE_INT:\n\t\tformat_config_int(buf, key_, value_, kvi, gently);\n\t\tbreak;\n\nand similar case arms for other types?\n\n"},{"id":"535693","messageId":"91fb7d01-cc6a-47b5-a23a-45b0fb31134a@gmail.com","threadId":"64966","inReplyTo":"xmqqfr791957.fsf@gitster.g","subject":"Re: [PATCH 3/5] config: allow format_config() to filter","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2026-02-10T18:12:00Z","receivedAt":"2026-02-10T18:12:02Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"\n\nOn 2/10/2026 12:04 AM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Derrick Stolee <stolee@gmail.com>\n>>\n>> The format_config() method in builtin/config.c currently only uses\n>> git_config_*() methods for parsing. This allows parsing errors to result\n>> in die() messages appropriate with keys in the error message.\n>>\n>> In a future change we will want to use format_config() within 'git\n>> config list' to help format the output, including when --type=<X>\n>> arguments are provided. When the parsing fails in that case, that\n>> key-value pair should be omitted instead of causing a failure across the\n>> entire command.\n>>\n>> This change is formatted in such a way that the if/else-if structure\n>> allows the default die_on_error version to appear first and then be\n>> followed by the gentle parsing mode immediately afterwards.\n>>\n>> The only callers right now have die_on_parse set to 1.\n> \n> Certainly you meant die-on-parse-errors, not unconditionally die\n> when asked to parse ;-).\n> \n> I wonder if a \"bool gently\" like everybody else takes would be\n> easier to understand by more developers and readers, though.\n\n'gently' makes a lot more sense.\n\n>> +\t\tif (opts->type == TYPE_INT && die_on_parse) {\n>>  \t\t\tstrbuf_addf(buf, \"%\"PRId64,\n>>  \t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\", kvi));\n>> +\t\t} else if (opts->type == TYPE_INT) {\n>> +\t\t\tint64_t v;\n>> +\t\t\tint ret = git_parse_int64(value_, &v);\n>> +\n>> +\t\t\tif (ret)\n>> +\t\t\t\treturn -1;\n>> +\n>> +\t\t\tstrbuf_addf(buf, \"%\"PRId64, v);\n>> +\t\t}\n> \n> So, this follows the typical layout that was described in the\n> proposed log message.  I wonder if it is too much to break the set\n> of helper functions further down so that this part of the caller can\n> say something like:\n> \n> \tswitch (opts->type) {\n> \tcase TYPE_INT:\n> \t\tformat_config_int(buf, key_, value_, kvi, gently);\n> \t\tbreak;\n> \n> and similar case arms for other types?\n\nI had a similar feeling that such a refactor would be necessary.\n\nI didn't want to go through that careful work if it wasn't\njustified by positive reactions to the RFC. Thanks for calling it\nout, and I'll definitely put in that effort if we find this worth\na v2.\n\nThanks,\n-Stolee\n\n\n"},{"id":"535696","messageId":"ccde188e-267c-479c-a012-a33027b2df63@gmail.com","threadId":"64966","inReplyTo":"xmqqldh119e7.fsf@gitster.g","subject":"Re: [PATCH 0/5] [RFC] Make 'git config list --type=' parse and filter types","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2026-02-10T18:18:37Z","receivedAt":"2026-02-10T18:18:40Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 2/9/2026 11:59 PM, Junio C Hamano wrote:\n> \"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:...\n>> This boils down to the fact that the iterator function show_all_config()\n>> doesn't call format_config(), which includes the type-parsing code.\n>>\n>> This wasn't super trivial to update:\n>>\n>>  1. format_config() uses git_config_parse_*() methods, which die() on a bad\n>>     parse.\n>>  2. The path parsing code didn't have a gentle version.\n>>  3. The two paths ('git config list' and 'git config --list') needed to\n>>     standardize their display options to work with format_config().\n> \n> Thanks for dealing with them.  These are what I would have expected\n> as part of the \"inevitable consequence\".\n> \n>>  4. Finally, we need to filter out key-value pairs that don't match the\n>>     given type.\n> \n> This one, however, I need to see the actual code before commenting,\n> as I do not think key-value pairs have inherent types.  The _only_\n> special case where you can tell what type the thing is is the\n> valueless true, which we can safely say is inherently boolean.\n> Everything else is text string, sometimes interpreted as boolean,\n> sometimes number, sometimes human-scaled number, sometimes path\n> (with possible tilde expansion), etc.\nThis is the crux of this series. If the caller asks for a type, then I\nsee a couple different ways to react:\n\n 1. If the value fails to parse in that type, then don't list that\n    result, allowing the caller to have confidence that every result\n    is of the correct format.\n\n 2. If the value fails to parse in that type, then list it in its\n    base string. The caller would need to do extra parsing to check\n    that the results match the correct format.\n\nI chose option 1. It avoids showing results that would result in\n'git config get --type=<X> <key>' to die().\n\nI'd be interested to hear if there are reasons to go with option 2,\nor if there exists an alternative option that I don't see.\n\nReordering your message somewhat:\n\n> As there is no \"inherent\" type associated with each configuration\n> variable (in other words, type of a particular configuration\n> variable is something determined by the caller that wants the value\n> of that variable), \"git config list/get --type=auto\" would not work,\n> but it would not be too bad to allow \"git config list --type=path\"\n> to treat everything as if it is a path and having to filter nonsense\n> out of the result (like \"core.bare = true/false\" or even \"core.bare\"\n> without value that means true, which may make the \"*force*\n> interpreting it as path\" approach to barf), which is an inevitable\n> consequence.\n\nI agree that there is not inherent type, so the user can only\nspecify the type that they are expecting. To me, this is a request to\nfilter the results.\n\nI don't think we'll have much success if we try to guess the type,\nsuch as trying to parse an int, then a bool, then a path.\n\nThanks,\n-Stolee\n\n"},{"id":"535762","messageId":"aYxyV1P4QUPmBzeZ@pks.im","threadId":"64966","inReplyTo":"pull.2044.git.1770698579.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/5] [RFC] Make 'git config list --type=' parse and filter types","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-11T12:13:11Z","receivedAt":"2026-02-11T12:13:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Feb 10, 2026 at 04:42:54AM +0000, Derrick Stolee via GitGitGadget wrote:\n> This is marked as an RFC because I need to add some more tests and because\n> this is a behavior change! If there are any tools currently passing the\n> --type=<X> argument to git config list then they will have a change of\n> behavior with this series. It's an easy workaround: drop the --type argument\n> or add --no-type to go back to the previous behavior.\n\nI think this is not a huge problem. It simply reads like a bug to me\nthat the command accepts the option, but doesn't honor it. Sure, it can\nlead to different behaviour, but I think that's acceptable.\n\nPatrick\n"},{"id":"535763","messageId":"aYxyXlH5Z0toWgPj@pks.im","threadId":"64966","inReplyTo":"8d3a6a8265714c5e4bae0f2e5a587ea46a6adddc.1770698579.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/5] parse: add git_parse_maybe_pathname()","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-11T12:13:18Z","receivedAt":"2026-02-11T12:13:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Feb 10, 2026 at 04:42:56AM +0000, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <stolee@gmail.com>\n> \n> This extraction of logic from config.c's git_config_pathname() allows\n> for parsing a fully-qualified path from a relative path along with\n> validation of the existence of the path without failing with a die().\n\nThat sentence is quite something. I had to read it thrice to understand\nwhat it wants to say :)\n\n> diff --git a/parse.c b/parse.c\n> index 48313571aa..3f37f0b93a 100644\n> --- a/parse.c\n> +++ b/parse.c\n> @@ -209,3 +210,26 @@ unsigned long git_env_ulong(const char *k, unsigned long val)\n>  \t\tdie(_(\"failed to parse %s\"), k);\n>  \treturn val;\n>  }\n> +\n> +int git_parse_maybe_pathname(const char *value, char **dest)\n> +{\n> +\tbool is_optional;\n> +\tchar *path;\n> +\n> +\tif (!value)\n> +\t\treturn -1;\n> +\n> +\tis_optional = skip_prefix(value, \":(optional)\", &value);\n> +\tpath = interpolate_path(value, 0);\n> +\tif (!path)\n> +\t\treturn -1;\n> +\n> +\tif (is_optional && is_missing_file(path)) {\n> +\t\tfree(path);\n> +\t\t*dest = NULL;\n> +\t\treturn 0;\n> +\t}\n> +\n> +\t*dest = path;\n> +\treturn 0;\n> +}\n\nOkay. So the difference is that this function here doesn't cause us to\ndie in case the path is not marked as optional and missing. Makes sense.\n\n> diff --git a/parse.h b/parse.h\n> index ea32de9a91..4f97c3727a 100644\n> --- a/parse.h\n> +++ b/parse.h\n> @@ -19,4 +19,6 @@ int git_parse_maybe_bool_text(const char *value);\n>  int git_env_bool(const char *, int);\n>  unsigned long git_env_ulong(const char *, unsigned long);\n>  \n> +int git_parse_maybe_pathname(const char *value, char **dest);\n\nI think this function could use some explanation what it actually does,\nas the behaviour is non-trivial:\n\n  - I think the \":(optional)\" part needs to be documented properly to\n    say that we return successfully with a NULL string in case the\n    target path doesn't exist.\n\n  - We should document that it expands \"~\" and \"%(prefix)\" (even though\n    the latter feels somewhat coincidental to me).\n\n  - The path is not resolved to an absolute path.\n\nThanks!\n\nPatrick\n"},{"id":"535764","messageId":"aYxyeAQ_-9KXxI-L@pks.im","threadId":"64966","inReplyTo":"e27d52c4a5970f774e324b0a9c0badb0532bf3d4.1770698579.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 5/5] config: make 'git config list --type=<X>' work","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-11T12:13:44Z","receivedAt":"2026-02-11T12:13:50Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Feb 10, 2026 at 04:42:59AM +0000, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <stolee@gmail.com>\n> \n> Previously, the --type=<X> argument to 'git config list' was ignored and\n> did nothing. Now, we add the use of format_config() to the\n> show_all_config() function so each key-value pair is attempted to be\n> parsed.\n> \n> If there is an error in parsing, then the row is not output.\n\nI was a bit surprised at first, but now that I think about it a bit more\nI think this is sensible behaviour. If I ask for `git config list\n--type=int`, then I don't want to see any non-int configuration. I\nwouldn't even expect a warning, as the option essentially works like a\nfilter.\n\n> This is a change in behavior! We are starting to respect an option that\n> was previously ignored, leading to potential user confusion. This is\n> probably still a good option, since the --type argument did not change\n> behavior at all previously, so users can get the behavior they expect by\n> removing the --type argument or adding the --no-type argument.\n\nYeah, I fully agree that this is a sensible change in behaviour. It is\nobviously broken right now, so I would claim that this is simply a bug\nfix.\n\n> diff --git a/Documentation/git-config.adoc b/Documentation/git-config.adoc\n> index ac3b536a15..5300dd4c51 100644\n> --- a/Documentation/git-config.adoc\n> +++ b/Documentation/git-config.adoc\n\nThe synopsis of `git config list` should also be amended.\n\n> diff --git a/builtin/config.c b/builtin/config.c\n> index e69b26af6a..c83514b4ff 100644\n> --- a/builtin/config.c\n> +++ b/builtin/config.c\n> @@ -363,21 +363,12 @@ static int show_all_config(const char *key_, const char *value_,\n>  {\n>  \tconst struct config_display_options *opts = cb;\n>  \tconst struct key_value_info *kvi = ctx->kvi;\n> +\tstruct strbuf formatted = STRBUF_INIT;\n>  \n> -\tif (opts->show_origin || opts->show_scope) {\n> -\t\tstruct strbuf buf = STRBUF_INIT;\n> -\t\tif (opts->show_scope)\n> -\t\t\tshow_config_scope(opts, kvi, &buf);\n> -\t\tif (opts->show_origin)\n> -\t\t\tshow_config_origin(opts, kvi, &buf);\n> -\t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n> -\t\tfwrite(buf.buf, 1, buf.len, stdout);\n> -\t\tstrbuf_release(&buf);\n> -\t}\n> -\tif (!opts->omit_values && value_)\n> -\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n> -\telse\n> -\t\tprintf(\"%s%c\", key_, opts->term);\n> +\tif (format_config(opts, &formatted, key_, value_, kvi, 0) >= 0)\n> +\t\tfwrite(formatted.buf, 1, formatted.len, stdout);\n> +\n> +\tstrbuf_release(&formatted);\n>  \treturn 0;\n>  }\n>  \n\nI wonder whether there is a good argument to be made here that we should\nkeep the old logic in case no \"--type=\" parameter was given. In that\ncase, for example the following output would remain the same:\n\n> diff --git a/t/t1300-config.sh b/t/t1300-config.sh\n> index 9850fcd5b5..b5ce900126 100755\n> --- a/t/t1300-config.sh\n> +++ b/t/t1300-config.sh\n> @@ -2459,9 +2459,10 @@ done\n>  \n>  cat >.git/config <<-\\EOF &&\n>  [section]\n> -foo = true\n> +foo = True\n>  number = 10\n>  big = 1M\n> +path = ~/dir\n>  EOF\n>  \n>  test_expect_success 'identical modern --type specifiers are allowed' '\n\nI'm not really sure whether we want that though. I actually like that\nthis also leads to some code duplication, so maybe this is fine?\n\nPatrick\n"},{"id":"535789","messageId":"1fb94c08-c36a-445b-b613-dda33c238d6e@gmail.com","threadId":"64966","inReplyTo":"aYxyeAQ_-9KXxI-L@pks.im","subject":"Re: [PATCH 5/5] config: make 'git config list --type=<X>' work","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2026-02-11T17:49:19Z","receivedAt":"2026-02-11T17:49:21Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 2/11/2026 7:13 AM, Patrick Steinhardt wrote:\n> On Tue, Feb 10, 2026 at 04:42:59AM +0000, Derrick Stolee via GitGitGadget wrote:\n\n>> diff --git a/Documentation/git-config.adoc b/Documentation/git-config.adoc\n>> index ac3b536a15..5300dd4c51 100644\n>> --- a/Documentation/git-config.adoc\n>> +++ b/Documentation/git-config.adoc\n> \n> The synopsis of `git config list` should also be amended.\n\nGood point. Will fix.\n \n>> diff --git a/builtin/config.c b/builtin/config.c\n>> index e69b26af6a..c83514b4ff 100644\n>> --- a/builtin/config.c\n>> +++ b/builtin/config.c\n>> @@ -363,21 +363,12 @@ static int show_all_config(const char *key_, const char *value_,\n>>  {\n>>  \tconst struct config_display_options *opts = cb;\n>>  \tconst struct key_value_info *kvi = ctx->kvi;\n>> +\tstruct strbuf formatted = STRBUF_INIT;\n>>  \n>> -\tif (opts->show_origin || opts->show_scope) {\n>> -\t\tstruct strbuf buf = STRBUF_INIT;\n>> -\t\tif (opts->show_scope)\n>> -\t\t\tshow_config_scope(opts, kvi, &buf);\n>> -\t\tif (opts->show_origin)\n>> -\t\t\tshow_config_origin(opts, kvi, &buf);\n>> -\t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n>> -\t\tfwrite(buf.buf, 1, buf.len, stdout);\n>> -\t\tstrbuf_release(&buf);\n>> -\t}\n>> -\tif (!opts->omit_values && value_)\n>> -\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n>> -\telse\n>> -\t\tprintf(\"%s%c\", key_, opts->term);\n>> +\tif (format_config(opts, &formatted, key_, value_, kvi, 0) >= 0)\n>> +\t\tfwrite(formatted.buf, 1, formatted.len, stdout);\n>> +\n>> +\tstrbuf_release(&formatted);\n>>  \treturn 0;\n>>  }\n>>  \n> \n> I wonder whether there is a good argument to be made here that we should\n> keep the old logic in case no \"--type=\" parameter was given. In that\n> case, for example the following output would remain the same:\n\nIf no `--type=` parameter is given, then this new implementation does\nthe exact same thing as the display_options use a string format (which\ndoes not mutate the config values).\n\n>> diff --git a/t/t1300-config.sh b/t/t1300-config.sh\n>> index 9850fcd5b5..b5ce900126 100755\n>> --- a/t/t1300-config.sh\n>> +++ b/t/t1300-config.sh\n>> @@ -2459,9 +2459,10 @@ done\n>>  \n>>  cat >.git/config <<-\\EOF &&\n>>  [section]\n>> -foo = true\n>> +foo = True\n>>  number = 10\n>>  big = 1M\n>> +path = ~/dir\n>>  EOF\n>>  \n>>  test_expect_success 'identical modern --type specifiers are allowed' '\n> \n> I'm not really sure whether we want that though. I actually like that\n> this also leads to some code duplication, so maybe this is fine?\n\nThe change you highlight here is a difference in the config file _contents_\nand not the expected output. These changes are to help demonstrate that the\nbool and path types make meaningful conversions when listing these values.\n\nThe previous tests for getting bool values did not demonstrate the way it\nmodifies case, for example.\n\nThanks,\n-Stolee\n\n"},{"id":"535824","messageId":"aY11ikm_sgDlneV7@pks.im","threadId":"64966","inReplyTo":"1fb94c08-c36a-445b-b613-dda33c238d6e@gmail.com","subject":"Re: [PATCH 5/5] config: make 'git config list --type=<X>' work","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-12T06:39:06Z","receivedAt":"2026-02-12T06:39:12Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Feb 11, 2026 at 12:49:19PM -0500, Derrick Stolee wrote:\n> On 2/11/2026 7:13 AM, Patrick Steinhardt wrote:\n> > On Tue, Feb 10, 2026 at 04:42:59AM +0000, Derrick Stolee via GitGitGadget wrote:\n> >> diff --git a/builtin/config.c b/builtin/config.c\n> >> index e69b26af6a..c83514b4ff 100644\n> >> --- a/builtin/config.c\n> >> +++ b/builtin/config.c\n> >> @@ -363,21 +363,12 @@ static int show_all_config(const char *key_, const char *value_,\n> >>  {\n> >>  \tconst struct config_display_options *opts = cb;\n> >>  \tconst struct key_value_info *kvi = ctx->kvi;\n> >> +\tstruct strbuf formatted = STRBUF_INIT;\n> >>  \n> >> -\tif (opts->show_origin || opts->show_scope) {\n> >> -\t\tstruct strbuf buf = STRBUF_INIT;\n> >> -\t\tif (opts->show_scope)\n> >> -\t\t\tshow_config_scope(opts, kvi, &buf);\n> >> -\t\tif (opts->show_origin)\n> >> -\t\t\tshow_config_origin(opts, kvi, &buf);\n> >> -\t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n> >> -\t\tfwrite(buf.buf, 1, buf.len, stdout);\n> >> -\t\tstrbuf_release(&buf);\n> >> -\t}\n> >> -\tif (!opts->omit_values && value_)\n> >> -\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n> >> -\telse\n> >> -\t\tprintf(\"%s%c\", key_, opts->term);\n> >> +\tif (format_config(opts, &formatted, key_, value_, kvi, 0) >= 0)\n> >> +\t\tfwrite(formatted.buf, 1, formatted.len, stdout);\n> >> +\n> >> +\tstrbuf_release(&formatted);\n> >>  \treturn 0;\n> >>  }\n> >>  \n> > \n> > I wonder whether there is a good argument to be made here that we should\n> > keep the old logic in case no \"--type=\" parameter was given. In that\n> > case, for example the following output would remain the same:\n> \n> If no `--type=` parameter is given, then this new implementation does\n> the exact same thing as the display_options use a string format (which\n> does not mutate the config values).\n> \n> >> diff --git a/t/t1300-config.sh b/t/t1300-config.sh\n> >> index 9850fcd5b5..b5ce900126 100755\n> >> --- a/t/t1300-config.sh\n> >> +++ b/t/t1300-config.sh\n> >> @@ -2459,9 +2459,10 @@ done\n> >>  \n> >>  cat >.git/config <<-\\EOF &&\n> >>  [section]\n> >> -foo = true\n> >> +foo = True\n> >>  number = 10\n> >>  big = 1M\n> >> +path = ~/dir\n> >>  EOF\n> >>  \n> >>  test_expect_success 'identical modern --type specifiers are allowed' '\n> > \n> > I'm not really sure whether we want that though. I actually like that\n> > this also leads to some code duplication, so maybe this is fine?\n> \n> The change you highlight here is a difference in the config file _contents_\n> and not the expected output. These changes are to help demonstrate that the\n> bool and path types make meaningful conversions when listing these values.\n\nOoh, right. Completely missed that, thanks for the clarification.\n\nPatrick\n"},{"id":"535977","messageId":"pull.2044.v2.git.1771026918.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.git.1770698579.gitgitgadget@gmail.com","subject":"[PATCH v2 00/13] Make 'git config list --type=' parse and filter types","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-13T23:55:05Z","receivedAt":"2026-02-13T23:55:22Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"I started down this road based on feedback on my 'git config-batch' RFC [1].\n\n[1]\nhttps://lore.kernel.org/git/pull.2033.git.1770214803.gitgitgadget@gmail.com/\n\nI had described my intention to use 'git config-batch' as a single process\nto load multiple config values one-by-one. Brian mentioned that 'git config\nlist -z' would probably suffice, so I started experimenting in that\ndirection [2].\n\n[2]\nhttps://github.com/git-ecosystem/git-credential-manager/compare/main...derrickstolee:config-list\n\nHowever, I ran into a problem: the most critical performance bottleneck is\nrelated to path-formatted config values that are queried with 'git config\nget --type=path -z'. It wasn't hard to update things to lazily load the full\nlist of config values by type [3], but I then noticed a big problem!\n\n[3]\nhttps://github.com/git-ecosystem/git-credential-manager/commit/d403c8e24ce6f37da920cce23842dd5a6cf6481d\n\nProblem: 'git config list' doesn't respect --type=<X>!\n\nThis boils down to the fact that the iterator function show_all_config()\ndoesn't call format_config(), which includes the type-parsing code.\n\nThis wasn't super trivial to update:\n\n 1. format_config() uses git_config_parse_*() methods, which die() on a bad\n    parse.\n 2. The path parsing code didn't have a gentle version.\n 3. The two paths ('git config list' and 'git config --list') needed to\n    standardize their display options to work with format_config().\n 4. Finally, we need to filter out key-value pairs that don't match the\n    given type.\n\n\nUpdates in v2\n=============\n\nBased on the positive feedback in round one, this is no longer an RFC.\n\n * format_config() now uses a 'gently' parameter instead of 'die_on_parse'\n   (flipped).\n * format_config() is more carefully updated with helper methods and a\n   global refactor.\n * New gentle parsing code is introduced right before the format_config()\n   helper is created to use it.\n * I squashed the change that updates the display_opts initial state into\n   the patch that uses format_config() for the 'list' command. The initial\n   state change on its own leads to test failures, so I am making a slightly\n   bigger patch to keep things passing tests at every change.\n * More tests for 'git config list --type=<X>' are added.\n * I rearranged things so the 'git config list --type' integration follows\n   the format_config() update immediately. The tests at that time show what\n   such a trivial implementation would do, including failing on bool parsing\n   and having several error messages for color and expiry-date parsing. The\n   tests modify as these issues are fixed with gentle parsers.\n * I have a prototype implementation of GCM using this option in [4] and it\n   gets the performance improvements I was hoping for. It requires polish\n   and a compatibility check that uses the Git version to guarantee that\n   this --type behavior change is recognized.\n\n[4] https://github.com/git-ecosystem/git-credential-manager/pull/2268\n\nThanks for any and all feedback, -Stolee\n\nDerrick Stolee (13):\n  config: move show_all_config()\n  config: add 'gently' parameter to format_config()\n  config: make 'git config list --type=<X>' work\n  config: format int64s gently\n  config: format bools gently\n  config: format bools or ints gently\n  config: format bools or strings in helper\n  parse: add git_parse_maybe_pathname()\n  config: format paths gently\n  config: format expiry dates gently\n  color: add color_parse_gently()\n  config: format colors gently\n  config: restructure format_config()\n\n Documentation/git-config.adoc |   3 +\n builtin/config.c              | 283 +++++++++++++++++++++++++---------\n color.c                       |  25 ++-\n color.h                       |   1 +\n config.c                      |  14 +-\n parse.c                       |  24 +++\n parse.h                       |   2 +\n t/t1300-config.sh             |  58 ++++++-\n 8 files changed, 318 insertions(+), 92 deletions(-)\n\n\nbase-commit: 67ad42147a7acc2af6074753ebd03d904476118f\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2044%2Fderrickstolee%2Fconfig-list-type-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2044/derrickstolee/config-list-type-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/2044\n\nRange-diff vs v1:\n\n  1:  bca83d8ca8 =  1:  bca83d8ca8 config: move show_all_config()\n  3:  d9e0424010 !  2:  93c94a1b25 config: allow format_config() to filter\n     @@ Metadata\n      Author: Derrick Stolee <stolee@gmail.com>\n      \n       ## Commit message ##\n     -    config: allow format_config() to filter\n     +    config: add 'gently' parameter to format_config()\n      \n     -    The format_config() method in builtin/config.c currently only uses\n     -    git_config_*() methods for parsing. This allows parsing errors to result\n     -    in die() messages appropriate with keys in the error message.\n     -\n     -    In a future change we will want to use format_config() within 'git\n     -    config list' to help format the output, including when --type=<X>\n     -    arguments are provided. When the parsing fails in that case, that\n     -    key-value pair should be omitted instead of causing a failure across the\n     -    entire command.\n     -\n     -    This change is formatted in such a way that the if/else-if structure\n     -    allows the default die_on_error version to appear first and then be\n     -    followed by the gentle parsing mode immediately afterwards.\n     -\n     -    The only callers right now have die_on_parse set to 1.\n     +    This parameter is set to 0 for all current callers and is UNUSED.\n     +    However, we will start using this option in future changes and in a\n     +    critical change that requires gentle parsing (not using die()) to try\n     +    parsing all values in a list.\n      \n          Signed-off-by: Derrick Stolee <stolee@gmail.com>\n      \n       ## builtin/config.c ##\n     -@@\n     - #include \"abspath.h\"\n     - #include \"config.h\"\n     - #include \"color.h\"\n     -+#include \"date.h\"\n     - #include \"editor.h\"\n     - #include \"environment.h\"\n     - #include \"gettext.h\"\n      @@ builtin/config.c: struct strbuf_list {\n     +  * append it into strbuf `buf`.  Returns a negative value on failure,\n     +  * 0 on success, 1 on a missing optional value (i.e., telling the\n     +  * caller to pretend that <key_,value_> did not exist).\n     ++ *\n     ++ * Note: 'gently' is currently ignored, but will be implemented in\n     ++ * a future change.\n        */\n       static int format_config(const struct config_display_options *opts,\n       \t\t\t struct strbuf *buf, const char *key_,\n      -\t\t\t const char *value_, const struct key_value_info *kvi)\n      +\t\t\t const char *value_, const struct key_value_info *kvi,\n     -+\t\t\t int die_on_parse)\n     ++\t\t\t int gently UNUSED)\n       {\n       \tif (opts->show_scope)\n       \t\tshow_config_scope(opts, kvi, buf);\n     -@@ builtin/config.c: static int format_config(const struct config_display_options *opts,\n     - \t\tif (opts->show_keys)\n     - \t\t\tstrbuf_addch(buf, opts->key_delim);\n     - \n     --\t\tif (opts->type == TYPE_INT)\n     -+\t\tif (opts->type == TYPE_INT && die_on_parse) {\n     - \t\t\tstrbuf_addf(buf, \"%\"PRId64,\n     - \t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\", kvi));\n     --\t\telse if (opts->type == TYPE_BOOL)\n     -+\t\t} else if (opts->type == TYPE_INT) {\n     -+\t\t\tint64_t v;\n     -+\t\t\tint ret = git_parse_int64(value_, &v);\n     -+\n     -+\t\t\tif (ret)\n     -+\t\t\t\treturn -1;\n     -+\n     -+\t\t\tstrbuf_addf(buf, \"%\"PRId64, v);\n     -+\t\t}\n     -+\t\telse if (opts->type == TYPE_BOOL && die_on_parse) {\n     - \t\t\tstrbuf_addstr(buf, git_config_bool(key_, value_) ?\n     - \t\t\t\t      \"true\" : \"false\");\n     --\t\telse if (opts->type == TYPE_BOOL_OR_INT) {\n     --\t\t\tint is_bool, v;\n     --\t\t\tv = git_config_bool_or_int(key_, value_, kvi,\n     --\t\t\t\t\t\t   &is_bool);\n     -+\t\t} else if (opts->type == TYPE_BOOL) {\n     -+\t\t\tint value = git_parse_maybe_bool(value_);\n     -+\n     -+\t\t\tif (value < 0)\n     -+\t\t\t\treturn -1;\n     -+\n     -+\t\t\tstrbuf_addstr(buf, value ? \"true\" : \"false\");\n     -+\t\t} else if (opts->type == TYPE_BOOL_OR_INT && die_on_parse) {\n     -+\t\t\tint is_bool = 0;\n     -+\t\t\tint v = git_config_bool_or_int(key_, value_, kvi,\n     -+\t\t\t\t\t\t       &is_bool);\n     -+\t\t\tif (is_bool)\n     -+\t\t\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n     -+\t\t\telse\n     -+\t\t\t\tstrbuf_addf(buf, \"%d\", v);\n     -+\t\t} else if (opts->type == TYPE_BOOL_OR_INT) {\n     -+\t\t\tint is_bool = 0;\n     -+\t\t\tint v = git_parse_maybe_bool_text(value_);\n     -+\n     -+\t\t\tif (v < 0)\n     -+\t\t\t\treturn -1;\n     -+\n     - \t\t\tif (is_bool)\n     - \t\t\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n     - \t\t\telse\n     - \t\t\t\tstrbuf_addf(buf, \"%d\", v);\n     - \t\t} else if (opts->type == TYPE_BOOL_OR_STR) {\n     -+\t\t\t/* Note: this can't fail to parse! */\n     - \t\t\tint v = git_parse_maybe_bool(value_);\n     - \t\t\tif (v < 0)\n     - \t\t\t\tstrbuf_addstr(buf, value_);\n     - \t\t\telse\n     - \t\t\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n     --\t\t} else if (opts->type == TYPE_PATH) {\n     -+\t\t} else if (opts->type == TYPE_PATH && die_on_parse) {\n     - \t\t\tchar *v;\n     - \t\t\tif (git_config_pathname(&v, key_, value_) < 0)\n     - \t\t\t\treturn -1;\n     -@@ builtin/config.c: static int format_config(const struct config_display_options *opts,\n     - \t\t\telse\n     - \t\t\t\treturn 1; /* :(optional)no-such-file */\n     - \t\t\tfree((char *)v);\n     --\t\t} else if (opts->type == TYPE_EXPIRY_DATE) {\n     -+\t\t} else if (opts->type == TYPE_PATH) {\n     -+\t\t\tchar *v;\n     -+\t\t\tif (git_parse_maybe_pathname(value_, &v) < 0)\n     -+\t\t\t\treturn -1;\n     -+\t\t\tif (v)\n     -+\t\t\t\tstrbuf_addstr(buf, v);\n     -+\t\t\telse\n     -+\t\t\t\treturn 1; /* :(optional)no-such-file */\n     -+\t\t\tfree((char *)v);\n     -+\t\t} else if (opts->type == TYPE_EXPIRY_DATE && die_on_parse) {\n     - \t\t\ttimestamp_t t;\n     - \t\t\tif (git_config_expiry_date(&t, key_, value_) < 0)\n     - \t\t\t\treturn -1;\n     - \t\t\tstrbuf_addf(buf, \"%\"PRItime, t);\n     --\t\t} else if (opts->type == TYPE_COLOR) {\n     -+\t\t} else if (opts->type == TYPE_EXPIRY_DATE) {\n     -+\t\t\ttimestamp_t t;\n     -+\t\t\tif (parse_expiry_date(value_, &t) < 0)\n     -+\t\t\t\treturn -1;\n     -+\t\t\tstrbuf_addf(buf, \"%\"PRItime, t);\n     -+\t\t} else if (opts->type == TYPE_COLOR && die_on_parse) {\n     - \t\t\tchar v[COLOR_MAXLEN];\n     - \t\t\tif (git_config_color(v, key_, value_) < 0)\n     - \t\t\t\treturn -1;\n     - \t\t\tstrbuf_addstr(buf, v);\n     -+\t\t} else if (opts->type == TYPE_COLOR) {\n     -+\t\t\tchar v[COLOR_MAXLEN];\n     -+\t\t\tif (color_parse(value_, v) < 0)\n     -+\t\t\t\treturn -1;\n     -+\t\t\tstrbuf_addstr(buf, v);\n     - \t\t} else if (value_) {\n     - \t\t\tstrbuf_addstr(buf, value_);\n     - \t\t} else {\n      @@ builtin/config.c: static int collect_config(const char *key_, const char *value_,\n       \tstrbuf_init(&values->items[values->nr], 0);\n       \n       \tstatus = format_config(data->display_opts, &values->items[values->nr++],\n      -\t\t\t       key_, value_, kvi);\n     -+\t\t\t       key_, value_, kvi, 1);\n     ++\t\t\t       key_, value_, kvi, 0);\n       \tif (status < 0)\n       \t\treturn status;\n       \tif (status) {\n     @@ builtin/config.c: static int get_value(const struct config_location_options *opt\n       \n       \t\tstatus = format_config(display_opts, item, key_,\n      -\t\t\t\t       display_opts->default_value, &kvi);\n     -+\t\t\t\t       display_opts->default_value, &kvi, 1);\n     ++\t\t\t\t       display_opts->default_value, &kvi, 0);\n       \t\tif (status < 0)\n       \t\t\tdie(_(\"failed to format default config value: %s\"),\n       \t\t\t    display_opts->default_value);\n     @@ builtin/config.c: static int get_urlmatch(const struct config_location_options *\n       \t\tstatus = format_config(&display_opts, &buf, item->string,\n       \t\t\t\t       matched->value_is_null ? NULL : matched->value.buf,\n      -\t\t\t\t       &matched->kvi);\n     -+\t\t\t\t       &matched->kvi, 1);\n     ++\t\t\t\t       &matched->kvi, 0);\n       \t\tif (!status)\n       \t\t\tfwrite(buf.buf, 1, buf.len, stdout);\n       \t\tstrbuf_release(&buf);\n  5:  e27d52c4a5 !  3:  6d2a48a3b7 config: make 'git config list --type=<X>' work\n     @@ Commit message\n          Previously, the --type=<X> argument to 'git config list' was ignored and\n          did nothing. Now, we add the use of format_config() to the\n          show_all_config() function so each key-value pair is attempted to be\n     -    parsed.\n     +    parsed. This is our first use of the 'gently' parameter with a nonzero\n     +    value.\n     +\n     +    When listing multiple values, our initial settings for the output format\n     +    is different. Add a new init helper to specify the fact that keys should\n     +    be shown and also add the default delimiters as they were unset in some\n     +    cases.\n      \n          If there is an error in parsing, then the row is not output.\n      \n     @@ Commit message\n          behavior at all previously, so users can get the behavior they expect by\n          removing the --type argument or adding the --no-type argument.\n      \n     +    t1300-config.sh is updated with the current behavior of this formatting\n     +    logic to justify the upcoming refactoring of format_config() that will\n     +    incrementally fix some of these cases to be more user-friendly.\n     +\n          Signed-off-by: Derrick Stolee <stolee@gmail.com>\n      \n       ## Documentation/git-config.adoc ##\n     @@ builtin/config.c: static int show_all_config(const char *key_, const char *value\n      -\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n      -\telse\n      -\t\tprintf(\"%s%c\", key_, opts->term);\n     -+\tif (format_config(opts, &formatted, key_, value_, kvi, 0) >= 0)\n     ++\tif (format_config(opts, &formatted, key_, value_, kvi, 1) >= 0)\n      +\t\tfwrite(formatted.buf, 1, formatted.len, stdout);\n      +\n      +\tstrbuf_release(&formatted);\n       \treturn 0;\n       }\n       \n     +@@ builtin/config.c: static void display_options_init(struct config_display_options *opts)\n     + \t}\n     + }\n     + \n     ++static void display_options_init_list(struct config_display_options *opts)\n     ++{\n     ++\topts->show_keys = 1;\n     ++\n     ++\tif (opts->end_nul) {\n     ++\t\tdisplay_options_init(opts);\n     ++\t} else {\n     ++\t\topts->term = '\\n';\n     ++\t\topts->delim = ' ';\n     ++\t\topts->key_delim = '=';\n     ++\t}\n     ++}\n     ++\n     + static int cmd_config_list(int argc, const char **argv, const char *prefix,\n     + \t\t\t   struct repository *repo UNUSED)\n     + {\n     +@@ builtin/config.c: static int cmd_config_list(int argc, const char **argv, const char *prefix,\n     + \tcheck_argc(argc, 0, 0);\n     + \n     + \tlocation_options_init(&location_opts, prefix);\n     +-\tdisplay_options_init(&display_opts);\n     ++\tdisplay_options_init_list(&display_opts);\n     + \n     + \tsetup_auto_pager(\"config\", 1);\n     + \n     +@@ builtin/config.c: static int cmd_config_actions(int argc, const char **argv, const char *prefix)\n     + \n     + \tif (actions == ACTION_LIST) {\n     + \t\tcheck_argc(argc, 0, 0);\n     ++\t\tdisplay_options_init_list(&display_opts);\n     + \t\tif (config_with_options(show_all_config, &display_opts,\n     + \t\t\t\t\t&location_opts.source, the_repository,\n     + \t\t\t\t\t&location_opts.options) < 0) {\n      \n       ## t/t1300-config.sh ##\n      @@ t/t1300-config.sh: done\n     @@ t/t1300-config.sh: done\n       number = 10\n       big = 1M\n      +path = ~/dir\n     ++red = red\n     ++blue = Blue\n     ++date = Fri Jun 4 15:46:55 2010\n       EOF\n       \n       test_expect_success 'identical modern --type specifiers are allowed' '\n     @@ t/t1300-config.sh: test_expect_success 'unset type specifiers may be reset to co\n      +\tsection.big=true\n      +\tEOF\n      +\n     -+\tgit config ${mode_prefix}list --type=bool >actual &&\n     -+\ttest_cmp expect actual\n     ++\ttest_must_fail git config ${mode_prefix}list --type=bool\n      +'\n      +\n      +test_expect_success 'list --type=path shows only canonicalizable path values' '\n     @@ t/t1300-config.sh: test_expect_success 'unset type specifiers may be reset to co\n      +\tsection.number=10\n      +\tsection.big=1M\n      +\tsection.path=$HOME/dir\n     ++\tsection.red=red\n     ++\tsection.blue=Blue\n     ++\tsection.date=Fri Jun 4 15:46:55 2010\n     ++\tEOF\n     ++\n     ++\tgit config ${mode_prefix}list --type=path >actual 2>err &&\n     ++\ttest_cmp expect actual &&\n     ++\ttest_must_be_empty err\n     ++'\n     ++\n     ++test_expect_success 'list --type=expiry-date shows only canonicalizable dates' '\n     ++\tcat >expecterr <<-EOF &&\n     ++\terror: '\\''True'\\'' for '\\''section.foo'\\'' is not a valid timestamp\n     ++\terror: '\\''~/dir'\\'' for '\\''section.path'\\'' is not a valid timestamp\n     ++\terror: '\\''red'\\'' for '\\''section.red'\\'' is not a valid timestamp\n     ++\terror: '\\''Blue'\\'' for '\\''section.blue'\\'' is not a valid timestamp\n     ++\tEOF\n     ++\n     ++\tgit config ${mode_prefix}list --type=expiry-date >actual 2>err &&\n     ++\n     ++\t# section.number and section.big parse as relative dates that could\n     ++\t# have clock skew in their results.\n     ++\ttest_grep section.big actual &&\n     ++\ttest_grep section.number actual &&\n     ++\ttest_grep \"section.date=$(git config --type=expiry-date section.$key)\" actual &&\n     ++\ttest_cmp expecterr err\n     ++'\n     ++\n     ++test_expect_success 'list --type=color shows only canonicalizable color values' '\n     ++\tcat >expect <<-EOF &&\n     ++\tsection.number=<>\n     ++\tsection.red=<RED>\n     ++\tsection.blue=<BLUE>\n     ++\tEOF\n     ++\n     ++\tcat >expecterr <<-EOF &&\n     ++\terror: invalid color value: True\n     ++\terror: invalid color value: 1M\n     ++\terror: invalid color value: ~/dir\n     ++\terror: invalid color value: Fri Jun 4 15:46:55 2010\n      +\tEOF\n      +\n     -+\tgit config ${mode_prefix}list --type=path >actual &&\n     -+\ttest_cmp expect actual\n     ++\tgit config ${mode_prefix}list --type=color >actual.raw 2>err &&\n     ++\ttest_decode_color <actual.raw >actual &&\n     ++\ttest_cmp expect actual &&\n     ++\ttest_cmp expecterr err\n      +'\n      +\n       test_expect_success '--type rejects unknown specifiers' '\n  4:  5601a5a84f !  4:  2bca4d2316 config: create special init for list mode\n     @@ Metadata\n      Author: Derrick Stolee <stolee@gmail.com>\n      \n       ## Commit message ##\n     -    config: create special init for list mode\n     +    config: format int64s gently\n      \n     -    When listing multiple values, our initial settings for the output format\n     -    is different. Add a new init helper to specify the fact that keys should\n     -    be shown and also add the default delimiters as they were unset in some\n     -    cases.\n     -\n     -    There are two places, differing by the 'git config list' and 'git config\n     -    --list' modes.\n     +    Move the logic for formatting int64 config values into a helper method\n     +    and use gentle parsing when needed.\n      \n          Signed-off-by: Derrick Stolee <stolee@gmail.com>\n      \n       ## builtin/config.c ##\n     -@@ builtin/config.c: static void display_options_init(struct config_display_options *opts)\n     - \t}\n     - }\n     +@@ builtin/config.c: struct strbuf_list {\n     + \tint alloc;\n     + };\n       \n     -+static void display_options_init_list(struct config_display_options *opts)\n     ++static int format_config_int64(struct strbuf *buf,\n     ++\t\t\t       const char *key_,\n     ++\t\t\t       const char *value_,\n     ++\t\t\t       const struct key_value_info *kvi,\n     ++\t\t\t       int gently)\n      +{\n     -+\topts->show_keys = 1;\n     -+\n     -+\tif (opts->end_nul) {\n     -+\t\tdisplay_options_init(opts);\n     ++\tint64_t v = 0;\n     ++\tif (gently) {\n     ++\t\tif (git_parse_int64(value_, &v))\n     ++\t\t\treturn -1;\n      +\t} else {\n     -+\t\topts->term = '\\n';\n     -+\t\topts->delim = ' ';\n     -+\t\topts->key_delim = '=';\n     ++\t\t/* may die() */\n     ++\t\tv = git_config_int64(key_, value_ ? value_ : \"\", kvi);\n      +\t}\n     ++\n     ++\tstrbuf_addf(buf, \"%\"PRId64, v);\n     ++\treturn 0;\n      +}\n      +\n     - static int cmd_config_list(int argc, const char **argv, const char *prefix,\n     - \t\t\t   struct repository *repo UNUSED)\n     + /*\n     +  * Format the configuration key-value pair (`key_`, `value_`) and\n     +  * append it into strbuf `buf`.  Returns a negative value on failure,\n     +@@ builtin/config.c: struct strbuf_list {\n     + static int format_config(const struct config_display_options *opts,\n     + \t\t\t struct strbuf *buf, const char *key_,\n     + \t\t\t const char *value_, const struct key_value_info *kvi,\n     +-\t\t\t int gently UNUSED)\n     ++\t\t\t int gently)\n       {\n     -@@ builtin/config.c: static int cmd_config_list(int argc, const char **argv, const char *prefix,\n     - \tcheck_argc(argc, 0, 0);\n     ++\tint res = 0;\n     + \tif (opts->show_scope)\n     + \t\tshow_config_scope(opts, kvi, buf);\n     + \tif (opts->show_origin)\n     +@@ builtin/config.c: static int format_config(const struct config_display_options *opts,\n     + \t\t\tstrbuf_addch(buf, opts->key_delim);\n       \n     - \tlocation_options_init(&location_opts, prefix);\n     --\tdisplay_options_init(&display_opts);\n     -+\tdisplay_options_init_list(&display_opts);\n     - \n     - \tsetup_auto_pager(\"config\", 1);\n     - \n     -@@ builtin/config.c: static int cmd_config_actions(int argc, const char **argv, const char *prefix)\n     + \t\tif (opts->type == TYPE_INT)\n     +-\t\t\tstrbuf_addf(buf, \"%\"PRId64,\n     +-\t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\", kvi));\n     ++\t\t\tres = format_config_int64(buf, key_, value_, kvi, gently);\n     + \t\telse if (opts->type == TYPE_BOOL)\n     + \t\t\tstrbuf_addstr(buf, git_config_bool(key_, value_) ?\n     + \t\t\t\t      \"true\" : \"false\");\n     +@@ builtin/config.c: static int format_config(const struct config_display_options *opts,\n     + \t\t}\n     + \t}\n     + \tstrbuf_addch(buf, opts->term);\n     +-\treturn 0;\n     ++\treturn res;\n     + }\n       \n     - \tif (actions == ACTION_LIST) {\n     - \t\tcheck_argc(argc, 0, 0);\n     -+\t\tdisplay_options_init_list(&display_opts);\n     - \t\tif (config_with_options(show_all_config, &display_opts,\n     - \t\t\t\t\t&location_opts.source, the_repository,\n     - \t\t\t\t\t&location_opts.options) < 0) {\n     + static int show_all_config(const char *key_, const char *value_,\n  -:  ---------- >  5:  f8e0b8304f config: format bools gently\n  -:  ---------- >  6:  0a428d2ffe config: format bools or ints gently\n  -:  ---------- >  7:  3fec3abbd6 config: format bools or strings in helper\n  2:  8d3a6a8265 !  8:  fafafc5465 parse: add git_parse_maybe_pathname()\n     @@ Metadata\n       ## Commit message ##\n          parse: add git_parse_maybe_pathname()\n      \n     -    This extraction of logic from config.c's git_config_pathname() allows\n     -    for parsing a fully-qualified path from a relative path along with\n     -    validation of the existence of the path without failing with a die().\n     +    The git_config_pathname() method parses a config value as a path, but\n     +    always die()s on an error. Move this logic into a gentler parsing\n     +    algorithm that will return an error value instead of ending the process.\n      \n          Signed-off-by: Derrick Stolee <stolee@gmail.com>\n      \n  -:  ---------- >  9:  d1cfa0c5e1 config: format paths gently\n  -:  ---------- > 10:  9221ca2352 config: format expiry dates gently\n  -:  ---------- > 11:  ddf6131ac9 color: add color_parse_gently()\n  -:  ---------- > 12:  d14937e6d1 config: format colors gently\n  -:  ---------- > 13:  48fc882785 config: restructure format_config()\n\n-- \ngitgitgadget\n"},{"id":"535978","messageId":"bca83d8ca86ead4df5cd424a8356367915248c43.1771026918.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v2.git.1771026918.gitgitgadget@gmail.com","subject":"[PATCH v2 01/13] config: move show_all_config()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-13T23:55:06Z","receivedAt":"2026-02-13T23:55:23Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nIn anticipation of using format_config() in this method, move\nshow_all_config() lower in the file without changes.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c | 48 ++++++++++++++++++++++++------------------------\n 1 file changed, 24 insertions(+), 24 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 288ebdfdaa..237f7a934d 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -231,30 +231,6 @@ static void show_config_scope(const struct config_display_options *opts,\n \tstrbuf_addch(buf, term);\n }\n \n-static int show_all_config(const char *key_, const char *value_,\n-\t\t\t   const struct config_context *ctx,\n-\t\t\t   void *cb)\n-{\n-\tconst struct config_display_options *opts = cb;\n-\tconst struct key_value_info *kvi = ctx->kvi;\n-\n-\tif (opts->show_origin || opts->show_scope) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tif (opts->show_scope)\n-\t\t\tshow_config_scope(opts, kvi, &buf);\n-\t\tif (opts->show_origin)\n-\t\t\tshow_config_origin(opts, kvi, &buf);\n-\t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n-\t\tfwrite(buf.buf, 1, buf.len, stdout);\n-\t\tstrbuf_release(&buf);\n-\t}\n-\tif (!opts->omit_values && value_)\n-\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n-\telse\n-\t\tprintf(\"%s%c\", key_, opts->term);\n-\treturn 0;\n-}\n-\n struct strbuf_list {\n \tstruct strbuf *items;\n \tint nr;\n@@ -332,6 +308,30 @@ static int format_config(const struct config_display_options *opts,\n \treturn 0;\n }\n \n+static int show_all_config(const char *key_, const char *value_,\n+\t\t\t   const struct config_context *ctx,\n+\t\t\t   void *cb)\n+{\n+\tconst struct config_display_options *opts = cb;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n+\n+\tif (opts->show_origin || opts->show_scope) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tif (opts->show_scope)\n+\t\t\tshow_config_scope(opts, kvi, &buf);\n+\t\tif (opts->show_origin)\n+\t\t\tshow_config_origin(opts, kvi, &buf);\n+\t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n+\t\tfwrite(buf.buf, 1, buf.len, stdout);\n+\t\tstrbuf_release(&buf);\n+\t}\n+\tif (!opts->omit_values && value_)\n+\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n+\telse\n+\t\tprintf(\"%s%c\", key_, opts->term);\n+\treturn 0;\n+}\n+\n #define GET_VALUE_ALL        (1 << 0)\n #define GET_VALUE_KEY_REGEXP (1 << 1)\n \n-- \ngitgitgadget\n\n"},{"id":"535979","messageId":"93c94a1b257d26a23942e902630c220e4a4f8eaf.1771026918.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v2.git.1771026918.gitgitgadget@gmail.com","subject":"[PATCH v2 02/13] config: add 'gently' parameter to format_config()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-13T23:55:07Z","receivedAt":"2026-02-13T23:55:25Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nThis parameter is set to 0 for all current callers and is UNUSED.\nHowever, we will start using this option in future changes and in a\ncritical change that requires gentle parsing (not using die()) to try\nparsing all values in a list.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 237f7a934d..b4c4228311 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -242,10 +242,14 @@ struct strbuf_list {\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n  * 0 on success, 1 on a missing optional value (i.e., telling the\n  * caller to pretend that <key_,value_> did not exist).\n+ *\n+ * Note: 'gently' is currently ignored, but will be implemented in\n+ * a future change.\n  */\n static int format_config(const struct config_display_options *opts,\n \t\t\t struct strbuf *buf, const char *key_,\n-\t\t\t const char *value_, const struct key_value_info *kvi)\n+\t\t\t const char *value_, const struct key_value_info *kvi,\n+\t\t\t int gently UNUSED)\n {\n \tif (opts->show_scope)\n \t\tshow_config_scope(opts, kvi, buf);\n@@ -372,7 +376,7 @@ static int collect_config(const char *key_, const char *value_,\n \tstrbuf_init(&values->items[values->nr], 0);\n \n \tstatus = format_config(data->display_opts, &values->items[values->nr++],\n-\t\t\t       key_, value_, kvi);\n+\t\t\t       key_, value_, kvi, 0);\n \tif (status < 0)\n \t\treturn status;\n \tif (status) {\n@@ -463,7 +467,7 @@ static int get_value(const struct config_location_options *opts,\n \t\tstrbuf_init(item, 0);\n \n \t\tstatus = format_config(display_opts, item, key_,\n-\t\t\t\t       display_opts->default_value, &kvi);\n+\t\t\t\t       display_opts->default_value, &kvi, 0);\n \t\tif (status < 0)\n \t\t\tdie(_(\"failed to format default config value: %s\"),\n \t\t\t    display_opts->default_value);\n@@ -743,7 +747,7 @@ static int get_urlmatch(const struct config_location_options *opts,\n \n \t\tstatus = format_config(&display_opts, &buf, item->string,\n \t\t\t\t       matched->value_is_null ? NULL : matched->value.buf,\n-\t\t\t\t       &matched->kvi);\n+\t\t\t\t       &matched->kvi, 0);\n \t\tif (!status)\n \t\t\tfwrite(buf.buf, 1, buf.len, stdout);\n \t\tstrbuf_release(&buf);\n-- \ngitgitgadget\n\n"},{"id":"535980","messageId":"6d2a48a3b7f61c068392e66933caaf1d78055857.1771026918.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v2.git.1771026918.gitgitgadget@gmail.com","subject":"[PATCH v2 03/13] config: make 'git config list --type=<X>' work","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-13T23:55:08Z","receivedAt":"2026-02-13T23:55:27Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nPreviously, the --type=<X> argument to 'git config list' was ignored and\ndid nothing. Now, we add the use of format_config() to the\nshow_all_config() function so each key-value pair is attempted to be\nparsed. This is our first use of the 'gently' parameter with a nonzero\nvalue.\n\nWhen listing multiple values, our initial settings for the output format\nis different. Add a new init helper to specify the fact that keys should\nbe shown and also add the default delimiters as they were unset in some\ncases.\n\nIf there is an error in parsing, then the row is not output.\n\nThis is a change in behavior! We are starting to respect an option that\nwas previously ignored, leading to potential user confusion. This is\nprobably still a good option, since the --type argument did not change\nbehavior at all previously, so users can get the behavior they expect by\nremoving the --type argument or adding the --no-type argument.\n\nt1300-config.sh is updated with the current behavior of this formatting\nlogic to justify the upcoming refactoring of format_config() that will\nincrementally fix some of these cases to be more user-friendly.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n Documentation/git-config.adoc |  3 ++\n builtin/config.c              | 35 ++++++++++--------\n t/t1300-config.sh             | 70 ++++++++++++++++++++++++++++++++++-\n 3 files changed, 92 insertions(+), 16 deletions(-)\n\ndiff --git a/Documentation/git-config.adoc b/Documentation/git-config.adoc\nindex ac3b536a15..5300dd4c51 100644\n--- a/Documentation/git-config.adoc\n+++ b/Documentation/git-config.adoc\n@@ -240,6 +240,9 @@ Valid `<type>`'s include:\n   that the given value is canonicalize-able as an ANSI color, but it is written\n   as-is.\n +\n+If the command is in `list` mode, then the `--type <type>` argument will apply\n+to each listed config value. If the value does not successfully parse in that\n+format, then it will be omitted from the list.\n \n --bool::\n --int::\ndiff --git a/builtin/config.c b/builtin/config.c\nindex b4c4228311..4c4c791883 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -318,21 +318,12 @@ static int show_all_config(const char *key_, const char *value_,\n {\n \tconst struct config_display_options *opts = cb;\n \tconst struct key_value_info *kvi = ctx->kvi;\n+\tstruct strbuf formatted = STRBUF_INIT;\n \n-\tif (opts->show_origin || opts->show_scope) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tif (opts->show_scope)\n-\t\t\tshow_config_scope(opts, kvi, &buf);\n-\t\tif (opts->show_origin)\n-\t\t\tshow_config_origin(opts, kvi, &buf);\n-\t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n-\t\tfwrite(buf.buf, 1, buf.len, stdout);\n-\t\tstrbuf_release(&buf);\n-\t}\n-\tif (!opts->omit_values && value_)\n-\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n-\telse\n-\t\tprintf(\"%s%c\", key_, opts->term);\n+\tif (format_config(opts, &formatted, key_, value_, kvi, 1) >= 0)\n+\t\tfwrite(formatted.buf, 1, formatted.len, stdout);\n+\n+\tstrbuf_release(&formatted);\n \treturn 0;\n }\n \n@@ -872,6 +863,19 @@ static void display_options_init(struct config_display_options *opts)\n \t}\n }\n \n+static void display_options_init_list(struct config_display_options *opts)\n+{\n+\topts->show_keys = 1;\n+\n+\tif (opts->end_nul) {\n+\t\tdisplay_options_init(opts);\n+\t} else {\n+\t\topts->term = '\\n';\n+\t\topts->delim = ' ';\n+\t\topts->key_delim = '=';\n+\t}\n+}\n+\n static int cmd_config_list(int argc, const char **argv, const char *prefix,\n \t\t\t   struct repository *repo UNUSED)\n {\n@@ -890,7 +894,7 @@ static int cmd_config_list(int argc, const char **argv, const char *prefix,\n \tcheck_argc(argc, 0, 0);\n \n \tlocation_options_init(&location_opts, prefix);\n-\tdisplay_options_init(&display_opts);\n+\tdisplay_options_init_list(&display_opts);\n \n \tsetup_auto_pager(\"config\", 1);\n \n@@ -1321,6 +1325,7 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix)\n \n \tif (actions == ACTION_LIST) {\n \t\tcheck_argc(argc, 0, 0);\n+\t\tdisplay_options_init_list(&display_opts);\n \t\tif (config_with_options(show_all_config, &display_opts,\n \t\t\t\t\t&location_opts.source, the_repository,\n \t\t\t\t\t&location_opts.options) < 0) {\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 9850fcd5b5..362e580604 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2459,9 +2459,13 @@ done\n \n cat >.git/config <<-\\EOF &&\n [section]\n-foo = true\n+foo = True\n number = 10\n big = 1M\n+path = ~/dir\n+red = red\n+blue = Blue\n+date = Fri Jun 4 15:46:55 2010\n EOF\n \n test_expect_success 'identical modern --type specifiers are allowed' '\n@@ -2503,6 +2507,70 @@ test_expect_success 'unset type specifiers may be reset to conflicting ones' '\n \ttest_cmp_config 1048576 --type=bool --no-type --type=int section.big\n '\n \n+test_expect_success 'list --type=bool shows only canonicalizable bool values' '\n+\tcat >expect <<-EOF &&\n+\tsection.foo=true\n+\tsection.number=true\n+\tsection.big=true\n+\tEOF\n+\n+\ttest_must_fail git config ${mode_prefix}list --type=bool\n+'\n+\n+test_expect_success 'list --type=path shows only canonicalizable path values' '\n+\tcat >expect <<-EOF &&\n+\tsection.foo=True\n+\tsection.number=10\n+\tsection.big=1M\n+\tsection.path=$HOME/dir\n+\tsection.red=red\n+\tsection.blue=Blue\n+\tsection.date=Fri Jun 4 15:46:55 2010\n+\tEOF\n+\n+\tgit config ${mode_prefix}list --type=path >actual 2>err &&\n+\ttest_cmp expect actual &&\n+\ttest_must_be_empty err\n+'\n+\n+test_expect_success 'list --type=expiry-date shows only canonicalizable dates' '\n+\tcat >expecterr <<-EOF &&\n+\terror: '\\''True'\\'' for '\\''section.foo'\\'' is not a valid timestamp\n+\terror: '\\''~/dir'\\'' for '\\''section.path'\\'' is not a valid timestamp\n+\terror: '\\''red'\\'' for '\\''section.red'\\'' is not a valid timestamp\n+\terror: '\\''Blue'\\'' for '\\''section.blue'\\'' is not a valid timestamp\n+\tEOF\n+\n+\tgit config ${mode_prefix}list --type=expiry-date >actual 2>err &&\n+\n+\t# section.number and section.big parse as relative dates that could\n+\t# have clock skew in their results.\n+\ttest_grep section.big actual &&\n+\ttest_grep section.number actual &&\n+\ttest_grep \"section.date=$(git config --type=expiry-date section.$key)\" actual &&\n+\ttest_cmp expecterr err\n+'\n+\n+test_expect_success 'list --type=color shows only canonicalizable color values' '\n+\tcat >expect <<-EOF &&\n+\tsection.number=<>\n+\tsection.red=<RED>\n+\tsection.blue=<BLUE>\n+\tEOF\n+\n+\tcat >expecterr <<-EOF &&\n+\terror: invalid color value: True\n+\terror: invalid color value: 1M\n+\terror: invalid color value: ~/dir\n+\terror: invalid color value: Fri Jun 4 15:46:55 2010\n+\tEOF\n+\n+\tgit config ${mode_prefix}list --type=color >actual.raw 2>err &&\n+\ttest_decode_color <actual.raw >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_cmp expecterr err\n+'\n+\n test_expect_success '--type rejects unknown specifiers' '\n \ttest_must_fail git config --type=nonsense section.foo 2>error &&\n \ttest_grep \"unrecognized --type argument\" error\n-- \ngitgitgadget\n\n"},{"id":"535981","messageId":"2bca4d231686e33ea9d4d85b10fcffd60a63ad46.1771026918.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v2.git.1771026918.gitgitgadget@gmail.com","subject":"[PATCH v2 04/13] config: format int64s gently","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-13T23:55:09Z","receivedAt":"2026-02-13T23:55:29Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nMove the logic for formatting int64 config values into a helper method\nand use gentle parsing when needed.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c | 27 +++++++++++++++++++++++----\n 1 file changed, 23 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 4c4c791883..d259a91d53 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -237,6 +237,25 @@ struct strbuf_list {\n \tint alloc;\n };\n \n+static int format_config_int64(struct strbuf *buf,\n+\t\t\t       const char *key_,\n+\t\t\t       const char *value_,\n+\t\t\t       const struct key_value_info *kvi,\n+\t\t\t       int gently)\n+{\n+\tint64_t v = 0;\n+\tif (gently) {\n+\t\tif (git_parse_int64(value_, &v))\n+\t\t\treturn -1;\n+\t} else {\n+\t\t/* may die() */\n+\t\tv = git_config_int64(key_, value_ ? value_ : \"\", kvi);\n+\t}\n+\n+\tstrbuf_addf(buf, \"%\"PRId64, v);\n+\treturn 0;\n+}\n+\n /*\n  * Format the configuration key-value pair (`key_`, `value_`) and\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n@@ -249,8 +268,9 @@ struct strbuf_list {\n static int format_config(const struct config_display_options *opts,\n \t\t\t struct strbuf *buf, const char *key_,\n \t\t\t const char *value_, const struct key_value_info *kvi,\n-\t\t\t int gently UNUSED)\n+\t\t\t int gently)\n {\n+\tint res = 0;\n \tif (opts->show_scope)\n \t\tshow_config_scope(opts, kvi, buf);\n \tif (opts->show_origin)\n@@ -262,8 +282,7 @@ static int format_config(const struct config_display_options *opts,\n \t\t\tstrbuf_addch(buf, opts->key_delim);\n \n \t\tif (opts->type == TYPE_INT)\n-\t\t\tstrbuf_addf(buf, \"%\"PRId64,\n-\t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\", kvi));\n+\t\t\tres = format_config_int64(buf, key_, value_, kvi, gently);\n \t\telse if (opts->type == TYPE_BOOL)\n \t\t\tstrbuf_addstr(buf, git_config_bool(key_, value_) ?\n \t\t\t\t      \"true\" : \"false\");\n@@ -309,7 +328,7 @@ static int format_config(const struct config_display_options *opts,\n \t\t}\n \t}\n \tstrbuf_addch(buf, opts->term);\n-\treturn 0;\n+\treturn res;\n }\n \n static int show_all_config(const char *key_, const char *value_,\n-- \ngitgitgadget\n\n"},{"id":"535982","messageId":"f8e0b8304fcff1d380029542fe2915c2d67a92e3.1771026918.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v2.git.1771026918.gitgitgadget@gmail.com","subject":"[PATCH v2 05/13] config: format bools gently","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-13T23:55:10Z","receivedAt":"2026-02-13T23:55:32Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nMove the logic for formatting bool config values into a helper method\nand use gentle parsing when needed.\n\nThis makes 'git config list --type=bool' not fail when coming across a\nnon-boolean value. Such unparseable values are filtered out quietly.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c  | 21 +++++++++++++++++++--\n t/t1300-config.sh |  4 +++-\n 2 files changed, 22 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex d259a91d53..2c169fc126 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -256,6 +256,24 @@ static int format_config_int64(struct strbuf *buf,\n \treturn 0;\n }\n \n+static int format_config_bool(struct strbuf *buf,\n+\t\t\t      const char *key_,\n+\t\t\t      const char *value_,\n+\t\t\t      int gently)\n+{\n+\tint v = 0;\n+\tif (gently) {\n+\t\tif ((v = git_parse_maybe_bool(value_)) < 0)\n+\t\t\treturn -1;\n+\t} else {\n+\t\t/* may die() */\n+\t\tv = git_config_bool(key_, value_);\n+\t}\n+\n+\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n+\treturn 0;\n+}\n+\n /*\n  * Format the configuration key-value pair (`key_`, `value_`) and\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n@@ -284,8 +302,7 @@ static int format_config(const struct config_display_options *opts,\n \t\tif (opts->type == TYPE_INT)\n \t\t\tres = format_config_int64(buf, key_, value_, kvi, gently);\n \t\telse if (opts->type == TYPE_BOOL)\n-\t\t\tstrbuf_addstr(buf, git_config_bool(key_, value_) ?\n-\t\t\t\t      \"true\" : \"false\");\n+\t\t\tres = format_config_bool(buf, key_, value_, gently);\n \t\telse if (opts->type == TYPE_BOOL_OR_INT) {\n \t\t\tint is_bool, v;\n \t\t\tv = git_config_bool_or_int(key_, value_, kvi,\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 362e580604..59a82b9aef 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2514,7 +2514,9 @@ test_expect_success 'list --type=bool shows only canonicalizable bool values' '\n \tsection.big=true\n \tEOF\n \n-\ttest_must_fail git config ${mode_prefix}list --type=bool\n+\tgit config ${mode_prefix}list --type=bool >actual 2>err &&\n+\ttest_cmp expect actual &&\n+\ttest_must_be_empty err\n '\n \n test_expect_success 'list --type=path shows only canonicalizable path values' '\n-- \ngitgitgadget\n\n"},{"id":"535983","messageId":"0a428d2ffe092c2af6789b5e698dc769b0eb883a.1771026918.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v2.git.1771026918.gitgitgadget@gmail.com","subject":"[PATCH v2 06/13] config: format bools or ints gently","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-13T23:55:11Z","receivedAt":"2026-02-13T23:55:34Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nMove the logic for formatting bool-or-int config values into a helper\nmethod and use gentle parsing when needed.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c | 40 +++++++++++++++++++++++++++++++---------\n 1 file changed, 31 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 2c169fc126..2c93e1725b 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -274,6 +274,34 @@ static int format_config_bool(struct strbuf *buf,\n \treturn 0;\n }\n \n+static int format_config_bool_or_int(struct strbuf *buf,\n+\t\t\t\t     const char *key_,\n+\t\t\t\t     const char *value_,\n+\t\t\t\t     const struct key_value_info *kvi,\n+\t\t\t\t     int gently)\n+{\n+\tint v, is_bool = 0;\n+\n+\tif (gently) {\n+\t\tv = git_parse_maybe_bool_text(value_);\n+\n+\t\tif (v >= 0)\n+\t\t\tis_bool = 1;\n+\t\telse if (git_parse_int(value_, &v))\n+\t\t\treturn -1;\n+\t} else {\n+\t\tv = git_config_bool_or_int(key_, value_, kvi,\n+\t\t\t\t\t   &is_bool);\n+\t}\n+\n+\tif (is_bool)\n+\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n+\telse\n+\t\tstrbuf_addf(buf, \"%d\", v);\n+\n+\treturn 0;\n+}\n+\n /*\n  * Format the configuration key-value pair (`key_`, `value_`) and\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n@@ -303,15 +331,9 @@ static int format_config(const struct config_display_options *opts,\n \t\t\tres = format_config_int64(buf, key_, value_, kvi, gently);\n \t\telse if (opts->type == TYPE_BOOL)\n \t\t\tres = format_config_bool(buf, key_, value_, gently);\n-\t\telse if (opts->type == TYPE_BOOL_OR_INT) {\n-\t\t\tint is_bool, v;\n-\t\t\tv = git_config_bool_or_int(key_, value_, kvi,\n-\t\t\t\t\t\t   &is_bool);\n-\t\t\tif (is_bool)\n-\t\t\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n-\t\t\telse\n-\t\t\t\tstrbuf_addf(buf, \"%d\", v);\n-\t\t} else if (opts->type == TYPE_BOOL_OR_STR) {\n+\t\telse if (opts->type == TYPE_BOOL_OR_INT)\n+\t\t\tres = format_config_bool_or_int(buf, key_, value_, kvi, gently);\n+\t\telse if (opts->type == TYPE_BOOL_OR_STR) {\n \t\t\tint v = git_parse_maybe_bool(value_);\n \t\t\tif (v < 0)\n \t\t\t\tstrbuf_addstr(buf, value_);\n-- \ngitgitgadget\n\n"},{"id":"535984","messageId":"3fec3abbd62307bec885cab0198007c13f0f1d8e.1771026918.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v2.git.1771026918.gitgitgadget@gmail.com","subject":"[PATCH v2 07/13] config: format bools or strings in helper","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-13T23:55:12Z","receivedAt":"2026-02-13T23:55:36Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nMove the logic for formatting bool-or-string config values into a\nhelper. This parsing has always been gentle, so this is not unlocking\nnew behavior. This extraction is only to match the formatting of the\nother cases that do need a behavior change.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c | 22 +++++++++++++++-------\n 1 file changed, 15 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 2c93e1725b..0c539ff98e 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -302,6 +302,18 @@ static int format_config_bool_or_int(struct strbuf *buf,\n \treturn 0;\n }\n \n+/* This mode is always gentle. */\n+static int format_config_bool_or_str(struct strbuf *buf,\n+\t\t\t\t     const char *value_)\n+{\n+\tint v = git_parse_maybe_bool(value_);\n+\tif (v < 0)\n+\t\tstrbuf_addstr(buf, value_);\n+\telse\n+\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n+\treturn 0;\n+}\n+\n /*\n  * Format the configuration key-value pair (`key_`, `value_`) and\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n@@ -333,13 +345,9 @@ static int format_config(const struct config_display_options *opts,\n \t\t\tres = format_config_bool(buf, key_, value_, gently);\n \t\telse if (opts->type == TYPE_BOOL_OR_INT)\n \t\t\tres = format_config_bool_or_int(buf, key_, value_, kvi, gently);\n-\t\telse if (opts->type == TYPE_BOOL_OR_STR) {\n-\t\t\tint v = git_parse_maybe_bool(value_);\n-\t\t\tif (v < 0)\n-\t\t\t\tstrbuf_addstr(buf, value_);\n-\t\t\telse\n-\t\t\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n-\t\t} else if (opts->type == TYPE_PATH) {\n+\t\telse if (opts->type == TYPE_BOOL_OR_STR)\n+\t\t\tres = format_config_bool_or_str(buf, value_);\n+\t\telse if (opts->type == TYPE_PATH) {\n \t\t\tchar *v;\n \t\t\tif (git_config_pathname(&v, key_, value_) < 0)\n \t\t\t\treturn -1;\n-- \ngitgitgadget\n\n"},{"id":"535985","messageId":"fafafc5465979dc62b7c8253a6d2053bc0aa171a.1771026918.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v2.git.1771026918.gitgitgadget@gmail.com","subject":"[PATCH v2 08/13] parse: add git_parse_maybe_pathname()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-13T23:55:13Z","receivedAt":"2026-02-13T23:55:38Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nThe git_config_pathname() method parses a config value as a path, but\nalways die()s on an error. Move this logic into a gentler parsing\nalgorithm that will return an error value instead of ending the process.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n config.c | 14 +-------------\n parse.c  | 24 ++++++++++++++++++++++++\n parse.h  |  2 ++\n 3 files changed, 27 insertions(+), 13 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 7f6d53b473..83257b7a97 100644\n--- a/config.c\n+++ b/config.c\n@@ -1278,24 +1278,12 @@ int git_config_string(char **dest, const char *var, const char *value)\n \n int git_config_pathname(char **dest, const char *var, const char *value)\n {\n-\tbool is_optional;\n-\tchar *path;\n-\n \tif (!value)\n \t\treturn config_error_nonbool(var);\n \n-\tis_optional = skip_prefix(value, \":(optional)\", &value);\n-\tpath = interpolate_path(value, 0);\n-\tif (!path)\n+\tif (git_parse_maybe_pathname(value, dest) < 0)\n \t\tdie(_(\"failed to expand user dir in: '%s'\"), value);\n \n-\tif (is_optional && is_missing_file(path)) {\n-\t\tfree(path);\n-\t\t*dest = NULL;\n-\t\treturn 0;\n-\t}\n-\n-\t*dest = path;\n \treturn 0;\n }\n \ndiff --git a/parse.c b/parse.c\nindex 48313571aa..3f37f0b93a 100644\n--- a/parse.c\n+++ b/parse.c\n@@ -1,6 +1,7 @@\n #include \"git-compat-util.h\"\n #include \"gettext.h\"\n #include \"parse.h\"\n+#include \"path.h\"\n \n static uintmax_t get_unit_factor(const char *end)\n {\n@@ -209,3 +210,26 @@ unsigned long git_env_ulong(const char *k, unsigned long val)\n \t\tdie(_(\"failed to parse %s\"), k);\n \treturn val;\n }\n+\n+int git_parse_maybe_pathname(const char *value, char **dest)\n+{\n+\tbool is_optional;\n+\tchar *path;\n+\n+\tif (!value)\n+\t\treturn -1;\n+\n+\tis_optional = skip_prefix(value, \":(optional)\", &value);\n+\tpath = interpolate_path(value, 0);\n+\tif (!path)\n+\t\treturn -1;\n+\n+\tif (is_optional && is_missing_file(path)) {\n+\t\tfree(path);\n+\t\t*dest = NULL;\n+\t\treturn 0;\n+\t}\n+\n+\t*dest = path;\n+\treturn 0;\n+}\ndiff --git a/parse.h b/parse.h\nindex ea32de9a91..4f97c3727a 100644\n--- a/parse.h\n+++ b/parse.h\n@@ -19,4 +19,6 @@ int git_parse_maybe_bool_text(const char *value);\n int git_env_bool(const char *, int);\n unsigned long git_env_ulong(const char *, unsigned long);\n \n+int git_parse_maybe_pathname(const char *value, char **dest);\n+\n #endif /* PARSE_H */\n-- \ngitgitgadget\n\n"},{"id":"535986","messageId":"d1cfa0c5e1295d7b6655a0962bd47f8e89bc4a04.1771026918.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v2.git.1771026918.gitgitgadget@gmail.com","subject":"[PATCH v2 09/13] config: format paths gently","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-13T23:55:14Z","receivedAt":"2026-02-13T23:55:40Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nMove the logic for formatting path config values into a helper method\nand use gentle parsing when needed.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c | 35 +++++++++++++++++++++++++----------\n 1 file changed, 25 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 0c539ff98e..4664651dd2 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -314,6 +314,28 @@ static int format_config_bool_or_str(struct strbuf *buf,\n \treturn 0;\n }\n \n+static int format_config_path(struct strbuf *buf,\n+\t\t\t      const char *key_,\n+\t\t\t      const char *value_,\n+\t\t\t      int gently)\n+{\n+\tchar *v;\n+\tif (gently) {\n+\t\tif (git_parse_maybe_pathname(value_, &v) < 0)\n+\t\t\treturn -1;\n+\t} else if (git_config_pathname(&v, key_, value_) < 0) {\n+\t\treturn -1;\n+\t}\n+\n+\tif (v)\n+\t\tstrbuf_addstr(buf, v);\n+\telse\n+\t\treturn 1; /* :(optional)no-such-file */\n+\n+\tfree(v);\n+\treturn 0;\n+}\n+\n /*\n  * Format the configuration key-value pair (`key_`, `value_`) and\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n@@ -347,16 +369,9 @@ static int format_config(const struct config_display_options *opts,\n \t\t\tres = format_config_bool_or_int(buf, key_, value_, kvi, gently);\n \t\telse if (opts->type == TYPE_BOOL_OR_STR)\n \t\t\tres = format_config_bool_or_str(buf, value_);\n-\t\telse if (opts->type == TYPE_PATH) {\n-\t\t\tchar *v;\n-\t\t\tif (git_config_pathname(&v, key_, value_) < 0)\n-\t\t\t\treturn -1;\n-\t\t\tif (v)\n-\t\t\t\tstrbuf_addstr(buf, v);\n-\t\t\telse\n-\t\t\t\treturn 1; /* :(optional)no-such-file */\n-\t\t\tfree((char *)v);\n-\t\t} else if (opts->type == TYPE_EXPIRY_DATE) {\n+\t\telse if (opts->type == TYPE_PATH)\n+\t\t\tres = format_config_path(buf, key_, value_, gently);\n+\t\telse if (opts->type == TYPE_EXPIRY_DATE) {\n \t\t\ttimestamp_t t;\n \t\t\tif (git_config_expiry_date(&t, key_, value_) < 0)\n \t\t\t\treturn -1;\n-- \ngitgitgadget\n\n"},{"id":"535987","messageId":"9221ca2352d65f3cbdec00f7577089c734799919.1771026918.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v2.git.1771026918.gitgitgadget@gmail.com","subject":"[PATCH v2 10/13] config: format expiry dates gently","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-13T23:55:15Z","receivedAt":"2026-02-13T23:55:42Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nMove the logic for formatting expiry date config values into a helper\nmethod and use gentle parsing when needed.\n\nThere is an unfortunate asymmetry in these two parsing methods, but we\nneed to treat a positive response from parse_expiry_date() as an error\nor we will get incorrect values.\n\nThis updates the behavior of 'git config list --type=expiry-date' to be\nquiet when attempting parsing on non-date values.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c  | 27 +++++++++++++++++++++------\n t/t1300-config.sh |  9 +--------\n 2 files changed, 22 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 4664651dd2..71b685d943 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -3,6 +3,7 @@\n #include \"abspath.h\"\n #include \"config.h\"\n #include \"color.h\"\n+#include \"date.h\"\n #include \"editor.h\"\n #include \"environment.h\"\n #include \"gettext.h\"\n@@ -336,6 +337,23 @@ static int format_config_path(struct strbuf *buf,\n \treturn 0;\n }\n \n+static int format_config_expiry_date(struct strbuf *buf,\n+\t\t\t\t     const char *key_,\n+\t\t\t\t     const char *value_,\n+\t\t\t\t     int gently)\n+{\n+\ttimestamp_t t;\n+\tif (gently) {\n+\t\tif (parse_expiry_date(value_, &t))\n+\t\t\treturn -1;\n+\t} else if (git_config_expiry_date(&t, key_, value_) < 0) {\n+\t\treturn -1;\n+\t}\n+\n+\tstrbuf_addf(buf, \"%\"PRItime, t);\n+\treturn 0;\n+}\n+\n /*\n  * Format the configuration key-value pair (`key_`, `value_`) and\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n@@ -371,12 +389,9 @@ static int format_config(const struct config_display_options *opts,\n \t\t\tres = format_config_bool_or_str(buf, value_);\n \t\telse if (opts->type == TYPE_PATH)\n \t\t\tres = format_config_path(buf, key_, value_, gently);\n-\t\telse if (opts->type == TYPE_EXPIRY_DATE) {\n-\t\t\ttimestamp_t t;\n-\t\t\tif (git_config_expiry_date(&t, key_, value_) < 0)\n-\t\t\t\treturn -1;\n-\t\t\tstrbuf_addf(buf, \"%\"PRItime, t);\n-\t\t} else if (opts->type == TYPE_COLOR) {\n+\t\telse if (opts->type == TYPE_EXPIRY_DATE)\n+\t\t\tres = format_config_expiry_date(buf, key_, value_, gently);\n+\t\telse if (opts->type == TYPE_COLOR) {\n \t\t\tchar v[COLOR_MAXLEN];\n \t\t\tif (git_config_color(v, key_, value_) < 0)\n \t\t\t\treturn -1;\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 59a82b9aef..c134d85d8a 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2536,13 +2536,6 @@ test_expect_success 'list --type=path shows only canonicalizable path values' '\n '\n \n test_expect_success 'list --type=expiry-date shows only canonicalizable dates' '\n-\tcat >expecterr <<-EOF &&\n-\terror: '\\''True'\\'' for '\\''section.foo'\\'' is not a valid timestamp\n-\terror: '\\''~/dir'\\'' for '\\''section.path'\\'' is not a valid timestamp\n-\terror: '\\''red'\\'' for '\\''section.red'\\'' is not a valid timestamp\n-\terror: '\\''Blue'\\'' for '\\''section.blue'\\'' is not a valid timestamp\n-\tEOF\n-\n \tgit config ${mode_prefix}list --type=expiry-date >actual 2>err &&\n \n \t# section.number and section.big parse as relative dates that could\n@@ -2550,7 +2543,7 @@ test_expect_success 'list --type=expiry-date shows only canonicalizable dates' '\n \ttest_grep section.big actual &&\n \ttest_grep section.number actual &&\n \ttest_grep \"section.date=$(git config --type=expiry-date section.$key)\" actual &&\n-\ttest_cmp expecterr err\n+\ttest_must_be_empty err\n '\n \n test_expect_success 'list --type=color shows only canonicalizable color values' '\n-- \ngitgitgadget\n\n"},{"id":"535988","messageId":"ddf6131ac9283bfbf4ec9ec000606765d887e6ec.1771026918.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v2.git.1771026918.gitgitgadget@gmail.com","subject":"[PATCH v2 11/13] color: add color_parse_gently()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-13T23:55:16Z","receivedAt":"2026-02-13T23:55:43Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nWhen parsing colors, a failed parse leads to an error message due to the\nresult returning error(). To allow for quiet parsing, create\ncolor_parse_gently().\n\nTo accomplish this, convert the implementation of color_parse_mem() into\na static color_parse_mem_1() helper that adds a 'gently' parameter. The\ncolor_parse_gently() method can then use this. Since it is a near\nequivalent to color_parse(), move that method down in the file so they\ncan be nearby while also appearing after color_parse_mem_1().\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n color.c | 25 ++++++++++++++++++-------\n color.h |  1 +\n 2 files changed, 19 insertions(+), 7 deletions(-)\n\ndiff --git a/color.c b/color.c\nindex 07ac8c9d40..ec8872d2dd 100644\n--- a/color.c\n+++ b/color.c\n@@ -223,11 +223,6 @@ static int parse_attr(const char *name, size_t len)\n \treturn -1;\n }\n \n-int color_parse(const char *value, char *dst)\n-{\n-\treturn color_parse_mem(value, strlen(value), dst);\n-}\n-\n /*\n  * Write the ANSI color codes for \"c\" to \"out\"; the string should\n  * already have the ANSI escape code in it. \"out\" should have enough\n@@ -264,7 +259,8 @@ static int color_empty(const struct color *c)\n \treturn c->type <= COLOR_NORMAL;\n }\n \n-int color_parse_mem(const char *value, int value_len, char *dst)\n+static int color_parse_mem_1(const char *value, int value_len,\n+\t\t\t     char *dst, int gently)\n {\n \tconst char *ptr = value;\n \tint len = value_len;\n@@ -365,10 +361,25 @@ int color_parse_mem(const char *value, int value_len, char *dst)\n \tOUT(0);\n \treturn 0;\n bad:\n-\treturn error(_(\"invalid color value: %.*s\"), value_len, value);\n+\treturn gently ? -1 : error(_(\"invalid color value: %.*s\"), value_len, value);\n #undef OUT\n }\n \n+int color_parse_mem(const char *value, int value_len, char *dst)\n+{\n+\treturn color_parse_mem_1(value, value_len, dst, 0);\n+}\n+\n+int color_parse(const char *value, char *dst)\n+{\n+\treturn color_parse_mem(value, strlen(value), dst);\n+}\n+\n+int color_parse_gently(const char *value, char *dst)\n+{\n+\treturn color_parse_mem_1(value, strlen(value), dst, 1);\n+}\n+\n enum git_colorbool git_config_colorbool(const char *var, const char *value)\n {\n \tif (value) {\ndiff --git a/color.h b/color.h\nindex 43e6c9ad09..30c783405d 100644\n--- a/color.h\n+++ b/color.h\n@@ -118,6 +118,7 @@ bool want_color_fd(int fd, enum git_colorbool var);\n  * terminal.\n  */\n int color_parse(const char *value, char *dst);\n+int color_parse_gently(const char *value, char *dst);\n int color_parse_mem(const char *value, int len, char *dst);\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"535989","messageId":"d14937e6d1e4e33bb7892107061807f2b3bba3d7.1771026918.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v2.git.1771026918.gitgitgadget@gmail.com","subject":"[PATCH v2 12/13] config: format colors gently","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-13T23:55:17Z","receivedAt":"2026-02-13T23:55:45Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nMove the logic for formatting color config value into a helper method\nand use gentle parsing when needed.\n\nThis removes error messages when parsing a list of config values that do\nnot match color formats.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c  | 27 +++++++++++++++++++++------\n t/t1300-config.sh |  9 +--------\n 2 files changed, 22 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 71b685d943..e8c02e5f21 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -354,6 +354,24 @@ static int format_config_expiry_date(struct strbuf *buf,\n \treturn 0;\n }\n \n+static int format_config_color(struct strbuf *buf,\n+\t\t\t       const char *key_,\n+\t\t\t       const char *value_,\n+\t\t\t       int gently)\n+{\n+\tchar v[COLOR_MAXLEN];\n+\n+\tif (gently) {\n+\t\tif (color_parse_gently(value_, v) < 0)\n+\t\t\treturn -1;\n+\t} else if (git_config_color(v, key_, value_) < 0) {\n+\t\treturn -1;\n+\t}\n+\n+\tstrbuf_addstr(buf, v);\n+\treturn 0;\n+}\n+\n /*\n  * Format the configuration key-value pair (`key_`, `value_`) and\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n@@ -391,12 +409,9 @@ static int format_config(const struct config_display_options *opts,\n \t\t\tres = format_config_path(buf, key_, value_, gently);\n \t\telse if (opts->type == TYPE_EXPIRY_DATE)\n \t\t\tres = format_config_expiry_date(buf, key_, value_, gently);\n-\t\telse if (opts->type == TYPE_COLOR) {\n-\t\t\tchar v[COLOR_MAXLEN];\n-\t\t\tif (git_config_color(v, key_, value_) < 0)\n-\t\t\t\treturn -1;\n-\t\t\tstrbuf_addstr(buf, v);\n-\t\t} else if (value_) {\n+\t\telse if (opts->type == TYPE_COLOR)\n+\t\t\tres = format_config_color(buf, key_, value_, gently);\n+\t\telse if (value_) {\n \t\t\tstrbuf_addstr(buf, value_);\n \t\t} else {\n \t\t\t/* Just show the key name; back out delimiter */\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex c134d85d8a..79b2ee203c 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2553,17 +2553,10 @@ test_expect_success 'list --type=color shows only canonicalizable color values'\n \tsection.blue=<BLUE>\n \tEOF\n \n-\tcat >expecterr <<-EOF &&\n-\terror: invalid color value: True\n-\terror: invalid color value: 1M\n-\terror: invalid color value: ~/dir\n-\terror: invalid color value: Fri Jun 4 15:46:55 2010\n-\tEOF\n-\n \tgit config ${mode_prefix}list --type=color >actual.raw 2>err &&\n \ttest_decode_color <actual.raw >actual &&\n \ttest_cmp expect actual &&\n-\ttest_cmp expecterr err\n+\ttest_must_be_empty err\n '\n \n test_expect_success '--type rejects unknown specifiers' '\n-- \ngitgitgadget\n\n"},{"id":"535990","messageId":"48fc882785013b129fba9b8aada6c1f2e239a4cd.1771026918.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v2.git.1771026918.gitgitgadget@gmail.com","subject":"[PATCH v2 13/13] config: restructure format_config()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-13T23:55:18Z","receivedAt":"2026-02-13T23:55:47Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nThe recent changes have replaced the bodies of most if/else-if cases\nwith simple helper method calls. This makes it easy to adapt the\nstructure into a clearer switch statement, leaving a simple if/else in\nthe default case.\n\nMake things a little simpler to read by reducing the nesting depth via a\nnew goto statement when we want to skip values.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c | 59 ++++++++++++++++++++++++++++++++----------------\n 1 file changed, 40 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex e8c02e5f21..1de3ce0eaa 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -393,25 +393,44 @@ static int format_config(const struct config_display_options *opts,\n \t\tshow_config_origin(opts, kvi, buf);\n \tif (opts->show_keys)\n \t\tstrbuf_addstr(buf, key_);\n-\tif (!opts->omit_values) {\n-\t\tif (opts->show_keys)\n-\t\t\tstrbuf_addch(buf, opts->key_delim);\n-\n-\t\tif (opts->type == TYPE_INT)\n-\t\t\tres = format_config_int64(buf, key_, value_, kvi, gently);\n-\t\telse if (opts->type == TYPE_BOOL)\n-\t\t\tres = format_config_bool(buf, key_, value_, gently);\n-\t\telse if (opts->type == TYPE_BOOL_OR_INT)\n-\t\t\tres = format_config_bool_or_int(buf, key_, value_, kvi, gently);\n-\t\telse if (opts->type == TYPE_BOOL_OR_STR)\n-\t\t\tres = format_config_bool_or_str(buf, value_);\n-\t\telse if (opts->type == TYPE_PATH)\n-\t\t\tres = format_config_path(buf, key_, value_, gently);\n-\t\telse if (opts->type == TYPE_EXPIRY_DATE)\n-\t\t\tres = format_config_expiry_date(buf, key_, value_, gently);\n-\t\telse if (opts->type == TYPE_COLOR)\n-\t\t\tres = format_config_color(buf, key_, value_, gently);\n-\t\telse if (value_) {\n+\n+\tif (opts->omit_values)\n+\t\tgoto terminator;\n+\n+\tif (opts->show_keys)\n+\t\tstrbuf_addch(buf, opts->key_delim);\n+\n+\tswitch (opts->type) {\n+\tcase TYPE_INT:\n+\t\tres = format_config_int64(buf, key_, value_, kvi, gently);\n+\t\tbreak;\n+\n+\tcase TYPE_BOOL:\n+\t\tres = format_config_bool(buf, key_, value_, gently);\n+\t\tbreak;\n+\n+\tcase TYPE_BOOL_OR_INT:\n+\t\tres = format_config_bool_or_int(buf, key_, value_, kvi, gently);\n+\t\tbreak;\n+\n+\tcase TYPE_BOOL_OR_STR:\n+\t\tres = format_config_bool_or_str(buf, value_);\n+\t\tbreak;\n+\n+\tcase TYPE_PATH:\n+\t\tres = format_config_path(buf, key_, value_, gently);\n+\t\tbreak;\n+\n+\tcase TYPE_EXPIRY_DATE:\n+\t\tres = format_config_expiry_date(buf, key_, value_, gently);\n+\t\tbreak;\n+\n+\tcase TYPE_COLOR:\n+\t\tres = format_config_color(buf, key_, value_, gently);\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tif (value_) {\n \t\t\tstrbuf_addstr(buf, value_);\n \t\t} else {\n \t\t\t/* Just show the key name; back out delimiter */\n@@ -419,6 +438,8 @@ static int format_config(const struct config_display_options *opts,\n \t\t\t\tstrbuf_setlen(buf, buf->len - 1);\n \t\t}\n \t}\n+\n+terminator:\n \tstrbuf_addch(buf, opts->term);\n \treturn res;\n }\n-- \ngitgitgadget\n"},{"id":"535993","messageId":"xmqq8qcwi29l.fsf@gitster.g","threadId":"64966","inReplyTo":"2bca4d231686e33ea9d4d85b10fcffd60a63ad46.1771026918.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 04/13] config: format int64s gently","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-14T00:42:46Z","receivedAt":"2026-02-14T00:42:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Derrick Stolee via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +static int format_config_int64(struct strbuf *buf,\n> +\t\t\t       const char *key_,\n> +\t\t\t       const char *value_,\n> +\t\t\t       const struct key_value_info *kvi,\n> +\t\t\t       int gently)\n> +{\n> +\tint64_t v = 0;\n> +\tif (gently) {\n> +\t\tif (git_parse_int64(value_, &v))\n> +\t\t\treturn -1;\n> +\t} else {\n> +\t\t/* may die() */\n> +\t\tv = git_config_int64(key_, value_ ? value_ : \"\", kvi);\n> +\t}\n> +\n> +\tstrbuf_addf(buf, \"%\"PRId64, v);\n> +\treturn 0;\n> +}\n\nThis establishes the pattern the next handful of patches follow.  We\nalready have in parse.c helpers that we can use for the gentler\nparsing, and otherwise we'd use git_config_*() that the caller of\nthese new helpers were using originally.\n\nI'd have preferred to have the blank line moved to the gap between\nthe decl and the first statement, i.e.,\n\n> +{\n> +\tint64_t v = 0;\n> +\n> +\tif (gently) {\n> +\t\tif (git_parse_int64(value_, &v))\n> +\t\t\treturn -1;\n> +\t} else {\n> +\t\t/* may die() */\n> +\t\tv = git_config_int64(key_, value_ ? value_ : \"\", kvi);\n> +\t}\n> +\tstrbuf_addf(buf, \"%\"PRId64, v);\n> +\treturn 0;\n> +}\n\nThese \"format X gently\" steps look very good.\n"},{"id":"536152","messageId":"aZQvLzL-AhPG3rbx@pks.im","threadId":"64966","inReplyTo":"6d2a48a3b7f61c068392e66933caaf1d78055857.1771026918.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 03/13] config: make 'git config list --type=<X>' work","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-17T09:04:47Z","receivedAt":"2026-02-17T09:05:00Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Feb 13, 2026 at 11:55:08PM +0000, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <stolee@gmail.com>\n> \n> Previously, the --type=<X> argument to 'git config list' was ignored and\n> did nothing. Now, we add the use of format_config() to the\n> show_all_config() function so each key-value pair is attempted to be\n> parsed. This is our first use of the 'gently' parameter with a nonzero\n> value.\n> \n> When listing multiple values, our initial settings for the output format\n> is different. Add a new init helper to specify the fact that keys should\n> be shown and also add the default delimiters as they were unset in some\n> cases.\n> \n> If there is an error in parsing, then the row is not output.\n\nIt might make sense to document the rationale behind this decision in\nthe commit message.\n\n> diff --git a/builtin/config.c b/builtin/config.c\n> index b4c4228311..4c4c791883 100644\n> --- a/builtin/config.c\n> +++ b/builtin/config.c\n> @@ -318,21 +318,12 @@ static int show_all_config(const char *key_, const char *value_,\n>  {\n>  \tconst struct config_display_options *opts = cb;\n>  \tconst struct key_value_info *kvi = ctx->kvi;\n> +\tstruct strbuf formatted = STRBUF_INIT;\n>  \n> -\tif (opts->show_origin || opts->show_scope) {\n> -\t\tstruct strbuf buf = STRBUF_INIT;\n> -\t\tif (opts->show_scope)\n> -\t\t\tshow_config_scope(opts, kvi, &buf);\n> -\t\tif (opts->show_origin)\n> -\t\t\tshow_config_origin(opts, kvi, &buf);\n> -\t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n> -\t\tfwrite(buf.buf, 1, buf.len, stdout);\n> -\t\tstrbuf_release(&buf);\n> -\t}\n> -\tif (!opts->omit_values && value_)\n> -\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n> -\telse\n> -\t\tprintf(\"%s%c\", key_, opts->term);\n> +\tif (format_config(opts, &formatted, key_, value_, kvi, 1) >= 0)\n> +\t\tfwrite(formatted.buf, 1, formatted.len, stdout);\n\nWe could probably use puts(3p) instead, but as we know the length of the\ndata ahead of time it might be more efficient to use fwrite(3p) indeed.\nUltimately I guess it doesn't matter much.\n\nPatrick\n"},{"id":"536153","messageId":"aZQvOrNOjqiJSRC5@pks.im","threadId":"64966","inReplyTo":"93c94a1b257d26a23942e902630c220e4a4f8eaf.1771026918.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 02/13] config: add 'gently' parameter to format_config()","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-17T09:04:58Z","receivedAt":"2026-02-17T09:05:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Feb 13, 2026 at 11:55:07PM +0000, Derrick Stolee via GitGitGadget wrote:\n> diff --git a/builtin/config.c b/builtin/config.c\n> index 237f7a934d..b4c4228311 100644\n> --- a/builtin/config.c\n> +++ b/builtin/config.c\n> @@ -242,10 +242,14 @@ struct strbuf_list {\n>   * append it into strbuf `buf`.  Returns a negative value on failure,\n>   * 0 on success, 1 on a missing optional value (i.e., telling the\n>   * caller to pretend that <key_,value_> did not exist).\n> + *\n> + * Note: 'gently' is currently ignored, but will be implemented in\n> + * a future change.\n>   */\n>  static int format_config(const struct config_display_options *opts,\n>  \t\t\t struct strbuf *buf, const char *key_,\n> -\t\t\t const char *value_, const struct key_value_info *kvi)\n> +\t\t\t const char *value_, const struct key_value_info *kvi,\n> +\t\t\t int gently UNUSED)\n\nI'd propose to either make this a bool, or turn it into an enum flag so\nthat it becomes easier to see at the callsite what the magic \"true\" or\n\"1\" means:\n\n    enum format_config_flags {\n        /*\n         * Do not die in case the value cannot be parsed properly, but\n         * return an error instead.\n         */\n        FORMAT_CONFIG_GENTLY = (1 << 0),\n    };\n\n    format_config(opts, buf, key, value, kv, FORMAT_CONFIG_GENTLY);\n\nI personally prefer this option over using a bool, even though it's a\nbit more verbose.\n\nPatrick\n"},{"id":"536154","messageId":"aZQvP21anXXZWcMh@pks.im","threadId":"64966","inReplyTo":"2bca4d231686e33ea9d4d85b10fcffd60a63ad46.1771026918.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 04/13] config: format int64s gently","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-17T09:05:03Z","receivedAt":"2026-02-17T09:05:09Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Feb 13, 2026 at 11:55:09PM +0000, Derrick Stolee via GitGitGadget wrote:\n> diff --git a/builtin/config.c b/builtin/config.c\n> index 4c4c791883..d259a91d53 100644\n> --- a/builtin/config.c\n> +++ b/builtin/config.c\n> @@ -237,6 +237,25 @@ struct strbuf_list {\n>  \tint alloc;\n>  };\n>  \n> +static int format_config_int64(struct strbuf *buf,\n> +\t\t\t       const char *key_,\n> +\t\t\t       const char *value_,\n\nWhy do we have the trailing underscores here?\n\n> @@ -249,8 +268,9 @@ struct strbuf_list {\n>  static int format_config(const struct config_display_options *opts,\n>  \t\t\t struct strbuf *buf, const char *key_,\n>  \t\t\t const char *value_, const struct key_value_info *kvi,\n> -\t\t\t int gently UNUSED)\n> +\t\t\t int gently)\n>  {\n> +\tint res = 0;\n>  \tif (opts->show_scope)\n>  \t\tshow_config_scope(opts, kvi, buf);\n>  \tif (opts->show_origin)\n> @@ -262,8 +282,7 @@ static int format_config(const struct config_display_options *opts,\n>  \t\t\tstrbuf_addch(buf, opts->key_delim);\n>  \n>  \t\tif (opts->type == TYPE_INT)\n> -\t\t\tstrbuf_addf(buf, \"%\"PRId64,\n> -\t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\", kvi));\n> +\t\t\tres = format_config_int64(buf, key_, value_, kvi, gently);\n>  \t\telse if (opts->type == TYPE_BOOL)\n>  \t\t\tstrbuf_addstr(buf, git_config_bool(key_, value_) ?\n>  \t\t\t\t      \"true\" : \"false\");\n> @@ -309,7 +328,7 @@ static int format_config(const struct config_display_options *opts,\n>  \t\t}\n>  \t}\n>  \tstrbuf_addch(buf, opts->term);\n> -\treturn 0;\n> +\treturn res;\n>  }\n\nOkay. We bubble up the return value now, but we know that the return\nvalue will only be different in case `gently != 0`. Otherwise, any error\nwould cause us to die.\n\nPatrick\n"},{"id":"536155","messageId":"aZQvRH4vPkPYHyg9@pks.im","threadId":"64966","inReplyTo":"0a428d2ffe092c2af6789b5e698dc769b0eb883a.1771026918.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 06/13] config: format bools or ints gently","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-17T09:05:08Z","receivedAt":"2026-02-17T09:05:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Feb 13, 2026 at 11:55:11PM +0000, Derrick Stolee via GitGitGadget wrote:\n> diff --git a/builtin/config.c b/builtin/config.c\n> index 2c169fc126..2c93e1725b 100644\n> --- a/builtin/config.c\n> +++ b/builtin/config.c\n> @@ -274,6 +274,34 @@ static int format_config_bool(struct strbuf *buf,\n>  \treturn 0;\n>  }\n>  \n> +static int format_config_bool_or_int(struct strbuf *buf,\n> +\t\t\t\t     const char *key_,\n> +\t\t\t\t     const char *value_,\n> +\t\t\t\t     const struct key_value_info *kvi,\n> +\t\t\t\t     int gently)\n> +{\n> +\tint v, is_bool = 0;\n> +\n> +\tif (gently) {\n> +\t\tv = git_parse_maybe_bool_text(value_);\n\nThis function also returns `1` in case `!value`. Is this intended? I\nguess so due to our implicit bool thingy, and `git_config_bool_or_int()`\nseems to behave the same.\n\nPatrick\n"},{"id":"536156","messageId":"aZQvSvBEebHFf9Bb@pks.im","threadId":"64966","inReplyTo":"d1cfa0c5e1295d7b6655a0962bd47f8e89bc4a04.1771026918.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 09/13] config: format paths gently","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-17T09:05:14Z","receivedAt":"2026-02-17T09:05:21Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Feb 13, 2026 at 11:55:14PM +0000, Derrick Stolee via GitGitGadget wrote:\n> diff --git a/builtin/config.c b/builtin/config.c\n> index 0c539ff98e..4664651dd2 100644\n> --- a/builtin/config.c\n> +++ b/builtin/config.c\n> @@ -314,6 +314,28 @@ static int format_config_bool_or_str(struct strbuf *buf,\n>  \treturn 0;\n>  }\n>  \n> +static int format_config_path(struct strbuf *buf,\n> +\t\t\t      const char *key_,\n> +\t\t\t      const char *value_,\n> +\t\t\t      int gently)\n> +{\n> +\tchar *v;\n> +\tif (gently) {\n> +\t\tif (git_parse_maybe_pathname(value_, &v) < 0)\n> +\t\t\treturn -1;\n> +\t} else if (git_config_pathname(&v, key_, value_) < 0) {\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tif (v)\n> +\t\tstrbuf_addstr(buf, v);\n> +\telse\n> +\t\treturn 1; /* :(optional)no-such-file */\n\nOkay, this is the first callsite where we return a vaule `> 0`, if I see\ncorrectly. But in `show_all_config()` we check for `res >= 0`, and if so\nwe would print the configuration regardless.\n\nBut `buf` will now be an empty string. So wouldn't this cause us to\nprint such an empty string, too? I'm not quite sure whether this\nbehaviour is intentional or not, or whether I'm missing something here.\n\nIn any case, I think this should be documented in the commit message.\n\nPatrick\n"},{"id":"536157","messageId":"aZQvT_fTlUIJd1DM@pks.im","threadId":"64966","inReplyTo":"ddf6131ac9283bfbf4ec9ec000606765d887e6ec.1771026918.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 11/13] color: add color_parse_gently()","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-17T09:05:19Z","receivedAt":"2026-02-17T09:05:26Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Feb 13, 2026 at 11:55:16PM +0000, Derrick Stolee via GitGitGadget wrote:\n> diff --git a/color.c b/color.c\n> index 07ac8c9d40..ec8872d2dd 100644\n> --- a/color.c\n> +++ b/color.c\n> @@ -264,7 +259,8 @@ static int color_empty(const struct color *c)\n>  \treturn c->type <= COLOR_NORMAL;\n>  }\n>  \n> -int color_parse_mem(const char *value, int value_len, char *dst)\n> +static int color_parse_mem_1(const char *value, int value_len,\n> +\t\t\t     char *dst, int gently)\n>  {\n>  \tconst char *ptr = value;\n>  \tint len = value_len;\n> @@ -365,10 +361,25 @@ int color_parse_mem(const char *value, int value_len, char *dst)\n>  \tOUT(0);\n>  \treturn 0;\n>  bad:\n> -\treturn error(_(\"invalid color value: %.*s\"), value_len, value);\n> +\treturn gently ? -1 : error(_(\"invalid color value: %.*s\"), value_len, value);\n>  #undef OUT\n>  }\n\nAs far as I can see this isn't really about whether or not the function\nshould be gentle. It's rather whether or not the function should print\nan error message when it sees an error.\n\nSo should we rename the parameter to `quiet`?\n\n>  \n> +int color_parse_mem(const char *value, int value_len, char *dst)\n> +{\n> +\treturn color_parse_mem_1(value, value_len, dst, 0);\n> +}\n> +\n> +int color_parse(const char *value, char *dst)\n> +{\n> +\treturn color_parse_mem(value, strlen(value), dst);\n> +}\n> +\n> +int color_parse_gently(const char *value, char *dst)\n> +{\n> +\treturn color_parse_mem_1(value, strlen(value), dst, 1);\n> +}\n\nAnd if so, this should probably be called `color_parse_quiet()`.\n\nPatrick\n"},{"id":"536158","messageId":"aZQvVwWoDtZGLbQ5@pks.im","threadId":"64966","inReplyTo":"48fc882785013b129fba9b8aada6c1f2e239a4cd.1771026918.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 13/13] config: restructure format_config()","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-17T09:05:27Z","receivedAt":"2026-02-17T09:05:33Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Feb 13, 2026 at 11:55:18PM +0000, Derrick Stolee via GitGitGadget wrote:\n> diff --git a/builtin/config.c b/builtin/config.c\n> index e8c02e5f21..1de3ce0eaa 100644\n> --- a/builtin/config.c\n> +++ b/builtin/config.c\n> @@ -393,25 +393,44 @@ static int format_config(const struct config_display_options *opts,\n>  \t\tshow_config_origin(opts, kvi, buf);\n>  \tif (opts->show_keys)\n>  \t\tstrbuf_addstr(buf, key_);\n> -\tif (!opts->omit_values) {\n> -\t\tif (opts->show_keys)\n> -\t\t\tstrbuf_addch(buf, opts->key_delim);\n> -\n> -\t\tif (opts->type == TYPE_INT)\n> -\t\t\tres = format_config_int64(buf, key_, value_, kvi, gently);\n> -\t\telse if (opts->type == TYPE_BOOL)\n> -\t\t\tres = format_config_bool(buf, key_, value_, gently);\n> -\t\telse if (opts->type == TYPE_BOOL_OR_INT)\n> -\t\t\tres = format_config_bool_or_int(buf, key_, value_, kvi, gently);\n> -\t\telse if (opts->type == TYPE_BOOL_OR_STR)\n> -\t\t\tres = format_config_bool_or_str(buf, value_);\n> -\t\telse if (opts->type == TYPE_PATH)\n> -\t\t\tres = format_config_path(buf, key_, value_, gently);\n> -\t\telse if (opts->type == TYPE_EXPIRY_DATE)\n> -\t\t\tres = format_config_expiry_date(buf, key_, value_, gently);\n> -\t\telse if (opts->type == TYPE_COLOR)\n> -\t\t\tres = format_config_color(buf, key_, value_, gently);\n> -\t\telse if (value_) {\n> +\n> +\tif (opts->omit_values)\n> +\t\tgoto terminator;\n> +\n> +\tif (opts->show_keys)\n> +\t\tstrbuf_addch(buf, opts->key_delim);\n> +\n> +\tswitch (opts->type) {\n\nI very much prefer this layout. Switches are more verbose, but if you\nask me they are easier to parse.\n\n> +\tcase TYPE_INT:\n> +\t\tres = format_config_int64(buf, key_, value_, kvi, gently);\n> +\t\tbreak;\n> +\n\nThat being said, I'm not a huge fan of the empty newlines here. But feel\nfree to ignore.\n\n> +\tcase TYPE_BOOL:\n> +\t\tres = format_config_bool(buf, key_, value_, gently);\n> +\t\tbreak;\n> +\n> +\tcase TYPE_BOOL_OR_INT:\n> +\t\tres = format_config_bool_or_int(buf, key_, value_, kvi, gently);\n> +\t\tbreak;\n> +\n> +\tcase TYPE_BOOL_OR_STR:\n> +\t\tres = format_config_bool_or_str(buf, value_);\n> +\t\tbreak;\n> +\n> +\tcase TYPE_PATH:\n> +\t\tres = format_config_path(buf, key_, value_, gently);\n> +\t\tbreak;\n> +\n> +\tcase TYPE_EXPIRY_DATE:\n> +\t\tres = format_config_expiry_date(buf, key_, value_, gently);\n> +\t\tbreak;\n> +\n> +\tcase TYPE_COLOR:\n> +\t\tres = format_config_color(buf, key_, value_, gently);\n> +\t\tbreak;\n> +\n> +\tdefault:\n\nShould we maybe handle all valid types explicitly and have the `default`\ncase `BUG()` instead?\n\nPatrick\n"},{"id":"536197","messageId":"xmqqa4x7e4eb.fsf@gitster.g","threadId":"64966","inReplyTo":"aZQvLzL-AhPG3rbx@pks.im","subject":"Re: [PATCH v2 03/13] config: make 'git config list --type=<X>' work","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-17T16:11:40Z","receivedAt":"2026-02-17T16:11:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> -\tif (!opts->omit_values && value_)\n>> -\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n>> -\telse\n>> -\t\tprintf(\"%s%c\", key_, opts->term);\n>> +\tif (format_config(opts, &formatted, key_, value_, kvi, 1) >= 0)\n>> +\t\tfwrite(formatted.buf, 1, formatted.len, stdout);\n>\n> We could probably use puts(3p) instead, but as we know the length of the\n> data ahead of time it might be more efficient to use fwrite(3p) indeed.\n> Ultimately I guess it doesn't matter much.\n>\n> Patrick\n\nIf we are not always doing LF-delimited output, puts(3) would not\nhelp us very much, I suspect.\n"},{"id":"536198","messageId":"aZSTsVApNL3A7y8D@pks.im","threadId":"64966","inReplyTo":"xmqqa4x7e4eb.fsf@gitster.g","subject":"Re: [PATCH v2 03/13] config: make 'git config list --type=<X>' work","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-17T16:13:37Z","receivedAt":"2026-02-17T16:13:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Feb 17, 2026 at 08:11:40AM -0800, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> >> -\tif (!opts->omit_values && value_)\n> >> -\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n> >> -\telse\n> >> -\t\tprintf(\"%s%c\", key_, opts->term);\n> >> +\tif (format_config(opts, &formatted, key_, value_, kvi, 1) >= 0)\n> >> +\t\tfwrite(formatted.buf, 1, formatted.len, stdout);\n> >\n> > We could probably use puts(3p) instead, but as we know the length of the\n> > data ahead of time it might be more efficient to use fwrite(3p) indeed.\n> > Ultimately I guess it doesn't matter much.\n> >\n> > Patrick\n> \n> If we are not always doing LF-delimited output, puts(3) would not\n> help us very much, I suspect.\n\nD'oh, right.\n\nPatrick\n"},{"id":"536199","messageId":"xmqq5x7ve3zw.fsf@gitster.g","threadId":"64966","inReplyTo":"aZQvT_fTlUIJd1DM@pks.im","subject":"Re: [PATCH v2 11/13] color: add color_parse_gently()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-17T16:20:19Z","receivedAt":"2026-02-17T16:20:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Fri, Feb 13, 2026 at 11:55:16PM +0000, Derrick Stolee via GitGitGadget wrote:\n>> diff --git a/color.c b/color.c\n>> index 07ac8c9d40..ec8872d2dd 100644\n>> --- a/color.c\n>> +++ b/color.c\n>> @@ -264,7 +259,8 @@ static int color_empty(const struct color *c)\n>>  \treturn c->type <= COLOR_NORMAL;\n>>  }\n>>  \n>> -int color_parse_mem(const char *value, int value_len, char *dst)\n>> +static int color_parse_mem_1(const char *value, int value_len,\n>> +\t\t\t     char *dst, int gently)\n>>  {\n>>  \tconst char *ptr = value;\n>>  \tint len = value_len;\n>> @@ -365,10 +361,25 @@ int color_parse_mem(const char *value, int value_len, char *dst)\n>>  \tOUT(0);\n>>  \treturn 0;\n>>  bad:\n>> -\treturn error(_(\"invalid color value: %.*s\"), value_len, value);\n>> +\treturn gently ? -1 : error(_(\"invalid color value: %.*s\"), value_len, value);\n>>  #undef OUT\n>>  }\n>\n> As far as I can see this isn't really about whether or not the function\n> should be gentle. It's rather whether or not the function should print\n> an error message when it sees an error.\n\nDo you mean that this error() call is not die(), the flag does not\nfit the usual \"gently\" criteria?  In other words, should we make\nthis call die() if we call it \"gently\"?\n\n>\n> So should we rename the parameter to `quiet`?\n>\n>>  \n>> +int color_parse_mem(const char *value, int value_len, char *dst)\n>> +{\n>> +\treturn color_parse_mem_1(value, value_len, dst, 0);\n>> +}\n>> +\n>> +int color_parse(const char *value, char *dst)\n>> +{\n>> +\treturn color_parse_mem(value, strlen(value), dst);\n>> +}\n>> +\n>> +int color_parse_gently(const char *value, char *dst)\n>> +{\n>> +\treturn color_parse_mem_1(value, strlen(value), dst, 1);\n>> +}\n>\n> And if so, this should probably be called `color_parse_quiet()`.\n>\n> Patrick\n"},{"id":"536683","messageId":"8fae2d15-cb0d-4ccb-8cd3-c4775b6666dd@gmail.com","threadId":"64966","inReplyTo":"xmqq5x7ve3zw.fsf@gitster.g","subject":"Re: [PATCH v2 11/13] color: add color_parse_gently()","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2026-02-23T02:12:04Z","receivedAt":"2026-02-23T02:12:07Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 2/17/26 11:20 AM, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n>> On Fri, Feb 13, 2026 at 11:55:16PM +0000, Derrick Stolee via GitGitGadget wrote:\n>>> diff --git a/color.c b/color.c\n>>> index 07ac8c9d40..ec8872d2dd 100644\n>>> --- a/color.c\n>>> +++ b/color.c\n>>> @@ -264,7 +259,8 @@ static int color_empty(const struct color *c)\n>>>   \treturn c->type <= COLOR_NORMAL;\n>>>   }\n>>>   \n>>> -int color_parse_mem(const char *value, int value_len, char *dst)\n>>> +static int color_parse_mem_1(const char *value, int value_len,\n>>> +\t\t\t     char *dst, int gently)\n>>>   {\n>>>   \tconst char *ptr = value;\n>>>   \tint len = value_len;\n>>> @@ -365,10 +361,25 @@ int color_parse_mem(const char *value, int value_len, char *dst)\n>>>   \tOUT(0);\n>>>   \treturn 0;\n>>>   bad:\n>>> -\treturn error(_(\"invalid color value: %.*s\"), value_len, value);\n>>> +\treturn gently ? -1 : error(_(\"invalid color value: %.*s\"), value_len, value);\n>>>   #undef OUT\n>>>   }\n>>\n>> As far as I can see this isn't really about whether or not the function\n>> should be gentle. It's rather whether or not the function should print\n>> an error message when it sees an error.\n> \n> Do you mean that this error() call is not die(), the flag does not\n> fit the usual \"gently\" criteria?  In other words, should we make\n> this call die() if we call it \"gently\"?\n\nThis is an interesting case where the existing color parsing logic is\nnot following the typical pattern that uses die() on a failed parse.\n\nIf we want to change the behavior to die() later, then that could be\nconsidered, though I don't want to consider the ramifications right now.\n\nI think the easiest \"local\" fix is to use the 'quiet' way, though it adds\nsome asymmetry in the config code in how it uses the 'gently' parameter.\nLet me give this a try in the next version so we can see how it feels.\n\nThanks,\n-Stolee\n\n"},{"id":"536685","messageId":"a72fd162-d904-496b-90e7-34c78299c356@gmail.com","threadId":"64966","inReplyTo":"aZQvRH4vPkPYHyg9@pks.im","subject":"Re: [PATCH v2 06/13] config: format bools or ints gently","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2026-02-23T03:25:49Z","receivedAt":"2026-02-23T03:25:51Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 2/17/26 4:05 AM, Patrick Steinhardt wrote:\n> On Fri, Feb 13, 2026 at 11:55:11PM +0000, Derrick Stolee via GitGitGadget wrote:\n>> diff --git a/builtin/config.c b/builtin/config.c\n>> index 2c169fc126..2c93e1725b 100644\n>> --- a/builtin/config.c\n>> +++ b/builtin/config.c\n>> @@ -274,6 +274,34 @@ static int format_config_bool(struct strbuf *buf,\n>>   \treturn 0;\n>>   }\n>>   \n>> +static int format_config_bool_or_int(struct strbuf *buf,\n>> +\t\t\t\t     const char *key_,\n>> +\t\t\t\t     const char *value_,\n>> +\t\t\t\t     const struct key_value_info *kvi,\n>> +\t\t\t\t     int gently)\n>> +{\n>> +\tint v, is_bool = 0;\n>> +\n>> +\tif (gently) {\n>> +\t\tv = git_parse_maybe_bool_text(value_);\n> \n> This function also returns `1` in case `!value`. Is this intended? I\n> guess so due to our implicit bool thingy, and `git_config_bool_or_int()`\n> seems to behave the same.\n\nDo you mean in the case of a NULL value?\n\nBased on the rules for iterating through config values, deep down in\nget_value() the value parameter sent to the function is never NULL.\nIt may be an empty string, but never NULL.\n\nThanks,\n-Stolee\n\n"},{"id":"536687","messageId":"94ee4b78-48f9-4e2c-aee5-ced1dc9a12fd@gmail.com","threadId":"64966","inReplyTo":"aZQvP21anXXZWcMh@pks.im","subject":"Re: [PATCH v2 04/13] config: format int64s gently","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2026-02-23T03:41:08Z","receivedAt":"2026-02-23T03:41:10Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 2/17/26 4:05 AM, Patrick Steinhardt wrote:\n> On Fri, Feb 13, 2026 at 11:55:09PM +0000, Derrick Stolee via GitGitGadget wrote:\n>> diff --git a/builtin/config.c b/builtin/config.c\n>> index 4c4c791883..d259a91d53 100644\n>> --- a/builtin/config.c\n>> +++ b/builtin/config.c\n>> @@ -237,6 +237,25 @@ struct strbuf_list {\n>>   \tint alloc;\n>>   };\n>>   \n>> +static int format_config_int64(struct strbuf *buf,\n>> +\t\t\t       const char *key_,\n>> +\t\t\t       const char *value_,\n> \n> Why do we have the trailing underscores here?\n\nThis is all to match the existing names from format_config(). This may help to\nrecognize moved lines by keeping the variable names the same. Definitely not\nmy preference to use this name format, but I thought it fitting to avoid a\nrename of all variables.\n\nThanks,\n-Stolee\n\n"},{"id":"536688","messageId":"xmqqldgkoxve.fsf@gitster.g","threadId":"64966","inReplyTo":"8fae2d15-cb0d-4ccb-8cd3-c4775b6666dd@gmail.com","subject":"Re: [PATCH v2 11/13] color: add color_parse_gently()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-23T05:03:01Z","receivedAt":"2026-02-23T05:03:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Derrick Stolee <stolee@gmail.com> writes:\n\n>> Do you mean that this error() call is not die(), the flag does not\n>> fit the usual \"gently\" criteria?  In other words, should we make\n>> this call die() if we call it \"gently\"?\n>\n> This is an interesting case where the existing color parsing logic is\n> not following the typical pattern that uses die() on a failed parse.\n\nI see.  I personally would view that an existing bug worth fixing,\nbut I ...\n\n> If we want to change the behavior to die() later, then that could be\n> considered, though I don't want to consider the ramifications right now.\n\n... agree with you that it should be fixed outside the scope of this\ntopic.\n\n> I think the easiest \"local\" fix is to use the 'quiet' way, though it adds\n> some asymmetry in the config code in how it uses the 'gently' parameter.\n\nOr, just add comments to the function that takes gently but does not\ndie() to warn those who would add new callers.  They can pass\ngently=1 if they want to handle the errors themselves and keep it\nthat way.  If they want the function to die, well they have to wait\nuntil the function is fixed to behave like everybody else.\n"},{"id":"536770","messageId":"pull.2044.v3.git.1771849615.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v2.git.1771026918.gitgitgadget@gmail.com","subject":"[PATCH v3 00/13] Make 'git config list --type=' parse and filter types","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-23T12:26:42Z","receivedAt":"2026-02-23T12:26:58Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"I started down this road based on feedback on my 'git config-batch' RFC [1].\n\n[1]\nhttps://lore.kernel.org/git/pull.2033.git.1770214803.gitgitgadget@gmail.com/\n\nI had described my intention to use 'git config-batch' as a single process\nto load multiple config values one-by-one. Brian mentioned that 'git config\nlist -z' would probably suffice, so I started experimenting in that\ndirection [2].\n\n[2]\nhttps://github.com/git-ecosystem/git-credential-manager/compare/main...derrickstolee:config-list\n\nHowever, I ran into a problem: the most critical performance bottleneck is\nrelated to path-formatted config values that are queried with 'git config\nget --type=path -z'. It wasn't hard to update things to lazily load the full\nlist of config values by type [3], but I then noticed a big problem!\n\n[3]\nhttps://github.com/git-ecosystem/git-credential-manager/commit/d403c8e24ce6f37da920cce23842dd5a6cf6481d\n\nProblem: 'git config list' doesn't respect --type=<X>!\n\nThis boils down to the fact that the iterator function show_all_config()\ndoesn't call format_config(), which includes the type-parsing code.\n\nThis wasn't super trivial to update:\n\n 1. format_config() uses git_config_parse_*() methods, which die() on a bad\n    parse.\n 2. The path parsing code didn't have a gentle version.\n 3. The two paths ('git config list' and 'git config --list') needed to\n    standardize their display options to work with format_config().\n 4. Finally, we need to filter out key-value pairs that don't match the\n    given type.\n\n\nUpdates in v2\n=============\n\nBased on the positive feedback in round one, this is no longer an RFC.\n\n * format_config() now uses a 'gently' parameter instead of 'die_on_parse'\n   (flipped).\n * format_config() is more carefully updated with helper methods and a\n   global refactor.\n * New gentle parsing code is introduced right before the format_config()\n   helper is created to use it.\n * I squashed the change that updates the display_opts initial state into\n   the patch that uses format_config() for the 'list' command. The initial\n   state change on its own leads to test failures, so I am making a slightly\n   bigger patch to keep things passing tests at every change.\n * More tests for 'git config list --type=<X>' are added.\n * I rearranged things so the 'git config list --type' integration follows\n   the format_config() update immediately. The tests at that time show what\n   such a trivial implementation would do, including failing on bool parsing\n   and having several error messages for color and expiry-date parsing. The\n   tests modify as these issues are fixed with gentle parsers.\n * I have a prototype implementation of GCM using this option in [4] and it\n   gets the performance improvements I was hoping for. It requires polish\n   and a compatibility check that uses the Git version to guarantee that\n   this --type behavior change is recognized.\n\n[4] https://github.com/git-ecosystem/git-credential-manager/pull/2268\n\n\nUpdates in V3\n=============\n\n * Expanded the commit message of patch 3 to include reasoning for the\n   filter.\n * Expanded the commit message of patch 3 to specify that tests are added\n   demonstrating problematic behavior before they are fixed in later\n   patches.\n * New test cases are added for ':(optional)' macros and int parsing.\n * The uses of git_parse_int64() and git_parse_int() were incorrect. These\n   are fixed after being revealed by new tests.\n * The git_parse_maybe_pathname() was removed after realizing it did not\n   contribute to a behavior change. Path parsing was already gentle, it just\n   needed assistance with ':(optional)' macros for nonexistent paths.\n * A distinction is made between parsing 'gently' (do not die()) and\n   'quietly' (do not error()). Neither cause the command to fail or emit to\n   stderr, but the distinction is made by what the original parsing behavior\n   did.\n * The conversion of the format_config() logic into a switch() is made more\n   complete by adding a BUG() statement for the default case.\n * The options for the config type are now converted into an enum to make\n   such switch() statements easier to validate as being complete.\n\nThanks for any and all feedback, -Stolee\n\nDerrick Stolee (13):\n  config: move show_all_config()\n  config: add 'gently' parameter to format_config()\n  config: make 'git config list --type=<X>' work\n  config: format int64s gently\n  config: format bools gently\n  config: format bools or ints gently\n  config: format bools or strings in helper\n  config: format paths gently\n  config: format expiry dates quietly\n  color: add color_parse_quietly()\n  config: format colors quietly\n  config: restructure format_config()\n  config: use an enum for type\n\n Documentation/git-config.adoc |   3 +\n builtin/config.c              | 306 +++++++++++++++++++++++++---------\n color.c                       |  25 ++-\n color.h                       |   1 +\n t/t1300-config.sh             |  84 +++++++++-\n 5 files changed, 331 insertions(+), 88 deletions(-)\n\n\nbase-commit: 67ad42147a7acc2af6074753ebd03d904476118f\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2044%2Fderrickstolee%2Fconfig-list-type-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2044/derrickstolee/config-list-type-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/2044\n\nRange-diff vs v2:\n\n  1:  bca83d8ca8 =  1:  bca83d8ca8 config: move show_all_config()\n  2:  93c94a1b25 =  2:  93c94a1b25 config: add 'gently' parameter to format_config()\n  3:  6d2a48a3b7 !  3:  2b2a325b55 config: make 'git config list --type=<X>' work\n     @@ Commit message\n          be shown and also add the default delimiters as they were unset in some\n          cases.\n      \n     -    If there is an error in parsing, then the row is not output.\n     +    Our intention is that if there is an error in parsing, then the row is\n     +    not output. This is necessary to avoid the caller needing to build their\n     +    own validator to understand the difference between valid, canonicalized\n     +    types and other raw string values. The raw values will always be\n     +    available to the user if they do not specify the --type=<X> option.\n     +\n     +    The current behavior is more complicated, including error messages on\n     +    bad parsing or potentially complete failure of the command.  We add\n     +    tests at this point that demonstrate the current behavior so we can\n     +    witness the fix in future changes that parse these values quietly and\n     +    gently.\n      \n          This is a change in behavior! We are starting to respect an option that\n          was previously ignored, leading to potential user confusion. This is\n     @@ t/t1300-config.sh: done\n      +red = red\n      +blue = Blue\n      +date = Fri Jun 4 15:46:55 2010\n     ++missing=:(optional)no-such-path\n     ++exists=:(optional)expect\n       EOF\n       \n       test_expect_success 'identical modern --type specifiers are allowed' '\n     @@ t/t1300-config.sh: test_expect_success 'unset type specifiers may be reset to co\n       \ttest_cmp_config 1048576 --type=bool --no-type --type=int section.big\n       '\n       \n     ++test_expect_success 'list --type=int shows only canonicalizable int values' '\n     ++\tcat >expect <<-EOF &&\n     ++\tsection.number=10\n     ++\tsection.big=1048576\n     ++\tEOF\n     ++\n     ++\ttest_must_fail git config ${mode_prefix}list --type=int\n     ++'\n     ++\n      +test_expect_success 'list --type=bool shows only canonicalizable bool values' '\n      +\tcat >expect <<-EOF &&\n      +\tsection.foo=true\n     @@ t/t1300-config.sh: test_expect_success 'unset type specifiers may be reset to co\n      +\ttest_must_fail git config ${mode_prefix}list --type=bool\n      +'\n      +\n     ++test_expect_success 'list --type=bool-or-int shows only canonicalizable values' '\n     ++\tcat >expect <<-EOF &&\n     ++\tsection.foo=true\n     ++\tsection.number=10\n     ++\tsection.big=1048576\n     ++\tEOF\n     ++\n     ++\ttest_must_fail git config ${mode_prefix}list --type=bool-or-int\n     ++'\n     ++\n      +test_expect_success 'list --type=path shows only canonicalizable path values' '\n     ++\t# TODO: handling of missing path is incorrect here.\n      +\tcat >expect <<-EOF &&\n      +\tsection.foo=True\n      +\tsection.number=10\n     @@ t/t1300-config.sh: test_expect_success 'unset type specifiers may be reset to co\n      +\tsection.red=red\n      +\tsection.blue=Blue\n      +\tsection.date=Fri Jun 4 15:46:55 2010\n     ++\tsection.missing=section.exists=expect\n      +\tEOF\n      +\n      +\tgit config ${mode_prefix}list --type=path >actual 2>err &&\n     @@ t/t1300-config.sh: test_expect_success 'unset type specifiers may be reset to co\n      +\terror: '\\''~/dir'\\'' for '\\''section.path'\\'' is not a valid timestamp\n      +\terror: '\\''red'\\'' for '\\''section.red'\\'' is not a valid timestamp\n      +\terror: '\\''Blue'\\'' for '\\''section.blue'\\'' is not a valid timestamp\n     ++\terror: '\\'':(optional)no-such-path'\\'' for '\\''section.missing'\\'' is not a valid timestamp\n     ++\terror: '\\'':(optional)expect'\\'' for '\\''section.exists'\\'' is not a valid timestamp\n      +\tEOF\n      +\n      +\tgit config ${mode_prefix}list --type=expiry-date >actual 2>err &&\n     @@ t/t1300-config.sh: test_expect_success 'unset type specifiers may be reset to co\n      +\terror: invalid color value: 1M\n      +\terror: invalid color value: ~/dir\n      +\terror: invalid color value: Fri Jun 4 15:46:55 2010\n     ++\terror: invalid color value: :(optional)no-such-path\n     ++\terror: invalid color value: :(optional)expect\n      +\tEOF\n      +\n      +\tgit config ${mode_prefix}list --type=color >actual.raw 2>err &&\n  4:  2bca4d2316 !  4:  4835ee5180 config: format int64s gently\n     @@ builtin/config.c: struct strbuf_list {\n      +{\n      +\tint64_t v = 0;\n      +\tif (gently) {\n     -+\t\tif (git_parse_int64(value_, &v))\n     ++\t\tif (!git_parse_int64(value_, &v))\n      +\t\t\treturn -1;\n      +\t} else {\n      +\t\t/* may die() */\n     @@ builtin/config.c: static int format_config(const struct config_display_options *\n       }\n       \n       static int show_all_config(const char *key_, const char *value_,\n     +\n     + ## t/t1300-config.sh ##\n     +@@ t/t1300-config.sh: test_expect_success 'list --type=int shows only canonicalizable int values' '\n     + \tsection.big=1048576\n     + \tEOF\n     + \n     +-\ttest_must_fail git config ${mode_prefix}list --type=int\n     ++\tgit config ${mode_prefix}list --type=int >actual 2>err &&\n     ++\ttest_cmp expect actual &&\n     ++\ttest_must_be_empty err\n     + '\n     + \n     + test_expect_success 'list --type=bool shows only canonicalizable bool values' '\n  5:  f8e0b8304f !  5:  24eb757a40 config: format bools gently\n     @@ t/t1300-config.sh: test_expect_success 'list --type=bool shows only canonicaliza\n      +\ttest_must_be_empty err\n       '\n       \n     - test_expect_success 'list --type=path shows only canonicalizable path values' '\n     + test_expect_success 'list --type=bool-or-int shows only canonicalizable values' '\n  6:  0a428d2ffe !  6:  02849ca621 config: format bools or ints gently\n     @@ builtin/config.c: static int format_config_bool(struct strbuf *buf,\n      +\n      +\t\tif (v >= 0)\n      +\t\t\tis_bool = 1;\n     -+\t\telse if (git_parse_int(value_, &v))\n     ++\t\telse if (!git_parse_int(value_, &v))\n      +\t\t\treturn -1;\n      +\t} else {\n      +\t\tv = git_config_bool_or_int(key_, value_, kvi,\n     @@ builtin/config.c: static int format_config(const struct config_display_options *\n       \t\t\tint v = git_parse_maybe_bool(value_);\n       \t\t\tif (v < 0)\n       \t\t\t\tstrbuf_addstr(buf, value_);\n     +\n     + ## t/t1300-config.sh ##\n     +@@ t/t1300-config.sh: test_expect_success 'list --type=bool-or-int shows only canonicalizable values'\n     + \tsection.big=1048576\n     + \tEOF\n     + \n     +-\ttest_must_fail git config ${mode_prefix}list --type=bool-or-int\n     ++\tgit config ${mode_prefix}list --type=bool-or-int >actual 2>err &&\n     ++\ttest_cmp expect actual &&\n     ++\ttest_must_be_empty err\n     + '\n     + \n     + test_expect_success 'list --type=path shows only canonicalizable path values' '\n  7:  3fec3abbd6 =  7:  9f06db29b9 config: format bools or strings in helper\n  8:  fafafc5465 <  -:  ---------- parse: add git_parse_maybe_pathname()\n  9:  d1cfa0c5e1 !  8:  d198c238e9 config: format paths gently\n     @@ Commit message\n          Move the logic for formatting path config values into a helper method\n          and use gentle parsing when needed.\n      \n     +    We need to be careful about how to handle the ':(optional)' macro, which\n     +    as tested in t1311-config-optional.sh must allow for ignoring a missing\n     +    path when other multiple values exist, but cause 'git config get' to\n     +    fail if it is the only possible value and thus no result is output.\n     +\n     +    In the case of our list, we need to omit those values silently. This\n     +    necessitates the use of the 'gently' parameter here.\n     +\n          Signed-off-by: Derrick Stolee <stolee@gmail.com>\n      \n       ## builtin/config.c ##\n     @@ builtin/config.c: static int format_config_bool_or_str(struct strbuf *buf,\n      +\t\t\t      int gently)\n      +{\n      +\tchar *v;\n     -+\tif (gently) {\n     -+\t\tif (git_parse_maybe_pathname(value_, &v) < 0)\n     -+\t\t\treturn -1;\n     -+\t} else if (git_config_pathname(&v, key_, value_) < 0) {\n     ++\n     ++\tif (git_config_pathname(&v, key_, value_) < 0)\n      +\t\treturn -1;\n     -+\t}\n      +\n      +\tif (v)\n      +\t\tstrbuf_addstr(buf, v);\n      +\telse\n     -+\t\treturn 1; /* :(optional)no-such-file */\n     ++\t\treturn gently ? -1 : 1; /* :(optional)no-such-file */\n      +\n      +\tfree(v);\n      +\treturn 0;\n     @@ builtin/config.c: static int format_config(const struct config_display_options *\n       \t\t\ttimestamp_t t;\n       \t\t\tif (git_config_expiry_date(&t, key_, value_) < 0)\n       \t\t\t\treturn -1;\n     +\n     + ## t/t1300-config.sh ##\n     +@@ t/t1300-config.sh: test_expect_success 'list --type=bool-or-int shows only canonicalizable values'\n     + '\n     + \n     + test_expect_success 'list --type=path shows only canonicalizable path values' '\n     +-\t# TODO: handling of missing path is incorrect here.\n     + \tcat >expect <<-EOF &&\n     + \tsection.foo=True\n     + \tsection.number=10\n     +@@ t/t1300-config.sh: test_expect_success 'list --type=path shows only canonicalizable path values' '\n     + \tsection.red=red\n     + \tsection.blue=Blue\n     + \tsection.date=Fri Jun 4 15:46:55 2010\n     +-\tsection.missing=section.exists=expect\n     ++\tsection.exists=expect\n     + \tEOF\n     + \n     + \tgit config ${mode_prefix}list --type=path >actual 2>err &&\n 10:  9221ca2352 !  9:  7568a0bc34 config: format expiry dates gently\n     @@ Metadata\n      Author: Derrick Stolee <stolee@gmail.com>\n      \n       ## Commit message ##\n     -    config: format expiry dates gently\n     +    config: format expiry dates quietly\n      \n          Move the logic for formatting expiry date config values into a helper\n     -    method and use gentle parsing when needed.\n     +    method and use quiet parsing when needed.\n     +\n     +    Note that git_config_expiry_date() will show an error on a bad parse and\n     +    not die() like most other git_config...() parsers. Thus, we use\n     +    'quietly' here instead of 'gently'.\n      \n          There is an unfortunate asymmetry in these two parsing methods, but we\n          need to treat a positive response from parse_expiry_date() as an error\n     @@ builtin/config.c: static int format_config_path(struct strbuf *buf,\n      +static int format_config_expiry_date(struct strbuf *buf,\n      +\t\t\t\t     const char *key_,\n      +\t\t\t\t     const char *value_,\n     -+\t\t\t\t     int gently)\n     ++\t\t\t\t     int quietly)\n      +{\n      +\ttimestamp_t t;\n     -+\tif (gently) {\n     ++\tif (quietly) {\n      +\t\tif (parse_expiry_date(value_, &t))\n      +\t\t\treturn -1;\n      +\t} else if (git_config_expiry_date(&t, key_, value_) < 0) {\n     @@ t/t1300-config.sh: test_expect_success 'list --type=path shows only canonicaliza\n      -\terror: '\\''~/dir'\\'' for '\\''section.path'\\'' is not a valid timestamp\n      -\terror: '\\''red'\\'' for '\\''section.red'\\'' is not a valid timestamp\n      -\terror: '\\''Blue'\\'' for '\\''section.blue'\\'' is not a valid timestamp\n     +-\terror: '\\'':(optional)no-such-path'\\'' for '\\''section.missing'\\'' is not a valid timestamp\n     +-\terror: '\\'':(optional)expect'\\'' for '\\''section.exists'\\'' is not a valid timestamp\n      -\tEOF\n      -\n       \tgit config ${mode_prefix}list --type=expiry-date >actual 2>err &&\n 11:  ddf6131ac9 ! 10:  d98966f53a color: add color_parse_gently()\n     @@ Metadata\n      Author: Derrick Stolee <stolee@gmail.com>\n      \n       ## Commit message ##\n     -    color: add color_parse_gently()\n     +    color: add color_parse_quietly()\n      \n          When parsing colors, a failed parse leads to an error message due to the\n          result returning error(). To allow for quiet parsing, create\n     -    color_parse_gently().\n     +    color_parse_quietly(). This is in contrast to an ..._gently() version\n     +    because the original does not die(), so both options are technically\n     +    'gentle'.\n      \n          To accomplish this, convert the implementation of color_parse_mem() into\n     -    a static color_parse_mem_1() helper that adds a 'gently' parameter. The\n     -    color_parse_gently() method can then use this. Since it is a near\n     +    a static color_parse_mem_1() helper that adds a 'quiet' parameter. The\n     +    color_parse_quietly() method can then use this. Since it is a near\n          equivalent to color_parse(), move that method down in the file so they\n          can be nearby while also appearing after color_parse_mem_1().\n      \n     @@ color.c: static int color_empty(const struct color *c)\n       \n      -int color_parse_mem(const char *value, int value_len, char *dst)\n      +static int color_parse_mem_1(const char *value, int value_len,\n     -+\t\t\t     char *dst, int gently)\n     ++\t\t\t     char *dst, int quiet)\n       {\n       \tconst char *ptr = value;\n       \tint len = value_len;\n     @@ color.c: int color_parse_mem(const char *value, int value_len, char *dst)\n       \treturn 0;\n       bad:\n      -\treturn error(_(\"invalid color value: %.*s\"), value_len, value);\n     -+\treturn gently ? -1 : error(_(\"invalid color value: %.*s\"), value_len, value);\n     ++\treturn quiet ? -1 : error(_(\"invalid color value: %.*s\"), value_len, value);\n       #undef OUT\n       }\n       \n     @@ color.c: int color_parse_mem(const char *value, int value_len, char *dst)\n      +\treturn color_parse_mem(value, strlen(value), dst);\n      +}\n      +\n     -+int color_parse_gently(const char *value, char *dst)\n     ++int color_parse_quietly(const char *value, char *dst)\n      +{\n      +\treturn color_parse_mem_1(value, strlen(value), dst, 1);\n      +}\n     @@ color.h: bool want_color_fd(int fd, enum git_colorbool var);\n        * terminal.\n        */\n       int color_parse(const char *value, char *dst);\n     -+int color_parse_gently(const char *value, char *dst);\n     ++int color_parse_quietly(const char *value, char *dst);\n       int color_parse_mem(const char *value, int len, char *dst);\n       \n       /*\n 12:  d14937e6d1 ! 11:  078065cff0 config: format colors gently\n     @@ Metadata\n      Author: Derrick Stolee <stolee@gmail.com>\n      \n       ## Commit message ##\n     -    config: format colors gently\n     +    config: format colors quietly\n      \n          Move the logic for formatting color config value into a helper method\n     -    and use gentle parsing when needed.\n     +    and use quiet parsing when needed.\n      \n          This removes error messages when parsing a list of config values that do\n          not match color formats.\n     @@ builtin/config.c: static int format_config_expiry_date(struct strbuf *buf,\n      +\tchar v[COLOR_MAXLEN];\n      +\n      +\tif (gently) {\n     -+\t\tif (color_parse_gently(value_, v) < 0)\n     ++\t\tif (color_parse_quietly(value_, v) < 0)\n      +\t\t\treturn -1;\n      +\t} else if (git_config_color(v, key_, value_) < 0) {\n      +\t\treturn -1;\n     @@ t/t1300-config.sh: test_expect_success 'list --type=color shows only canonicaliz\n      -\terror: invalid color value: 1M\n      -\terror: invalid color value: ~/dir\n      -\terror: invalid color value: Fri Jun 4 15:46:55 2010\n     +-\terror: invalid color value: :(optional)no-such-path\n     +-\terror: invalid color value: :(optional)expect\n      -\tEOF\n      -\n       \tgit config ${mode_prefix}list --type=color >actual.raw 2>err &&\n 13:  48fc882785 ! 12:  76fc7670fc config: restructure format_config()\n     @@ Commit message\n          Signed-off-by: Derrick Stolee <stolee@gmail.com>\n      \n       ## builtin/config.c ##\n     +@@ builtin/config.c: struct config_display_options {\n     + \t.key_delim = ' ', \\\n     + }\n     + \n     ++#define TYPE_NONE\t\t0\n     + #define TYPE_BOOL\t\t1\n     + #define TYPE_INT\t\t2\n     + #define TYPE_BOOL_OR_INT\t3\n      @@ builtin/config.c: static int format_config(const struct config_display_options *opts,\n       \t\tshow_config_origin(opts, kvi, buf);\n       \tif (opts->show_keys)\n     @@ builtin/config.c: static int format_config(const struct config_display_options *\n      +\t\tres = format_config_color(buf, key_, value_, gently);\n      +\t\tbreak;\n      +\n     -+\tdefault:\n     ++\tcase TYPE_NONE:\n      +\t\tif (value_) {\n       \t\t\tstrbuf_addstr(buf, value_);\n       \t\t} else {\n       \t\t\t/* Just show the key name; back out delimiter */\n     -@@ builtin/config.c: static int format_config(const struct config_display_options *opts,\n     + \t\t\tif (opts->show_keys)\n       \t\t\t\tstrbuf_setlen(buf, buf->len - 1);\n       \t\t}\n     ++\t\tbreak;\n     ++\n     ++\tdefault:\n     ++\t\tBUG(\"undefined type %d\", opts->type);\n       \t}\n      +\n      +terminator:\n  -:  ---------- > 13:  3c32c037bc config: use an enum for type\n\n-- \ngitgitgadget\n"},{"id":"536771","messageId":"bca83d8ca86ead4df5cd424a8356367915248c43.1771849615.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v3.git.1771849615.gitgitgadget@gmail.com","subject":"[PATCH v3 01/13] config: move show_all_config()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-23T12:26:43Z","receivedAt":"2026-02-23T12:26:59Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nIn anticipation of using format_config() in this method, move\nshow_all_config() lower in the file without changes.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c | 48 ++++++++++++++++++++++++------------------------\n 1 file changed, 24 insertions(+), 24 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 288ebdfdaa..237f7a934d 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -231,30 +231,6 @@ static void show_config_scope(const struct config_display_options *opts,\n \tstrbuf_addch(buf, term);\n }\n \n-static int show_all_config(const char *key_, const char *value_,\n-\t\t\t   const struct config_context *ctx,\n-\t\t\t   void *cb)\n-{\n-\tconst struct config_display_options *opts = cb;\n-\tconst struct key_value_info *kvi = ctx->kvi;\n-\n-\tif (opts->show_origin || opts->show_scope) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tif (opts->show_scope)\n-\t\t\tshow_config_scope(opts, kvi, &buf);\n-\t\tif (opts->show_origin)\n-\t\t\tshow_config_origin(opts, kvi, &buf);\n-\t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n-\t\tfwrite(buf.buf, 1, buf.len, stdout);\n-\t\tstrbuf_release(&buf);\n-\t}\n-\tif (!opts->omit_values && value_)\n-\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n-\telse\n-\t\tprintf(\"%s%c\", key_, opts->term);\n-\treturn 0;\n-}\n-\n struct strbuf_list {\n \tstruct strbuf *items;\n \tint nr;\n@@ -332,6 +308,30 @@ static int format_config(const struct config_display_options *opts,\n \treturn 0;\n }\n \n+static int show_all_config(const char *key_, const char *value_,\n+\t\t\t   const struct config_context *ctx,\n+\t\t\t   void *cb)\n+{\n+\tconst struct config_display_options *opts = cb;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n+\n+\tif (opts->show_origin || opts->show_scope) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tif (opts->show_scope)\n+\t\t\tshow_config_scope(opts, kvi, &buf);\n+\t\tif (opts->show_origin)\n+\t\t\tshow_config_origin(opts, kvi, &buf);\n+\t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n+\t\tfwrite(buf.buf, 1, buf.len, stdout);\n+\t\tstrbuf_release(&buf);\n+\t}\n+\tif (!opts->omit_values && value_)\n+\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n+\telse\n+\t\tprintf(\"%s%c\", key_, opts->term);\n+\treturn 0;\n+}\n+\n #define GET_VALUE_ALL        (1 << 0)\n #define GET_VALUE_KEY_REGEXP (1 << 1)\n \n-- \ngitgitgadget\n\n"},{"id":"536772","messageId":"93c94a1b257d26a23942e902630c220e4a4f8eaf.1771849615.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v3.git.1771849615.gitgitgadget@gmail.com","subject":"[PATCH v3 02/13] config: add 'gently' parameter to format_config()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-23T12:26:44Z","receivedAt":"2026-02-23T12:27:00Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nThis parameter is set to 0 for all current callers and is UNUSED.\nHowever, we will start using this option in future changes and in a\ncritical change that requires gentle parsing (not using die()) to try\nparsing all values in a list.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 237f7a934d..b4c4228311 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -242,10 +242,14 @@ struct strbuf_list {\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n  * 0 on success, 1 on a missing optional value (i.e., telling the\n  * caller to pretend that <key_,value_> did not exist).\n+ *\n+ * Note: 'gently' is currently ignored, but will be implemented in\n+ * a future change.\n  */\n static int format_config(const struct config_display_options *opts,\n \t\t\t struct strbuf *buf, const char *key_,\n-\t\t\t const char *value_, const struct key_value_info *kvi)\n+\t\t\t const char *value_, const struct key_value_info *kvi,\n+\t\t\t int gently UNUSED)\n {\n \tif (opts->show_scope)\n \t\tshow_config_scope(opts, kvi, buf);\n@@ -372,7 +376,7 @@ static int collect_config(const char *key_, const char *value_,\n \tstrbuf_init(&values->items[values->nr], 0);\n \n \tstatus = format_config(data->display_opts, &values->items[values->nr++],\n-\t\t\t       key_, value_, kvi);\n+\t\t\t       key_, value_, kvi, 0);\n \tif (status < 0)\n \t\treturn status;\n \tif (status) {\n@@ -463,7 +467,7 @@ static int get_value(const struct config_location_options *opts,\n \t\tstrbuf_init(item, 0);\n \n \t\tstatus = format_config(display_opts, item, key_,\n-\t\t\t\t       display_opts->default_value, &kvi);\n+\t\t\t\t       display_opts->default_value, &kvi, 0);\n \t\tif (status < 0)\n \t\t\tdie(_(\"failed to format default config value: %s\"),\n \t\t\t    display_opts->default_value);\n@@ -743,7 +747,7 @@ static int get_urlmatch(const struct config_location_options *opts,\n \n \t\tstatus = format_config(&display_opts, &buf, item->string,\n \t\t\t\t       matched->value_is_null ? NULL : matched->value.buf,\n-\t\t\t\t       &matched->kvi);\n+\t\t\t\t       &matched->kvi, 0);\n \t\tif (!status)\n \t\t\tfwrite(buf.buf, 1, buf.len, stdout);\n \t\tstrbuf_release(&buf);\n-- \ngitgitgadget\n\n"},{"id":"536773","messageId":"2b2a325b55a906cc8eba97f2020684f44c42bfc5.1771849615.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v3.git.1771849615.gitgitgadget@gmail.com","subject":"[PATCH v3 03/13] config: make 'git config list --type=<X>' work","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-23T12:26:45Z","receivedAt":"2026-02-23T12:27:02Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nPreviously, the --type=<X> argument to 'git config list' was ignored and\ndid nothing. Now, we add the use of format_config() to the\nshow_all_config() function so each key-value pair is attempted to be\nparsed. This is our first use of the 'gently' parameter with a nonzero\nvalue.\n\nWhen listing multiple values, our initial settings for the output format\nis different. Add a new init helper to specify the fact that keys should\nbe shown and also add the default delimiters as they were unset in some\ncases.\n\nOur intention is that if there is an error in parsing, then the row is\nnot output. This is necessary to avoid the caller needing to build their\nown validator to understand the difference between valid, canonicalized\ntypes and other raw string values. The raw values will always be\navailable to the user if they do not specify the --type=<X> option.\n\nThe current behavior is more complicated, including error messages on\nbad parsing or potentially complete failure of the command.  We add\ntests at this point that demonstrate the current behavior so we can\nwitness the fix in future changes that parse these values quietly and\ngently.\n\nThis is a change in behavior! We are starting to respect an option that\nwas previously ignored, leading to potential user confusion. This is\nprobably still a good option, since the --type argument did not change\nbehavior at all previously, so users can get the behavior they expect by\nremoving the --type argument or adding the --no-type argument.\n\nt1300-config.sh is updated with the current behavior of this formatting\nlogic to justify the upcoming refactoring of format_config() that will\nincrementally fix some of these cases to be more user-friendly.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n Documentation/git-config.adoc |  3 ++\n builtin/config.c              | 35 +++++++------\n t/t1300-config.sh             | 97 ++++++++++++++++++++++++++++++++++-\n 3 files changed, 119 insertions(+), 16 deletions(-)\n\ndiff --git a/Documentation/git-config.adoc b/Documentation/git-config.adoc\nindex ac3b536a15..5300dd4c51 100644\n--- a/Documentation/git-config.adoc\n+++ b/Documentation/git-config.adoc\n@@ -240,6 +240,9 @@ Valid `<type>`'s include:\n   that the given value is canonicalize-able as an ANSI color, but it is written\n   as-is.\n +\n+If the command is in `list` mode, then the `--type <type>` argument will apply\n+to each listed config value. If the value does not successfully parse in that\n+format, then it will be omitted from the list.\n \n --bool::\n --int::\ndiff --git a/builtin/config.c b/builtin/config.c\nindex b4c4228311..4c4c791883 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -318,21 +318,12 @@ static int show_all_config(const char *key_, const char *value_,\n {\n \tconst struct config_display_options *opts = cb;\n \tconst struct key_value_info *kvi = ctx->kvi;\n+\tstruct strbuf formatted = STRBUF_INIT;\n \n-\tif (opts->show_origin || opts->show_scope) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tif (opts->show_scope)\n-\t\t\tshow_config_scope(opts, kvi, &buf);\n-\t\tif (opts->show_origin)\n-\t\t\tshow_config_origin(opts, kvi, &buf);\n-\t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n-\t\tfwrite(buf.buf, 1, buf.len, stdout);\n-\t\tstrbuf_release(&buf);\n-\t}\n-\tif (!opts->omit_values && value_)\n-\t\tprintf(\"%s%c%s%c\", key_, opts->delim, value_, opts->term);\n-\telse\n-\t\tprintf(\"%s%c\", key_, opts->term);\n+\tif (format_config(opts, &formatted, key_, value_, kvi, 1) >= 0)\n+\t\tfwrite(formatted.buf, 1, formatted.len, stdout);\n+\n+\tstrbuf_release(&formatted);\n \treturn 0;\n }\n \n@@ -872,6 +863,19 @@ static void display_options_init(struct config_display_options *opts)\n \t}\n }\n \n+static void display_options_init_list(struct config_display_options *opts)\n+{\n+\topts->show_keys = 1;\n+\n+\tif (opts->end_nul) {\n+\t\tdisplay_options_init(opts);\n+\t} else {\n+\t\topts->term = '\\n';\n+\t\topts->delim = ' ';\n+\t\topts->key_delim = '=';\n+\t}\n+}\n+\n static int cmd_config_list(int argc, const char **argv, const char *prefix,\n \t\t\t   struct repository *repo UNUSED)\n {\n@@ -890,7 +894,7 @@ static int cmd_config_list(int argc, const char **argv, const char *prefix,\n \tcheck_argc(argc, 0, 0);\n \n \tlocation_options_init(&location_opts, prefix);\n-\tdisplay_options_init(&display_opts);\n+\tdisplay_options_init_list(&display_opts);\n \n \tsetup_auto_pager(\"config\", 1);\n \n@@ -1321,6 +1325,7 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix)\n \n \tif (actions == ACTION_LIST) {\n \t\tcheck_argc(argc, 0, 0);\n+\t\tdisplay_options_init_list(&display_opts);\n \t\tif (config_with_options(show_all_config, &display_opts,\n \t\t\t\t\t&location_opts.source, the_repository,\n \t\t\t\t\t&location_opts.options) < 0) {\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 9850fcd5b5..dc744c0bae 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2459,9 +2459,15 @@ done\n \n cat >.git/config <<-\\EOF &&\n [section]\n-foo = true\n+foo = True\n number = 10\n big = 1M\n+path = ~/dir\n+red = red\n+blue = Blue\n+date = Fri Jun 4 15:46:55 2010\n+missing=:(optional)no-such-path\n+exists=:(optional)expect\n EOF\n \n test_expect_success 'identical modern --type specifiers are allowed' '\n@@ -2503,6 +2509,95 @@ test_expect_success 'unset type specifiers may be reset to conflicting ones' '\n \ttest_cmp_config 1048576 --type=bool --no-type --type=int section.big\n '\n \n+test_expect_success 'list --type=int shows only canonicalizable int values' '\n+\tcat >expect <<-EOF &&\n+\tsection.number=10\n+\tsection.big=1048576\n+\tEOF\n+\n+\ttest_must_fail git config ${mode_prefix}list --type=int\n+'\n+\n+test_expect_success 'list --type=bool shows only canonicalizable bool values' '\n+\tcat >expect <<-EOF &&\n+\tsection.foo=true\n+\tsection.number=true\n+\tsection.big=true\n+\tEOF\n+\n+\ttest_must_fail git config ${mode_prefix}list --type=bool\n+'\n+\n+test_expect_success 'list --type=bool-or-int shows only canonicalizable values' '\n+\tcat >expect <<-EOF &&\n+\tsection.foo=true\n+\tsection.number=10\n+\tsection.big=1048576\n+\tEOF\n+\n+\ttest_must_fail git config ${mode_prefix}list --type=bool-or-int\n+'\n+\n+test_expect_success 'list --type=path shows only canonicalizable path values' '\n+\t# TODO: handling of missing path is incorrect here.\n+\tcat >expect <<-EOF &&\n+\tsection.foo=True\n+\tsection.number=10\n+\tsection.big=1M\n+\tsection.path=$HOME/dir\n+\tsection.red=red\n+\tsection.blue=Blue\n+\tsection.date=Fri Jun 4 15:46:55 2010\n+\tsection.missing=section.exists=expect\n+\tEOF\n+\n+\tgit config ${mode_prefix}list --type=path >actual 2>err &&\n+\ttest_cmp expect actual &&\n+\ttest_must_be_empty err\n+'\n+\n+test_expect_success 'list --type=expiry-date shows only canonicalizable dates' '\n+\tcat >expecterr <<-EOF &&\n+\terror: '\\''True'\\'' for '\\''section.foo'\\'' is not a valid timestamp\n+\terror: '\\''~/dir'\\'' for '\\''section.path'\\'' is not a valid timestamp\n+\terror: '\\''red'\\'' for '\\''section.red'\\'' is not a valid timestamp\n+\terror: '\\''Blue'\\'' for '\\''section.blue'\\'' is not a valid timestamp\n+\terror: '\\'':(optional)no-such-path'\\'' for '\\''section.missing'\\'' is not a valid timestamp\n+\terror: '\\'':(optional)expect'\\'' for '\\''section.exists'\\'' is not a valid timestamp\n+\tEOF\n+\n+\tgit config ${mode_prefix}list --type=expiry-date >actual 2>err &&\n+\n+\t# section.number and section.big parse as relative dates that could\n+\t# have clock skew in their results.\n+\ttest_grep section.big actual &&\n+\ttest_grep section.number actual &&\n+\ttest_grep \"section.date=$(git config --type=expiry-date section.$key)\" actual &&\n+\ttest_cmp expecterr err\n+'\n+\n+test_expect_success 'list --type=color shows only canonicalizable color values' '\n+\tcat >expect <<-EOF &&\n+\tsection.number=<>\n+\tsection.red=<RED>\n+\tsection.blue=<BLUE>\n+\tEOF\n+\n+\tcat >expecterr <<-EOF &&\n+\terror: invalid color value: True\n+\terror: invalid color value: 1M\n+\terror: invalid color value: ~/dir\n+\terror: invalid color value: Fri Jun 4 15:46:55 2010\n+\terror: invalid color value: :(optional)no-such-path\n+\terror: invalid color value: :(optional)expect\n+\tEOF\n+\n+\tgit config ${mode_prefix}list --type=color >actual.raw 2>err &&\n+\ttest_decode_color <actual.raw >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_cmp expecterr err\n+'\n+\n test_expect_success '--type rejects unknown specifiers' '\n \ttest_must_fail git config --type=nonsense section.foo 2>error &&\n \ttest_grep \"unrecognized --type argument\" error\n-- \ngitgitgadget\n\n"},{"id":"536774","messageId":"4835ee51800871f0388bc0c4ea812bb90bfe4284.1771849615.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v3.git.1771849615.gitgitgadget@gmail.com","subject":"[PATCH v3 04/13] config: format int64s gently","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-23T12:26:46Z","receivedAt":"2026-02-23T12:27:03Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nMove the logic for formatting int64 config values into a helper method\nand use gentle parsing when needed.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c  | 27 +++++++++++++++++++++++----\n t/t1300-config.sh |  4 +++-\n 2 files changed, 26 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 4c4c791883..448b148563 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -237,6 +237,25 @@ struct strbuf_list {\n \tint alloc;\n };\n \n+static int format_config_int64(struct strbuf *buf,\n+\t\t\t       const char *key_,\n+\t\t\t       const char *value_,\n+\t\t\t       const struct key_value_info *kvi,\n+\t\t\t       int gently)\n+{\n+\tint64_t v = 0;\n+\tif (gently) {\n+\t\tif (!git_parse_int64(value_, &v))\n+\t\t\treturn -1;\n+\t} else {\n+\t\t/* may die() */\n+\t\tv = git_config_int64(key_, value_ ? value_ : \"\", kvi);\n+\t}\n+\n+\tstrbuf_addf(buf, \"%\"PRId64, v);\n+\treturn 0;\n+}\n+\n /*\n  * Format the configuration key-value pair (`key_`, `value_`) and\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n@@ -249,8 +268,9 @@ struct strbuf_list {\n static int format_config(const struct config_display_options *opts,\n \t\t\t struct strbuf *buf, const char *key_,\n \t\t\t const char *value_, const struct key_value_info *kvi,\n-\t\t\t int gently UNUSED)\n+\t\t\t int gently)\n {\n+\tint res = 0;\n \tif (opts->show_scope)\n \t\tshow_config_scope(opts, kvi, buf);\n \tif (opts->show_origin)\n@@ -262,8 +282,7 @@ static int format_config(const struct config_display_options *opts,\n \t\t\tstrbuf_addch(buf, opts->key_delim);\n \n \t\tif (opts->type == TYPE_INT)\n-\t\t\tstrbuf_addf(buf, \"%\"PRId64,\n-\t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\", kvi));\n+\t\t\tres = format_config_int64(buf, key_, value_, kvi, gently);\n \t\telse if (opts->type == TYPE_BOOL)\n \t\t\tstrbuf_addstr(buf, git_config_bool(key_, value_) ?\n \t\t\t\t      \"true\" : \"false\");\n@@ -309,7 +328,7 @@ static int format_config(const struct config_display_options *opts,\n \t\t}\n \t}\n \tstrbuf_addch(buf, opts->term);\n-\treturn 0;\n+\treturn res;\n }\n \n static int show_all_config(const char *key_, const char *value_,\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex dc744c0bae..05a812fd6d 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2515,7 +2515,9 @@ test_expect_success 'list --type=int shows only canonicalizable int values' '\n \tsection.big=1048576\n \tEOF\n \n-\ttest_must_fail git config ${mode_prefix}list --type=int\n+\tgit config ${mode_prefix}list --type=int >actual 2>err &&\n+\ttest_cmp expect actual &&\n+\ttest_must_be_empty err\n '\n \n test_expect_success 'list --type=bool shows only canonicalizable bool values' '\n-- \ngitgitgadget\n\n"},{"id":"536775","messageId":"24eb757a4031c0c712d24b835a97e81c6b2a84f1.1771849615.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v3.git.1771849615.gitgitgadget@gmail.com","subject":"[PATCH v3 05/13] config: format bools gently","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-23T12:26:47Z","receivedAt":"2026-02-23T12:27:04Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nMove the logic for formatting bool config values into a helper method\nand use gentle parsing when needed.\n\nThis makes 'git config list --type=bool' not fail when coming across a\nnon-boolean value. Such unparseable values are filtered out quietly.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c  | 21 +++++++++++++++++++--\n t/t1300-config.sh |  4 +++-\n 2 files changed, 22 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 448b148563..d8b38c51d3 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -256,6 +256,24 @@ static int format_config_int64(struct strbuf *buf,\n \treturn 0;\n }\n \n+static int format_config_bool(struct strbuf *buf,\n+\t\t\t      const char *key_,\n+\t\t\t      const char *value_,\n+\t\t\t      int gently)\n+{\n+\tint v = 0;\n+\tif (gently) {\n+\t\tif ((v = git_parse_maybe_bool(value_)) < 0)\n+\t\t\treturn -1;\n+\t} else {\n+\t\t/* may die() */\n+\t\tv = git_config_bool(key_, value_);\n+\t}\n+\n+\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n+\treturn 0;\n+}\n+\n /*\n  * Format the configuration key-value pair (`key_`, `value_`) and\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n@@ -284,8 +302,7 @@ static int format_config(const struct config_display_options *opts,\n \t\tif (opts->type == TYPE_INT)\n \t\t\tres = format_config_int64(buf, key_, value_, kvi, gently);\n \t\telse if (opts->type == TYPE_BOOL)\n-\t\t\tstrbuf_addstr(buf, git_config_bool(key_, value_) ?\n-\t\t\t\t      \"true\" : \"false\");\n+\t\t\tres = format_config_bool(buf, key_, value_, gently);\n \t\telse if (opts->type == TYPE_BOOL_OR_INT) {\n \t\t\tint is_bool, v;\n \t\t\tv = git_config_bool_or_int(key_, value_, kvi,\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 05a812fd6d..568cfaa3c5 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2527,7 +2527,9 @@ test_expect_success 'list --type=bool shows only canonicalizable bool values' '\n \tsection.big=true\n \tEOF\n \n-\ttest_must_fail git config ${mode_prefix}list --type=bool\n+\tgit config ${mode_prefix}list --type=bool >actual 2>err &&\n+\ttest_cmp expect actual &&\n+\ttest_must_be_empty err\n '\n \n test_expect_success 'list --type=bool-or-int shows only canonicalizable values' '\n-- \ngitgitgadget\n\n"},{"id":"536776","messageId":"02849ca62124f6d76359bdf22473e00b79822eac.1771849615.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v3.git.1771849615.gitgitgadget@gmail.com","subject":"[PATCH v3 06/13] config: format bools or ints gently","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-23T12:26:48Z","receivedAt":"2026-02-23T12:27:05Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nMove the logic for formatting bool-or-int config values into a helper\nmethod and use gentle parsing when needed.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c  | 40 +++++++++++++++++++++++++++++++---------\n t/t1300-config.sh |  4 +++-\n 2 files changed, 34 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex d8b38c51d3..491a880e56 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -274,6 +274,34 @@ static int format_config_bool(struct strbuf *buf,\n \treturn 0;\n }\n \n+static int format_config_bool_or_int(struct strbuf *buf,\n+\t\t\t\t     const char *key_,\n+\t\t\t\t     const char *value_,\n+\t\t\t\t     const struct key_value_info *kvi,\n+\t\t\t\t     int gently)\n+{\n+\tint v, is_bool = 0;\n+\n+\tif (gently) {\n+\t\tv = git_parse_maybe_bool_text(value_);\n+\n+\t\tif (v >= 0)\n+\t\t\tis_bool = 1;\n+\t\telse if (!git_parse_int(value_, &v))\n+\t\t\treturn -1;\n+\t} else {\n+\t\tv = git_config_bool_or_int(key_, value_, kvi,\n+\t\t\t\t\t   &is_bool);\n+\t}\n+\n+\tif (is_bool)\n+\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n+\telse\n+\t\tstrbuf_addf(buf, \"%d\", v);\n+\n+\treturn 0;\n+}\n+\n /*\n  * Format the configuration key-value pair (`key_`, `value_`) and\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n@@ -303,15 +331,9 @@ static int format_config(const struct config_display_options *opts,\n \t\t\tres = format_config_int64(buf, key_, value_, kvi, gently);\n \t\telse if (opts->type == TYPE_BOOL)\n \t\t\tres = format_config_bool(buf, key_, value_, gently);\n-\t\telse if (opts->type == TYPE_BOOL_OR_INT) {\n-\t\t\tint is_bool, v;\n-\t\t\tv = git_config_bool_or_int(key_, value_, kvi,\n-\t\t\t\t\t\t   &is_bool);\n-\t\t\tif (is_bool)\n-\t\t\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n-\t\t\telse\n-\t\t\t\tstrbuf_addf(buf, \"%d\", v);\n-\t\t} else if (opts->type == TYPE_BOOL_OR_STR) {\n+\t\telse if (opts->type == TYPE_BOOL_OR_INT)\n+\t\t\tres = format_config_bool_or_int(buf, key_, value_, kvi, gently);\n+\t\telse if (opts->type == TYPE_BOOL_OR_STR) {\n \t\t\tint v = git_parse_maybe_bool(value_);\n \t\t\tif (v < 0)\n \t\t\t\tstrbuf_addstr(buf, value_);\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 568cfaa3c5..1fc8e788ee 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2539,7 +2539,9 @@ test_expect_success 'list --type=bool-or-int shows only canonicalizable values'\n \tsection.big=1048576\n \tEOF\n \n-\ttest_must_fail git config ${mode_prefix}list --type=bool-or-int\n+\tgit config ${mode_prefix}list --type=bool-or-int >actual 2>err &&\n+\ttest_cmp expect actual &&\n+\ttest_must_be_empty err\n '\n \n test_expect_success 'list --type=path shows only canonicalizable path values' '\n-- \ngitgitgadget\n\n"},{"id":"536777","messageId":"9f06db29b9e87f9d041003f38513015c41447dd8.1771849615.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v3.git.1771849615.gitgitgadget@gmail.com","subject":"[PATCH v3 07/13] config: format bools or strings in helper","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-23T12:26:49Z","receivedAt":"2026-02-23T12:27:07Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nMove the logic for formatting bool-or-string config values into a\nhelper. This parsing has always been gentle, so this is not unlocking\nnew behavior. This extraction is only to match the formatting of the\nother cases that do need a behavior change.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c | 22 +++++++++++++++-------\n 1 file changed, 15 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 491a880e56..79c139c5b0 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -302,6 +302,18 @@ static int format_config_bool_or_int(struct strbuf *buf,\n \treturn 0;\n }\n \n+/* This mode is always gentle. */\n+static int format_config_bool_or_str(struct strbuf *buf,\n+\t\t\t\t     const char *value_)\n+{\n+\tint v = git_parse_maybe_bool(value_);\n+\tif (v < 0)\n+\t\tstrbuf_addstr(buf, value_);\n+\telse\n+\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n+\treturn 0;\n+}\n+\n /*\n  * Format the configuration key-value pair (`key_`, `value_`) and\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n@@ -333,13 +345,9 @@ static int format_config(const struct config_display_options *opts,\n \t\t\tres = format_config_bool(buf, key_, value_, gently);\n \t\telse if (opts->type == TYPE_BOOL_OR_INT)\n \t\t\tres = format_config_bool_or_int(buf, key_, value_, kvi, gently);\n-\t\telse if (opts->type == TYPE_BOOL_OR_STR) {\n-\t\t\tint v = git_parse_maybe_bool(value_);\n-\t\t\tif (v < 0)\n-\t\t\t\tstrbuf_addstr(buf, value_);\n-\t\t\telse\n-\t\t\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n-\t\t} else if (opts->type == TYPE_PATH) {\n+\t\telse if (opts->type == TYPE_BOOL_OR_STR)\n+\t\t\tres = format_config_bool_or_str(buf, value_);\n+\t\telse if (opts->type == TYPE_PATH) {\n \t\t\tchar *v;\n \t\t\tif (git_config_pathname(&v, key_, value_) < 0)\n \t\t\t\treturn -1;\n-- \ngitgitgadget\n\n"},{"id":"536778","messageId":"d198c238e948e500b769d7b4d8267109f7297a8c.1771849615.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v3.git.1771849615.gitgitgadget@gmail.com","subject":"[PATCH v3 08/13] config: format paths gently","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-23T12:26:50Z","receivedAt":"2026-02-23T12:27:08Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nMove the logic for formatting path config values into a helper method\nand use gentle parsing when needed.\n\nWe need to be careful about how to handle the ':(optional)' macro, which\nas tested in t1311-config-optional.sh must allow for ignoring a missing\npath when other multiple values exist, but cause 'git config get' to\nfail if it is the only possible value and thus no result is output.\n\nIn the case of our list, we need to omit those values silently. This\nnecessitates the use of the 'gently' parameter here.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c  | 32 ++++++++++++++++++++++----------\n t/t1300-config.sh |  3 +--\n 2 files changed, 23 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 79c139c5b0..2828b6dcf1 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -314,6 +314,25 @@ static int format_config_bool_or_str(struct strbuf *buf,\n \treturn 0;\n }\n \n+static int format_config_path(struct strbuf *buf,\n+\t\t\t      const char *key_,\n+\t\t\t      const char *value_,\n+\t\t\t      int gently)\n+{\n+\tchar *v;\n+\n+\tif (git_config_pathname(&v, key_, value_) < 0)\n+\t\treturn -1;\n+\n+\tif (v)\n+\t\tstrbuf_addstr(buf, v);\n+\telse\n+\t\treturn gently ? -1 : 1; /* :(optional)no-such-file */\n+\n+\tfree(v);\n+\treturn 0;\n+}\n+\n /*\n  * Format the configuration key-value pair (`key_`, `value_`) and\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n@@ -347,16 +366,9 @@ static int format_config(const struct config_display_options *opts,\n \t\t\tres = format_config_bool_or_int(buf, key_, value_, kvi, gently);\n \t\telse if (opts->type == TYPE_BOOL_OR_STR)\n \t\t\tres = format_config_bool_or_str(buf, value_);\n-\t\telse if (opts->type == TYPE_PATH) {\n-\t\t\tchar *v;\n-\t\t\tif (git_config_pathname(&v, key_, value_) < 0)\n-\t\t\t\treturn -1;\n-\t\t\tif (v)\n-\t\t\t\tstrbuf_addstr(buf, v);\n-\t\t\telse\n-\t\t\t\treturn 1; /* :(optional)no-such-file */\n-\t\t\tfree((char *)v);\n-\t\t} else if (opts->type == TYPE_EXPIRY_DATE) {\n+\t\telse if (opts->type == TYPE_PATH)\n+\t\t\tres = format_config_path(buf, key_, value_, gently);\n+\t\telse if (opts->type == TYPE_EXPIRY_DATE) {\n \t\t\ttimestamp_t t;\n \t\t\tif (git_config_expiry_date(&t, key_, value_) < 0)\n \t\t\t\treturn -1;\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 1fc8e788ee..48d9c554d8 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2545,7 +2545,6 @@ test_expect_success 'list --type=bool-or-int shows only canonicalizable values'\n '\n \n test_expect_success 'list --type=path shows only canonicalizable path values' '\n-\t# TODO: handling of missing path is incorrect here.\n \tcat >expect <<-EOF &&\n \tsection.foo=True\n \tsection.number=10\n@@ -2554,7 +2553,7 @@ test_expect_success 'list --type=path shows only canonicalizable path values' '\n \tsection.red=red\n \tsection.blue=Blue\n \tsection.date=Fri Jun 4 15:46:55 2010\n-\tsection.missing=section.exists=expect\n+\tsection.exists=expect\n \tEOF\n \n \tgit config ${mode_prefix}list --type=path >actual 2>err &&\n-- \ngitgitgadget\n\n"},{"id":"536779","messageId":"7568a0bc349e7cc454a50cc43a1549031ca0ab9b.1771849615.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v3.git.1771849615.gitgitgadget@gmail.com","subject":"[PATCH v3 09/13] config: format expiry dates quietly","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-23T12:26:51Z","receivedAt":"2026-02-23T12:27:12Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nMove the logic for formatting expiry date config values into a helper\nmethod and use quiet parsing when needed.\n\nNote that git_config_expiry_date() will show an error on a bad parse and\nnot die() like most other git_config...() parsers. Thus, we use\n'quietly' here instead of 'gently'.\n\nThere is an unfortunate asymmetry in these two parsing methods, but we\nneed to treat a positive response from parse_expiry_date() as an error\nor we will get incorrect values.\n\nThis updates the behavior of 'git config list --type=expiry-date' to be\nquiet when attempting parsing on non-date values.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c  | 27 +++++++++++++++++++++------\n t/t1300-config.sh | 11 +----------\n 2 files changed, 22 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 2828b6dcf1..ee77ddc87c 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -3,6 +3,7 @@\n #include \"abspath.h\"\n #include \"config.h\"\n #include \"color.h\"\n+#include \"date.h\"\n #include \"editor.h\"\n #include \"environment.h\"\n #include \"gettext.h\"\n@@ -333,6 +334,23 @@ static int format_config_path(struct strbuf *buf,\n \treturn 0;\n }\n \n+static int format_config_expiry_date(struct strbuf *buf,\n+\t\t\t\t     const char *key_,\n+\t\t\t\t     const char *value_,\n+\t\t\t\t     int quietly)\n+{\n+\ttimestamp_t t;\n+\tif (quietly) {\n+\t\tif (parse_expiry_date(value_, &t))\n+\t\t\treturn -1;\n+\t} else if (git_config_expiry_date(&t, key_, value_) < 0) {\n+\t\treturn -1;\n+\t}\n+\n+\tstrbuf_addf(buf, \"%\"PRItime, t);\n+\treturn 0;\n+}\n+\n /*\n  * Format the configuration key-value pair (`key_`, `value_`) and\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n@@ -368,12 +386,9 @@ static int format_config(const struct config_display_options *opts,\n \t\t\tres = format_config_bool_or_str(buf, value_);\n \t\telse if (opts->type == TYPE_PATH)\n \t\t\tres = format_config_path(buf, key_, value_, gently);\n-\t\telse if (opts->type == TYPE_EXPIRY_DATE) {\n-\t\t\ttimestamp_t t;\n-\t\t\tif (git_config_expiry_date(&t, key_, value_) < 0)\n-\t\t\t\treturn -1;\n-\t\t\tstrbuf_addf(buf, \"%\"PRItime, t);\n-\t\t} else if (opts->type == TYPE_COLOR) {\n+\t\telse if (opts->type == TYPE_EXPIRY_DATE)\n+\t\t\tres = format_config_expiry_date(buf, key_, value_, gently);\n+\t\telse if (opts->type == TYPE_COLOR) {\n \t\t\tchar v[COLOR_MAXLEN];\n \t\t\tif (git_config_color(v, key_, value_) < 0)\n \t\t\t\treturn -1;\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 48d9c554d8..72bdd6ab03 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2562,15 +2562,6 @@ test_expect_success 'list --type=path shows only canonicalizable path values' '\n '\n \n test_expect_success 'list --type=expiry-date shows only canonicalizable dates' '\n-\tcat >expecterr <<-EOF &&\n-\terror: '\\''True'\\'' for '\\''section.foo'\\'' is not a valid timestamp\n-\terror: '\\''~/dir'\\'' for '\\''section.path'\\'' is not a valid timestamp\n-\terror: '\\''red'\\'' for '\\''section.red'\\'' is not a valid timestamp\n-\terror: '\\''Blue'\\'' for '\\''section.blue'\\'' is not a valid timestamp\n-\terror: '\\'':(optional)no-such-path'\\'' for '\\''section.missing'\\'' is not a valid timestamp\n-\terror: '\\'':(optional)expect'\\'' for '\\''section.exists'\\'' is not a valid timestamp\n-\tEOF\n-\n \tgit config ${mode_prefix}list --type=expiry-date >actual 2>err &&\n \n \t# section.number and section.big parse as relative dates that could\n@@ -2578,7 +2569,7 @@ test_expect_success 'list --type=expiry-date shows only canonicalizable dates' '\n \ttest_grep section.big actual &&\n \ttest_grep section.number actual &&\n \ttest_grep \"section.date=$(git config --type=expiry-date section.$key)\" actual &&\n-\ttest_cmp expecterr err\n+\ttest_must_be_empty err\n '\n \n test_expect_success 'list --type=color shows only canonicalizable color values' '\n-- \ngitgitgadget\n\n"},{"id":"536780","messageId":"d98966f53a4f201be041c4e8a83ed549f64aa404.1771849615.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v3.git.1771849615.gitgitgadget@gmail.com","subject":"[PATCH v3 10/13] color: add color_parse_quietly()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-23T12:26:52Z","receivedAt":"2026-02-23T12:27:13Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nWhen parsing colors, a failed parse leads to an error message due to the\nresult returning error(). To allow for quiet parsing, create\ncolor_parse_quietly(). This is in contrast to an ..._gently() version\nbecause the original does not die(), so both options are technically\n'gentle'.\n\nTo accomplish this, convert the implementation of color_parse_mem() into\na static color_parse_mem_1() helper that adds a 'quiet' parameter. The\ncolor_parse_quietly() method can then use this. Since it is a near\nequivalent to color_parse(), move that method down in the file so they\ncan be nearby while also appearing after color_parse_mem_1().\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n color.c | 25 ++++++++++++++++++-------\n color.h |  1 +\n 2 files changed, 19 insertions(+), 7 deletions(-)\n\ndiff --git a/color.c b/color.c\nindex 07ac8c9d40..00b53f97ac 100644\n--- a/color.c\n+++ b/color.c\n@@ -223,11 +223,6 @@ static int parse_attr(const char *name, size_t len)\n \treturn -1;\n }\n \n-int color_parse(const char *value, char *dst)\n-{\n-\treturn color_parse_mem(value, strlen(value), dst);\n-}\n-\n /*\n  * Write the ANSI color codes for \"c\" to \"out\"; the string should\n  * already have the ANSI escape code in it. \"out\" should have enough\n@@ -264,7 +259,8 @@ static int color_empty(const struct color *c)\n \treturn c->type <= COLOR_NORMAL;\n }\n \n-int color_parse_mem(const char *value, int value_len, char *dst)\n+static int color_parse_mem_1(const char *value, int value_len,\n+\t\t\t     char *dst, int quiet)\n {\n \tconst char *ptr = value;\n \tint len = value_len;\n@@ -365,10 +361,25 @@ int color_parse_mem(const char *value, int value_len, char *dst)\n \tOUT(0);\n \treturn 0;\n bad:\n-\treturn error(_(\"invalid color value: %.*s\"), value_len, value);\n+\treturn quiet ? -1 : error(_(\"invalid color value: %.*s\"), value_len, value);\n #undef OUT\n }\n \n+int color_parse_mem(const char *value, int value_len, char *dst)\n+{\n+\treturn color_parse_mem_1(value, value_len, dst, 0);\n+}\n+\n+int color_parse(const char *value, char *dst)\n+{\n+\treturn color_parse_mem(value, strlen(value), dst);\n+}\n+\n+int color_parse_quietly(const char *value, char *dst)\n+{\n+\treturn color_parse_mem_1(value, strlen(value), dst, 1);\n+}\n+\n enum git_colorbool git_config_colorbool(const char *var, const char *value)\n {\n \tif (value) {\ndiff --git a/color.h b/color.h\nindex 43e6c9ad09..0d72540300 100644\n--- a/color.h\n+++ b/color.h\n@@ -118,6 +118,7 @@ bool want_color_fd(int fd, enum git_colorbool var);\n  * terminal.\n  */\n int color_parse(const char *value, char *dst);\n+int color_parse_quietly(const char *value, char *dst);\n int color_parse_mem(const char *value, int len, char *dst);\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"536781","messageId":"078065cff0dae7f2da4d4339caa8163199b907b4.1771849615.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v3.git.1771849615.gitgitgadget@gmail.com","subject":"[PATCH v3 11/13] config: format colors quietly","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-23T12:26:53Z","receivedAt":"2026-02-23T12:27:14Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nMove the logic for formatting color config value into a helper method\nand use quiet parsing when needed.\n\nThis removes error messages when parsing a list of config values that do\nnot match color formats.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c  | 27 +++++++++++++++++++++------\n t/t1300-config.sh | 11 +----------\n 2 files changed, 22 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex ee77ddc87c..45304076dc 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -351,6 +351,24 @@ static int format_config_expiry_date(struct strbuf *buf,\n \treturn 0;\n }\n \n+static int format_config_color(struct strbuf *buf,\n+\t\t\t       const char *key_,\n+\t\t\t       const char *value_,\n+\t\t\t       int gently)\n+{\n+\tchar v[COLOR_MAXLEN];\n+\n+\tif (gently) {\n+\t\tif (color_parse_quietly(value_, v) < 0)\n+\t\t\treturn -1;\n+\t} else if (git_config_color(v, key_, value_) < 0) {\n+\t\treturn -1;\n+\t}\n+\n+\tstrbuf_addstr(buf, v);\n+\treturn 0;\n+}\n+\n /*\n  * Format the configuration key-value pair (`key_`, `value_`) and\n  * append it into strbuf `buf`.  Returns a negative value on failure,\n@@ -388,12 +406,9 @@ static int format_config(const struct config_display_options *opts,\n \t\t\tres = format_config_path(buf, key_, value_, gently);\n \t\telse if (opts->type == TYPE_EXPIRY_DATE)\n \t\t\tres = format_config_expiry_date(buf, key_, value_, gently);\n-\t\telse if (opts->type == TYPE_COLOR) {\n-\t\t\tchar v[COLOR_MAXLEN];\n-\t\t\tif (git_config_color(v, key_, value_) < 0)\n-\t\t\t\treturn -1;\n-\t\t\tstrbuf_addstr(buf, v);\n-\t\t} else if (value_) {\n+\t\telse if (opts->type == TYPE_COLOR)\n+\t\t\tres = format_config_color(buf, key_, value_, gently);\n+\t\telse if (value_) {\n \t\t\tstrbuf_addstr(buf, value_);\n \t\t} else {\n \t\t\t/* Just show the key name; back out delimiter */\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 72bdd6ab03..128971ee12 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2579,19 +2579,10 @@ test_expect_success 'list --type=color shows only canonicalizable color values'\n \tsection.blue=<BLUE>\n \tEOF\n \n-\tcat >expecterr <<-EOF &&\n-\terror: invalid color value: True\n-\terror: invalid color value: 1M\n-\terror: invalid color value: ~/dir\n-\terror: invalid color value: Fri Jun 4 15:46:55 2010\n-\terror: invalid color value: :(optional)no-such-path\n-\terror: invalid color value: :(optional)expect\n-\tEOF\n-\n \tgit config ${mode_prefix}list --type=color >actual.raw 2>err &&\n \ttest_decode_color <actual.raw >actual &&\n \ttest_cmp expect actual &&\n-\ttest_cmp expecterr err\n+\ttest_must_be_empty err\n '\n \n test_expect_success '--type rejects unknown specifiers' '\n-- \ngitgitgadget\n\n"},{"id":"536782","messageId":"76fc7670fcd2ec6e4d236f415e887a6ac66c9fcd.1771849615.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v3.git.1771849615.gitgitgadget@gmail.com","subject":"[PATCH v3 12/13] config: restructure format_config()","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-23T12:26:54Z","receivedAt":"2026-02-23T12:27:15Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nThe recent changes have replaced the bodies of most if/else-if cases\nwith simple helper method calls. This makes it easy to adapt the\nstructure into a clearer switch statement, leaving a simple if/else in\nthe default case.\n\nMake things a little simpler to read by reducing the nesting depth via a\nnew goto statement when we want to skip values.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c | 64 ++++++++++++++++++++++++++++++++++--------------\n 1 file changed, 45 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 45304076dc..2e8bc6590c 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -124,6 +124,7 @@ struct config_display_options {\n \t.key_delim = ' ', \\\n }\n \n+#define TYPE_NONE\t\t0\n #define TYPE_BOOL\t\t1\n #define TYPE_INT\t\t2\n #define TYPE_BOOL_OR_INT\t3\n@@ -390,32 +391,57 @@ static int format_config(const struct config_display_options *opts,\n \t\tshow_config_origin(opts, kvi, buf);\n \tif (opts->show_keys)\n \t\tstrbuf_addstr(buf, key_);\n-\tif (!opts->omit_values) {\n-\t\tif (opts->show_keys)\n-\t\t\tstrbuf_addch(buf, opts->key_delim);\n-\n-\t\tif (opts->type == TYPE_INT)\n-\t\t\tres = format_config_int64(buf, key_, value_, kvi, gently);\n-\t\telse if (opts->type == TYPE_BOOL)\n-\t\t\tres = format_config_bool(buf, key_, value_, gently);\n-\t\telse if (opts->type == TYPE_BOOL_OR_INT)\n-\t\t\tres = format_config_bool_or_int(buf, key_, value_, kvi, gently);\n-\t\telse if (opts->type == TYPE_BOOL_OR_STR)\n-\t\t\tres = format_config_bool_or_str(buf, value_);\n-\t\telse if (opts->type == TYPE_PATH)\n-\t\t\tres = format_config_path(buf, key_, value_, gently);\n-\t\telse if (opts->type == TYPE_EXPIRY_DATE)\n-\t\t\tres = format_config_expiry_date(buf, key_, value_, gently);\n-\t\telse if (opts->type == TYPE_COLOR)\n-\t\t\tres = format_config_color(buf, key_, value_, gently);\n-\t\telse if (value_) {\n+\n+\tif (opts->omit_values)\n+\t\tgoto terminator;\n+\n+\tif (opts->show_keys)\n+\t\tstrbuf_addch(buf, opts->key_delim);\n+\n+\tswitch (opts->type) {\n+\tcase TYPE_INT:\n+\t\tres = format_config_int64(buf, key_, value_, kvi, gently);\n+\t\tbreak;\n+\n+\tcase TYPE_BOOL:\n+\t\tres = format_config_bool(buf, key_, value_, gently);\n+\t\tbreak;\n+\n+\tcase TYPE_BOOL_OR_INT:\n+\t\tres = format_config_bool_or_int(buf, key_, value_, kvi, gently);\n+\t\tbreak;\n+\n+\tcase TYPE_BOOL_OR_STR:\n+\t\tres = format_config_bool_or_str(buf, value_);\n+\t\tbreak;\n+\n+\tcase TYPE_PATH:\n+\t\tres = format_config_path(buf, key_, value_, gently);\n+\t\tbreak;\n+\n+\tcase TYPE_EXPIRY_DATE:\n+\t\tres = format_config_expiry_date(buf, key_, value_, gently);\n+\t\tbreak;\n+\n+\tcase TYPE_COLOR:\n+\t\tres = format_config_color(buf, key_, value_, gently);\n+\t\tbreak;\n+\n+\tcase TYPE_NONE:\n+\t\tif (value_) {\n \t\t\tstrbuf_addstr(buf, value_);\n \t\t} else {\n \t\t\t/* Just show the key name; back out delimiter */\n \t\t\tif (opts->show_keys)\n \t\t\t\tstrbuf_setlen(buf, buf->len - 1);\n \t\t}\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tBUG(\"undefined type %d\", opts->type);\n \t}\n+\n+terminator:\n \tstrbuf_addch(buf, opts->term);\n \treturn res;\n }\n-- \ngitgitgadget\n\n"},{"id":"536783","messageId":"3c32c037bc9651ca5492b2e1f9f6a30dc0d9c989.1771849615.git.gitgitgadget@gmail.com","threadId":"64966","inReplyTo":"pull.2044.v3.git.1771849615.gitgitgadget@gmail.com","subject":"[PATCH v3 13/13] config: use an enum for type","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-02-23T12:26:55Z","receivedAt":"2026-02-23T12:27:16Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nThe --type=<X> option for 'git config' has previously been defined using\nmacros, but using a typed enum is better for tracking the possible\nvalues.\n\nMove the definition up to make sure it is defined before a macro uses\nsome of its terms.\n\nUpdate the initializer for config_display_options to explicitly set\n'type' to TYPE_NONE even though this is implied by a zero value.\n\nThis assists in knowing that the switch statement added in the previous\nchange has a complete set of cases for a properly-valued enum.\n\nSigned-off-by: Derrick Stolee <stolee@gmail.com>\n---\n builtin/config.c | 23 +++++++++++++----------\n 1 file changed, 13 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 2e8bc6590c..7c4857be62 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -86,6 +86,17 @@ struct config_location_options {\n \t.respect_includes_opt = -1, \\\n }\n \n+enum config_type {\n+\tTYPE_NONE = 0,\n+\tTYPE_BOOL,\n+\tTYPE_INT,\n+\tTYPE_BOOL_OR_INT,\n+\tTYPE_PATH,\n+\tTYPE_EXPIRY_DATE,\n+\tTYPE_COLOR,\n+\tTYPE_BOOL_OR_STR,\n+};\n+\n #define CONFIG_TYPE_OPTIONS(type) \\\n \tOPT_GROUP(N_(\"Type\")), \\\n \tOPT_CALLBACK('t', \"type\", &type, N_(\"type\"), N_(\"value is given this type\"), option_parse_type), \\\n@@ -111,7 +122,7 @@ struct config_display_options {\n \tint show_origin;\n \tint show_scope;\n \tint show_keys;\n-\tint type;\n+\tenum config_type type;\n \tchar *default_value;\n \t/* Populated via `display_options_init()`. */\n \tint term;\n@@ -122,17 +133,9 @@ struct config_display_options {\n \t.term = '\\n', \\\n \t.delim = '=', \\\n \t.key_delim = ' ', \\\n+\t.type = TYPE_NONE, \\\n }\n \n-#define TYPE_NONE\t\t0\n-#define TYPE_BOOL\t\t1\n-#define TYPE_INT\t\t2\n-#define TYPE_BOOL_OR_INT\t3\n-#define TYPE_PATH\t\t4\n-#define TYPE_EXPIRY_DATE\t5\n-#define TYPE_COLOR\t\t6\n-#define TYPE_BOOL_OR_STR\t7\n-\n #define OPT_CALLBACK_VALUE(s, l, v, h, i) { \\\n \t.type = OPTION_CALLBACK, \\\n \t.short_name = (s), \\\n-- \ngitgitgadget\n"}]}