{"thread":{"id":"64290","subject":"[PATCH/RFC 0/4] config: read both home and xdg files for --global","startedAt":"2025-10-10T01:14:11Z","lastAt":"2026-09-10T04:49:05Z","messageCount":25,"participants":["Delilah Ashley Wu via GitGitGadget","Kristoffer Haugsbakk","Johannes Schindelin","Junio C Hamano","Delilah Ashley Wu","Chris Torek"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"528444","messageId":"pull.1938.git.1760058849.gitgitgadget@gmail.com","threadId":"64290","inReplyTo":null,"subject":"[PATCH/RFC 0/4] config: read both home and xdg files for --global","fromName":"Delilah Ashley Wu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-10-10T01:14:05Z","receivedAt":"2025-10-10T01:14:11Z","isPatch":true,"body":"Hi!\n\nAs reported in [1]: `$HOME/.gitconfig` and `$XDG_CONFIG_HOME/git/config` are\nboth valid global config locations, but `git config list --global` only\nincludes the former in its output.\n\nSuppose we have this config in `$HOME/.gitconfig`:\n\n[home]\n    config = true\n\n\nAnd this config in `$XDG_CONFIG_HOME/git/config`:\n\n[xdg]\n    config = true\n\n\nThen, to reproduce the issue that `--global` only shows the home config:\n\n$ git config list --global --show-scope --show-origin\nglobal  file:/Users/delilah/.gitconfig    home.config=true\n\n\nGit correctly applies the XDG config in its effective configuration, but it\ndoesn't show up when `--global` is specified. We can confirm this by\nchecking the output without the `--global` flag:\n\n$ git config list --show-scope --show-origin\nglobal  file:/Users/delilah/.config/git/config    xdg.config=true\nglobal  file:/Users/delilah/.gitconfig            home.config=true\n\n\nThe expected behaviour is both configs should be shown when `--global` is\nspecified, so we'd expect its output to look the same as above. This was\nconfirmed in [2], which quoted the `git config` documentation:\n\n> OPTIONS\n>     --global::\n>         For writing options: write to global `~/.gitconfig` file\n>         rather than the repository `.git/config`, write to\n>         `$XDG_CONFIG_HOME/git/config` file if this file exists and the\n>         `~/.gitconfig` file doesn't.\n>\n>         For reading options: read only from global `~/.gitconfig` and from\n>         `$XDG_CONFIG_HOME/git/config` rather than from all available files.\n\n\nThe first patch fixes forward slash normalisation on Windows paths. The\nsecond patch introduces tests and regression checks. The third and fourth\npatches implement the fix to include both config files when `--global` is\nspecified. Johannes has kindly pre-reviewed this patch series via GitHub on\nGitGitGadget #1938 [3]. You'll notice some force-pushes after the review,\nbut I only changed commit messages.\n\n[1]:\nhttps://lore.kernel.org/git/CAFA9we-QLQRzJdGMMCPatmfrk1oHeiUu9msMRXXk1MLE5HRxBQ@mail.gmail.com/\n[2]: https://lore.kernel.org/git/xmqqmt5lezi3.fsf@gitster.g/\n[3]: https://github.com/gitgitgadget/git/pull/1938/\n\nThank you all for your time!\nDelilah\n\nDelilah Ashley Wu (4):\n  cleanup_path: force forward slashes on Windows\n  config: test home and xdg files in `list --global`\n  config: read global scope via config_sequence\n  config: keep bailing on unreadable global files\n\n builtin/config.c     | 12 ++++++++\n config.c             | 54 ++++++++++++++++++++++++++----------\n config.h             |  2 ++\n path.c               | 10 +++++--\n t/t1300-config.sh    | 65 ++++++++++++++++++++++++++++++++++++++++++++\n t/t1306-xdg-files.sh |  3 +-\n 6 files changed, 128 insertions(+), 18 deletions(-)\n\n\nbase-commit: ca2559c1d630eb4f04cdee2328aaf1c768907a9e\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1938%2Fdelilahw%2Flilah%2Ffix-config-list-global-home-and-xdg%2Fpatchset-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1938/delilahw/lilah/fix-config-list-global-home-and-xdg/patchset-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1938\n-- \ngitgitgadget\n"},{"id":"528445","messageId":"c8df6a042b9e971f392b2fd2d09a9c3c655dbceb.1760058849.git.gitgitgadget@gmail.com","threadId":"64290","inReplyTo":"pull.1938.git.1760058849.gitgitgadget@gmail.com","subject":"[PATCH/RFC 1/4] cleanup_path: force forward slashes on Windows","fromName":"Delilah Ashley Wu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-10-10T01:14:06Z","receivedAt":"2025-10-10T01:14:12Z","isPatch":true,"body":"From: Delilah Ashley Wu <delilahwu@microsoft.com>\n\nGit prefers forward slashes as directory separators across all\nplatforms. On Windows, the backslash is the native directory separator,\nbut all Windows versions supported by Git also accept the forward slash\nin all but rare circumstances. Our tests expect forward slashes. Git\ngenerates relative paths with forward slashes. Forward slashes are more\nconvenient to use in shell scripts.\n\nFor these reasons, we enforced forward slashes in `interpolate_path()`\nin 5ca6b7bb47b (config --show-origin: report paths with forward slashes,\n2016-03-23). However, other code paths may generate paths containing\nbackslashes. For example, `config --show-origin` prints the XDG config\npath with mixed slashes on Windows:\n\n$ git config --list --show-origin\nfile:C:/Program Files/Git/etc/gitconfig         system.foo=bar\nfile:\"C:\\\\Users\\\\delilah/.config/git/config\"    xdg.foo=bar\nfile:C:/Users/delilah/.gitconfig                home.foo=bar\nfile:.git/config                                local.foo=bar\n\nLet's enforce forward slashes in all code paths that directly or\nindirectly call `cleanup_path()` by modifying it to use\n`convert_slashes()` on Windows. Since `convert_slashes()` modifies the\npath in-place, change the argument and return type of `cleanup_path()`\nfrom `const char *` to `char *`. All existing callers of\n`cleanup_path()` pass `char *` anyways, so this change is compatible.\n\nThe next patch, config: test home and xdg files in `list --global`, will\nassert that the XDG config path uses forward slashes.\n\nSuggested-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Delilah Ashley Wu <delilahwu@microsoft.com>\nReviewed-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n path.c | 10 +++++++---\n 1 file changed, 7 insertions(+), 3 deletions(-)\n\ndiff --git a/path.c b/path.c\nindex 7f56eaf993..db7b94fcda 100644\n--- a/path.c\n+++ b/path.c\n@@ -40,13 +40,17 @@ static struct strbuf *get_pathname(void)\n \treturn sb;\n }\n \n-static const char *cleanup_path(const char *path)\n+static char *cleanup_path(char *path)\n {\n \t/* Clean it up */\n-\tif (skip_prefix(path, \"./\", &path)) {\n+\tif (skip_prefix(path, \"./\", (const char **)&path))\n \t\twhile (*path == '/')\n \t\t\tpath++;\n-\t}\n+\n+#ifdef GIT_WINDOWS_NATIVE\n+\tconvert_slashes(path);\n+#endif\n+\n \treturn path;\n }\n \n-- \ngitgitgadget\n\n"},{"id":"528446","messageId":"d2167a81d31defddbcdda06726b004e44a192f8d.1760058849.git.gitgitgadget@gmail.com","threadId":"64290","inReplyTo":"pull.1938.git.1760058849.gitgitgadget@gmail.com","subject":"[PATCH/RFC 2/4] config: test home and xdg files in `list --global`","fromName":"Delilah Ashley Wu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-10-10T01:14:07Z","receivedAt":"2025-10-10T01:14:13Z","isPatch":true,"body":"From: Delilah Ashley Wu <delilahwu@microsoft.com>\n\nThe `git config list --global` output includes `$HOME/.gitconfig` (home\nconfig), but ignores `$XDG_CONFIG_HOME/git/config` (XDG config). It\nshould include both files.\n\nModify tests to check the following and expect a failure:\n  - `git config list --global` should include contents from both the\n     home and XDG config locations (assuming they are readable), not\n     just the former.\n\n  - `--show-origin` should print correct paths to both config files,\n    assuming they exist.\n\nAlso, add tests to ensure subsequent patches do not introduce\nregressions to `git config list`. Specifically, check that:\n  - The home config should take precedence over the XDG config.\n\n  - Without `--global`, it should not bail on unreadable/non-existent\n    global config files.\n\n  - With `--global`, it should bail when both `$HOME/.gitconfig` and\n    `$XDG_CONFIG_HOME/git/config` are unreadable. It should not bail if\n    at least one of them is readable.\n\nThe next patch, config: read global scope via config_sequence, will\nimplement a fix to include both config files when `--global` is\nspecified.\n\nReported-by: Jade Lovelace <lists@jade.fyi>\nHelped-by: Derrick Stolee <stolee@gmail.com>\nSigned-off-by: Delilah Ashley Wu <delilahwu@microsoft.com>\nReviewed-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t1300-config.sh    | 65 ++++++++++++++++++++++++++++++++++++++++++++\n t/t1306-xdg-files.sh |  5 ++--\n 2 files changed, 68 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex f856821839..5fa0111bd9 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2367,6 +2367,71 @@ test_expect_success '--show-scope with --default' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'list with nonexistent global config' '\n+\trm -rf \"$HOME\"/.gitconfig \"$HOME\"/.config/git/config &&\n+\tgit config ${mode_prefix}list --show-scope\n+'\n+\n+test_expect_success 'list --global with nonexistent global config' '\n+\trm -rf \"$HOME\"/.gitconfig \"$HOME\"/.config/git/config &&\n+\ttest_must_fail git config ${mode_prefix}list --global --show-scope\n+'\n+\n+test_expect_success 'list --global with only home' '\n+\trm -rf \"$HOME\"/.config/git/config &&\n+\n+\ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n+\tcat >\"$HOME\"/.gitconfig <<-EOF &&\n+\t[home]\n+\t\tconfig = true\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tglobal\thome.config=true\n+\tEOF\n+\tgit config ${mode_prefix}list --global --show-scope >output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'list --global with only xdg' '\n+\trm -f \"$HOME\"/.gitconfig &&\n+\n+\ttest_when_finished rm -rf \\\"\\$HOME\\\"/.config/git &&\n+\tmkdir -p \"$HOME\"/.config/git &&\n+\tcat >\"$HOME\"/.config/git/config <<-EOF &&\n+\t[xdg]\n+\t\tconfig = true\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tglobal\txdg.config=true\n+\tEOF\n+\tgit config ${mode_prefix}list --global --show-scope >output &&\n+\ttest_cmp expect output\n+'\n+\n+test_expect_success 'list --global with both home and xdg' '\n+\ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n+\tcat >\"$HOME\"/.gitconfig <<-EOF &&\n+\t[home]\n+\t\tconfig = true\n+\tEOF\n+\n+\ttest_when_finished rm -rf \\\"\\$HOME\\\"/.config/git &&\n+\tmkdir -p \"$HOME\"/.config/git &&\n+\tcat >\"$HOME\"/.config/git/config <<-EOF &&\n+\t[xdg]\n+\t\tconfig = true\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tglobal\tfile:$HOME/.config/git/config\txdg.config=true\n+\tglobal\tfile:$HOME/.gitconfig\thome.config=true\n+\tEOF\n+\tgit config ${mode_prefix}list --global --show-scope --show-origin >output &&\n+\t! test_cmp expect output\n+'\n+\n test_expect_success 'override global and system config' '\n \ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n \tcat >\"$HOME\"/.gitconfig <<-EOF &&\ndiff --git a/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh\nindex 40d3c42618..0318755799 100755\n--- a/t/t1306-xdg-files.sh\n+++ b/t/t1306-xdg-files.sh\n@@ -68,9 +68,10 @@ test_expect_success 'read with --list: xdg file exists and ~/.gitconfig exists'\n \t>.gitconfig &&\n \techo \"[user]\" >.gitconfig &&\n \techo \"\tname = read_gitconfig\" >>.gitconfig &&\n-\techo user.name=read_gitconfig >expected &&\n+\techo user.name=read_config >expected &&\n+\techo user.name=read_gitconfig >>expected &&\n \tgit config --global --list >actual &&\n-\ttest_cmp expected actual\n+\t! test_cmp expected actual\n '\n \n \n-- \ngitgitgadget\n\n"},{"id":"528447","messageId":"9d8af4e6164002b8096fc03fa8189a670133bc77.1760058849.git.gitgitgadget@gmail.com","threadId":"64290","inReplyTo":"pull.1938.git.1760058849.gitgitgadget@gmail.com","subject":"[PATCH/RFC 3/4] config: read global scope via config_sequence","fromName":"Delilah Ashley Wu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-10-10T01:14:08Z","receivedAt":"2025-10-10T01:14:14Z","isPatch":true,"body":"From: Delilah Ashley Wu <delilahwu@microsoft.com>\n\nThe output of `git config list --global` should include both the home\n(`$HOME/.gitconfig`) and XDG (`$XDG_CONFIG_HOME/git/config`) configs,\nbut it only reads from the former.\n\nWe assumed each config scope corresponds to a single config file. Under\nthis assumption, `git config list --global` reads the global config by\ncalling `git_config_from_file_with_options(...,\"~/.gitconfig\", ...)`.\nThis function usage restricts us to a single config file. Because the\nglobal scope includes two files, we should read the configs via another\nmethod.\n\nThe output of `git config list --show-scope --show-origin` (without\n`--global`) correctly includes both the home and XDG config files. So\nthere's existing code that respects both locations, namely the\n`do_git_config_sequence()` function which reads from all scopes.\nIntroduce flags to make it possible to ignore all but the global scope\n(i.e. ignore system, local, worktree, and cmdline). Then, reuse the\nfunction to read only the global scope when `--global` is specified.\nThis was the suggested solution in the bug report:\nhttps://lore.kernel.org/git/kl6ly1oze7wb.fsf@chooglen-macbookpro.roam.corp.google.com.\n\nThen, modify the tests to check that `git config list --global` includes\nboth home and XDG configs.\n\nThis patch introduces a regression. If both global config files are\nunreadable, then `git config list --global` should exit non-zero. This\nis no longer the case, so mark the corresponding test as a \"TODO known\nbreakage\" and address the issue in the next patch, config: keep bailing\non unreadable global files.\n\nImplementation notes:\n  1. The `ignore_global` flag is not set anywhere, so the\n     `if (!opts->ignore_global)` condition is always met. We can remove\n     this flag if desired.\n\n  2. I've assumed that `config_source->scope == CONFIG_SCOPE_GLOBAL` iff\n     `--global` is specified. This comparison determines whether to call\n     `do_git_config_sequence()` for the global scope, or to keep calling\n     `git_config_from_file_with_options()` for other scopes.\n\n  3. Keep populating `opts->source.file` in `builtin/config.c` because\n     it is used as the destination config file for write operations.\n     The proposed changes could convolute the code because there is no\n     single source of truth for the config file locations in the global\n     scope. Add a comment to help clarify this. Please let me know if\n     it's unclear.\n\nReported-by: Jade Lovelace <lists@jade.fyi>\nSuggested-by: Glen Choo <glencbz@gmail.com>\nHelped-by: Derrick Stolee <stolee@gmail.com>\nSigned-off-by: Delilah Ashley Wu <delilahwu@microsoft.com>\nReviewed-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/config.c     | 12 ++++++++++++\n config.c             | 26 +++++++++++++++-----------\n config.h             |  2 ++\n t/t1300-config.sh    |  6 +++---\n t/t1306-xdg-files.sh |  2 +-\n 5 files changed, 33 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 59fb113b07..3fd1bd7f8d 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -768,6 +768,18 @@ static void location_options_init(struct config_location_options *opts,\n \t}\n \n \tif (opts->use_global_config) {\n+\t\t/*\n+\t\t * Since global config is sourced from more than one location,\n+\t\t * use `config.c#do_git_config_sequence()` with `opts->options`\n+\t\t * to read it. However, writing global config should point to a\n+\t\t * single destination, set in `opts->source.file`.\n+\t\t */\n+\t\topts->options.ignore_repo = 1;\n+\t\topts->options.ignore_cmdline= 1;\n+\t\topts->options.ignore_worktree = 1;\n+\t\topts->options.ignore_system = 1;\n+\t\topts->source.scope = CONFIG_SCOPE_GLOBAL;\n+\n \t\topts->source.file = opts->file_to_free = git_global_config();\n \t\tif (!opts->source.file)\n \t\t\t/*\ndiff --git a/config.c b/config.c\nindex 74bf76a97e..4b9f3831b1 100644\n--- a/config.c\n+++ b/config.c\n@@ -1526,22 +1526,27 @@ static int do_git_config_sequence(const struct config_options *opts,\n \t\tworktree_config = NULL;\n \t}\n \n-\tif (git_config_system() && system_config &&\n+\tif (!opts->ignore_system && git_config_system() && system_config &&\n \t    !access_or_die(system_config, R_OK,\n \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n \t\tret += git_config_from_file_with_options(fn, system_config,\n \t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n \t\t\t\t\t\t\t NULL);\n \n-\tgit_global_config_paths(&user_config, &xdg_config);\n+\tif (!opts->ignore_global) {\n+\t\tgit_global_config_paths(&user_config, &xdg_config);\n+\n+\t\tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n+\t\t\tret += git_config_from_file_with_options(fn, xdg_config, data,\n+\t\t\t\t\t\tCONFIG_SCOPE_GLOBAL, NULL);\n \n-\tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n-\t\tret += git_config_from_file_with_options(fn, xdg_config, data,\n-\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n+\t\tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n+\t\t\tret += git_config_from_file_with_options(fn, user_config, data,\n+\t\t\t\t\t\tCONFIG_SCOPE_GLOBAL, NULL);\n \n-\tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n-\t\tret += git_config_from_file_with_options(fn, user_config, data,\n-\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n+\t\tfree(xdg_config);\n+\t\tfree(user_config);\n+\t}\n \n \tif (!opts->ignore_repo && repo_config &&\n \t    !access_or_die(repo_config, R_OK, 0))\n@@ -1560,8 +1565,6 @@ static int do_git_config_sequence(const struct config_options *opts,\n \t\tdie(_(\"unable to parse command-line config\"));\n \n \tfree(system_config);\n-\tfree(xdg_config);\n-\tfree(user_config);\n \tfree(repo_config);\n \tfree(worktree_config);\n \treturn ret;\n@@ -1591,7 +1594,8 @@ int config_with_options(config_fn_t fn, void *data,\n \t */\n \tif (config_source && config_source->use_stdin) {\n \t\tret = git_config_from_stdin(fn, data, config_source->scope);\n-\t} else if (config_source && config_source->file) {\n+\t} else if (config_source && config_source->file &&\n+\t\t   config_source->scope != CONFIG_SCOPE_GLOBAL) {\n \t\tret = git_config_from_file_with_options(fn, config_source->file,\n \t\t\t\t\t\t\tdata, config_source->scope,\n \t\t\t\t\t\t\tNULL);\ndiff --git a/config.h b/config.h\nindex 19c87fc0bc..9425fe115d 100644\n--- a/config.h\n+++ b/config.h\n@@ -87,6 +87,8 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n \n struct config_options {\n \tunsigned int respect_includes : 1;\n+\tunsigned int ignore_system : 1;\n+\tunsigned int ignore_global : 1;\n \tunsigned int ignore_repo : 1;\n \tunsigned int ignore_worktree : 1;\n \tunsigned int ignore_cmdline : 1;\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 5fa0111bd9..42f256e122 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2372,7 +2372,7 @@ test_expect_success 'list with nonexistent global config' '\n \tgit config ${mode_prefix}list --show-scope\n '\n \n-test_expect_success 'list --global with nonexistent global config' '\n+test_expect_failure 'list --global with nonexistent global config' '\n \trm -rf \"$HOME\"/.gitconfig \"$HOME\"/.config/git/config &&\n \ttest_must_fail git config ${mode_prefix}list --global --show-scope\n '\n@@ -2429,7 +2429,7 @@ test_expect_success 'list --global with both home and xdg' '\n \tglobal\tfile:$HOME/.gitconfig\thome.config=true\n \tEOF\n \tgit config ${mode_prefix}list --global --show-scope --show-origin >output &&\n-\t! test_cmp expect output\n+\ttest_cmp expect output\n '\n \n test_expect_success 'override global and system config' '\n@@ -2483,7 +2483,7 @@ test_expect_success 'override global and system config' '\n \ttest_cmp expect output\n '\n \n-test_expect_success 'override global and system config with missing file' '\n+test_expect_failure 'override global and system config with missing file' '\n \ttest_must_fail env GIT_CONFIG_GLOBAL=does-not-exist GIT_CONFIG_SYSTEM=/dev/null git config ${mode_prefix}list --global &&\n \ttest_must_fail env GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=does-not-exist git config ${mode_prefix}list --system &&\n \tGIT_CONFIG_GLOBAL=does-not-exist GIT_CONFIG_SYSTEM=does-not-exist git version\ndiff --git a/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh\nindex 0318755799..475bd26aba 100755\n--- a/t/t1306-xdg-files.sh\n+++ b/t/t1306-xdg-files.sh\n@@ -71,7 +71,7 @@ test_expect_success 'read with --list: xdg file exists and ~/.gitconfig exists'\n \techo user.name=read_config >expected &&\n \techo user.name=read_gitconfig >>expected &&\n \tgit config --global --list >actual &&\n-\t! test_cmp expected actual\n+\ttest_cmp expected actual\n '\n \n \n-- \ngitgitgadget\n\n"},{"id":"528448","messageId":"6119cee0c6557e67f3eb4e2f9d488e8684a63c99.1760058849.git.gitgitgadget@gmail.com","threadId":"64290","inReplyTo":"pull.1938.git.1760058849.gitgitgadget@gmail.com","subject":"[PATCH/RFC 4/4] config: keep bailing on unreadable global files","fromName":"Delilah Ashley Wu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-10-10T01:14:09Z","receivedAt":"2025-10-10T01:14:16Z","isPatch":true,"body":"From: Delilah Ashley Wu <delilahwu@microsoft.com>\n\nThe expected behaviour for `git config list` is:\n  A. Without `--global`, it should not bail on unreadable/non-existent\n     global config files.\n\n  B. With `--global`, it should bail when both `$HOME/.gitconfig` and\n     `$XDG_CONFIG_HOME/git/config` are unreadable. It should not bail\n     when one or more of them is readable.\n\nThe previous patch, config: read global scope via config_sequence,\nintroduced a regression in scenario B. When both global config files are\nunreadable, running `git config list --global` would not fail. For\nexample, `GIT_CONFIG_GLOBAL=does-not-exist git config list --global`\nexits with status code 0.\n\nAssuming that `config_source->scope == CONFIG_SCOPE_GLOBAL` iff the\n`--global` argument is specified, use this to determine whether to bail.\nWhen reading only the global scope and both config files are unreadable,\nthen adjust the return code to be non-zero.\n\nNote: When bailing, the exit code is not determined by sum of the return\ncodes of the underlying operations. Instead, the exit code is modified\nvia a single decrement. If this is undesirable, we can change it to sum\nthe return codes of the underlying operations instead.\n\nLastly, modify the tests to remove the known breakage/regression. The\ntests for scenario B will now pass.\n\nHelped-by: Derrick Stolee <stolee@gmail.com>\nSigned-off-by: Delilah Ashley Wu <delilahwu@microsoft.com>\nReviewed-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n config.c          | 40 +++++++++++++++++++++++++++++++---------\n t/t1300-config.sh |  4 ++--\n 2 files changed, 33 insertions(+), 11 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 4b9f3831b1..3057c16f59 100644\n--- a/config.c\n+++ b/config.c\n@@ -1500,8 +1500,8 @@ int git_config_system(void)\n }\n \n static int do_git_config_sequence(const struct config_options *opts,\n-\t\t\t\t  const struct repository *repo,\n-\t\t\t\t  config_fn_t fn, void *data)\n+\t\t\t\t  const struct repository *repo, config_fn_t fn,\n+\t\t\t\t  void *data, enum config_scope scope)\n {\n \tint ret = 0;\n \tchar *system_config = git_system_config();\n@@ -1534,15 +1534,34 @@ static int do_git_config_sequence(const struct config_options *opts,\n \t\t\t\t\t\t\t NULL);\n \n \tif (!opts->ignore_global) {\n+\t\tint global_config_success_count = 0;\n+\t\tint nonzero_ret_on_global_config_error = scope == CONFIG_SCOPE_GLOBAL;\n+\n \t\tgit_global_config_paths(&user_config, &xdg_config);\n \n-\t\tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n-\t\t\tret += git_config_from_file_with_options(fn, xdg_config, data,\n-\t\t\t\t\t\tCONFIG_SCOPE_GLOBAL, NULL);\n+\t\tif (xdg_config &&\n+\t\t    !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK)) {\n+\t\t\tret += git_config_from_file_with_options(fn, xdg_config,\n+\t\t\t\t\t\t\t\t data,\n+\t\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL,\n+\t\t\t\t\t\t\t\t NULL);\n+\t\t\tif (!ret)\n+\t\t\t\tglobal_config_success_count++;\n+\t\t}\n+\n+\t\tif (user_config &&\n+\t\t    !access_or_die(user_config, R_OK, ACCESS_EACCES_OK)) {\n+\t\t\tret += git_config_from_file_with_options(fn, user_config,\n+\t\t\t\t\t\t\t\t data,\n+\t\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL,\n+\t\t\t\t\t\t\t\t NULL);\n+\t\t\tif (!ret)\n+\t\t\t\tglobal_config_success_count++;\n+\t\t}\n \n-\t\tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n-\t\t\tret += git_config_from_file_with_options(fn, user_config, data,\n-\t\t\t\t\t\tCONFIG_SCOPE_GLOBAL, NULL);\n+\t\tif (nonzero_ret_on_global_config_error &&\n+\t\t    !global_config_success_count)\n+\t\t\t--ret;\n \n \t\tfree(xdg_config);\n \t\tfree(user_config);\n@@ -1603,7 +1622,10 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n \t\t\t\t\t       data, config_source->scope);\n \t} else {\n-\t\tret = do_git_config_sequence(opts, repo, fn, data);\n+\t\tret = do_git_config_sequence(opts, repo, fn, data,\n+\t\t\t\t\t     config_source ?\n+\t\t\t\t\t\t     config_source->scope :\n+\t\t\t\t\t\t     CONFIG_SCOPE_UNKNOWN);\n \t}\n \n \tif (inc.remote_urls) {\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 42f256e122..0c3911183c 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2372,7 +2372,7 @@ test_expect_success 'list with nonexistent global config' '\n \tgit config ${mode_prefix}list --show-scope\n '\n \n-test_expect_failure 'list --global with nonexistent global config' '\n+test_expect_success 'list --global with nonexistent global config' '\n \trm -rf \"$HOME\"/.gitconfig \"$HOME\"/.config/git/config &&\n \ttest_must_fail git config ${mode_prefix}list --global --show-scope\n '\n@@ -2483,7 +2483,7 @@ test_expect_success 'override global and system config' '\n \ttest_cmp expect output\n '\n \n-test_expect_failure 'override global and system config with missing file' '\n+test_expect_success 'override global and system config with missing file' '\n \ttest_must_fail env GIT_CONFIG_GLOBAL=does-not-exist GIT_CONFIG_SYSTEM=/dev/null git config ${mode_prefix}list --global &&\n \ttest_must_fail env GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=does-not-exist git config ${mode_prefix}list --system &&\n \tGIT_CONFIG_GLOBAL=does-not-exist GIT_CONFIG_SYSTEM=does-not-exist git version\n-- \ngitgitgadget\n"},{"id":"528450","messageId":"46aa7034-b186-4adc-ad0f-8c7a4d799687@app.fastmail.com","threadId":"64290","inReplyTo":"pull.1938.git.1760058849.gitgitgadget@gmail.com","subject":"Re: [PATCH/RFC 0/4] config: read both home and xdg files for --global","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2025-10-10T01:27:24Z","receivedAt":"2025-10-10T01:27:51Z","isPatch":true,"body":"On Fri, Oct 10, 2025, at 03:14, Delilah Ashley Wu via GitGitGadget wrote:\n> As reported in [1]: `$HOME/.gitconfig` and `$XDG_CONFIG_HOME/git/config` are\n> both valid global config locations, but `git config list --global` only\n> includes the former in its output.\n\nNote only if both files exist.\n\n-- \nKristoffer Haugsbakk\n\n\n"},{"id":"530801","messageId":"8e2b868d-8753-bbcd-840c-8eaefaa7cbd1@gmx.de","threadId":"64290","inReplyTo":"pull.1938.git.1760058849.gitgitgadget@gmail.com","subject":"Re: [PATCH/RFC 0/4] config: read both home and xdg files for --global","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2025-11-17T13:29:33Z","receivedAt":"2025-11-17T13:29:43Z","isPatch":true,"body":"Hi,\n\nOn Fri, 10 Oct 2025, Delilah Ashley Wu via GitGitGadget wrote:\n\n> As reported in [1]: `$HOME/.gitconfig` and `$XDG_CONFIG_HOME/git/config` are\n> both valid global config locations, but `git config list --global` only\n> includes the former in its output.\n> \n> Suppose we have this config in `$HOME/.gitconfig`:\n> \n> [home]\n>     config = true\n> \n> \n> And this config in `$XDG_CONFIG_HOME/git/config`:\n> \n> [xdg]\n>     config = true\n> \n> \n> Then, to reproduce the issue that `--global` only shows the home config:\n> \n> $ git config list --global --show-scope --show-origin\n> global  file:/Users/delilah/.gitconfig    home.config=true\n> \n> \n> Git correctly applies the XDG config in its effective configuration, but it\n> doesn't show up when `--global` is specified. We can confirm this by\n> checking the output without the `--global` flag:\n> \n> $ git config list --show-scope --show-origin\n> global  file:/Users/delilah/.config/git/config    xdg.config=true\n> global  file:/Users/delilah/.gitconfig            home.config=true\n> \n> \n> The expected behaviour is both configs should be shown when `--global` is\n> specified, so we'd expect its output to look the same as above. This was\n> confirmed in [2], which quoted the `git config` documentation:\n> \n> > OPTIONS\n> >     --global::\n> >         For writing options: write to global `~/.gitconfig` file\n> >         rather than the repository `.git/config`, write to\n> >         `$XDG_CONFIG_HOME/git/config` file if this file exists and the\n> >         `~/.gitconfig` file doesn't.\n> >\n> >         For reading options: read only from global `~/.gitconfig` and from\n> >         `$XDG_CONFIG_HOME/git/config` rather than from all available files.\n> \n> \n> The first patch fixes forward slash normalisation on Windows paths. The\n> second patch introduces tests and regression checks. The third and fourth\n> patches implement the fix to include both config files when `--global` is\n> specified. Johannes has kindly pre-reviewed this patch series via GitHub on\n> GitGitGadget #1938 [3]. You'll notice some force-pushes after the review,\n> but I only changed commit messages.\n> \n> [1]:\n> https://lore.kernel.org/git/CAFA9we-QLQRzJdGMMCPatmfrk1oHeiUu9msMRXXk1MLE5HRxBQ@mail.gmail.com/\n> [2]: https://lore.kernel.org/git/xmqqmt5lezi3.fsf@gitster.g/\n> [3]: https://github.com/gitgitgadget/git/pull/1938/\n> \n> Thank you all for your time!\n\nFor the record, my \"Reviewed-by:\" still stands, if lack of reviews should\nbe the reason why this patch series has not even entered the `seen`\nbranch.\n\nCiao,\nJohannes\n"},{"id":"530865","messageId":"xmqqtsysfakt.fsf@gitster.g","threadId":"64290","inReplyTo":"8e2b868d-8753-bbcd-840c-8eaefaa7cbd1@gmx.de","subject":"Re: [PATCH/RFC 0/4] config: read both home and xdg files for --global","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-18T00:28:34Z","receivedAt":"2025-11-18T00:28:38Z","isPatch":true,"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> For the record, my \"Reviewed-by:\" still stands, if lack of reviews should\n> be the reason why this patch series has not even entered the `seen`\n> branch.\n\nThanks for pinging.\n\n\"Why is it not in 'next'\" is a legitimate question.  I think that is\nbecause the topic has no discussion on the list in the thread.\n\n\"Why is it in 'seen'\" is a question with no answer.  As I often say,\n'seen' is merely what I happened to have seen and found it promising\nbut is not ready for 'next', and people should not read anything\nmore into it.\n\nI didn't look at it primarily because nobody, not even one on a\nhandful of experienced contributors whose opinions are well regarded\nin the community on the CC: list, responded to the thread at all.\nBefore the message I am responding to, that is ;-)\n\nI wanted to see how well people receive the motivation behind the\nproposed change, as I vaguely recalled that not using both at the\nsame time was deliberate to help those who migrate from historical\nlocation to XDG layout, but did not have time and energy to do the\ndigging myself to become knowledgeable again to give any comment\nworth reading.\n"},{"id":"530991","messageId":"xmqqbjkyccae.fsf@gitster.g","threadId":"64290","inReplyTo":"pull.1938.git.1760058849.gitgitgadget@gmail.com","subject":"Re: [PATCH/RFC 0/4] config: read both home and xdg files for --global","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-19T14:44:25Z","receivedAt":"2025-11-19T14:44:28Z","isPatch":true,"body":"\"Delilah Ashley Wu via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> As reported in [1]: `$HOME/.gitconfig` and `$XDG_CONFIG_HOME/git/config` are\n> both valid global config locations, but `git config list --global` only\n> includes the former in its output.\n\n... while \"git config list\" includes both, which is an inconsistency\nwithout good reason.\n\nThanks for addressing this issue.  I haven't had a chance to look at\nthese patches yet, but both analysis and Glen's outline for the best\napproach presented in the thread [1] do look very sensible.\n\nWhat is the reason behind [RFC] in the title?  Are there things that\nare iffy yourself in the patches that reviewers want to pay special\nattention to?\n\nThanks.\n\n"},{"id":"530999","messageId":"xmqq7bvldidv.fsf@gitster.g","threadId":"64290","inReplyTo":"c8df6a042b9e971f392b2fd2d09a9c3c655dbceb.1760058849.git.gitgitgadget@gmail.com","subject":"Re: [PATCH/RFC 1/4] cleanup_path: force forward slashes on Windows","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-19T17:47:24Z","receivedAt":"2025-11-19T17:47:28Z","isPatch":true,"body":"\"Delilah Ashley Wu via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> All existing callers of\n> `cleanup_path()` pass `char *` anyways, so this change is compatible.\n\nNot just compatible ;-).  If there is a caller that wants\ncleanup_path() not to munge what it passes, this change will\nintroduce a bug for them.  Have you made sure that none of these\ncallers mind that backslashes are converted into forward slashes?\n\n> The next patch, config: test home and xdg files in `list --global`, will\n> assert that the XDG config path uses forward slashes.\n\nThe path to the leaf-level blobs is always slash separated in the\nindex, a tree object sorts an entry that points at a subtree as if\nits path component has terminating slash, etc., and only when these\npaths are externalized, they are converted to filesystem dependent\nhierarchy separator (by system call like creat(2) even on platforms\nlike Windows whose filesystem uses backslashes as the pathname\nseparator).  Canonicalizing end-user supplied path early at a\ncentral place does make sense.\n\n> -static const char *cleanup_path(const char *path)\n> +static char *cleanup_path(char *path)\n>  {\n>  \t/* Clean it up */\n> -\tif (skip_prefix(path, \"./\", &path)) {\n> +\tif (skip_prefix(path, \"./\", (const char **)&path))\n>  \t\twhile (*path == '/')\n>  \t\t\tpath++;\n> -\t}\n\nHmph, the need for cast is a bit annoying, but more importantly, why\ndon't we have to worry about leading \".\\\\\\\\\" instead of \".////\"?\nShouldn't we be stripping backslashes the same way on Windows?\n\n> +#ifdef GIT_WINDOWS_NATIVE\n> +\tconvert_slashes(path);\n> +#endif\n\nIn other words, why do it here, not _before_ the loop that says \"If\nthe path begins with dot (i.e. the thing is relative to the current\ndirectory) followed by a directory separator, remove it together\nwith any extra directory separators that come immediately after it\"?\n\n>  \treturn path;\n>  }\n"},{"id":"531000","messageId":"xmqqsee9c1v1.fsf@gitster.g","threadId":"64290","inReplyTo":"d2167a81d31defddbcdda06726b004e44a192f8d.1760058849.git.gitgitgadget@gmail.com","subject":"Re: [PATCH/RFC 2/4] config: test home and xdg files in `list --global`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-19T18:29:38Z","receivedAt":"2025-11-19T18:29:44Z","isPatch":true,"body":"\"Delilah Ashley Wu via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Delilah Ashley Wu <delilahwu@microsoft.com>\n>\n> The `git config list --global` output includes `$HOME/.gitconfig` (home\n> config), but ignores `$XDG_CONFIG_HOME/git/config` (XDG config). It\n> should include both files.\n\nPlease be gentle to future readers of \"git log\" and help them with a\nbit more explanation on the \"should\" here.  E.g., \n\n    should include both files, to be consistent with the output from\n    `git config list` (not limited to `--global`) that lists entries\n    from both files (in addition to system-wide and repository-specific\n    entries, of course).\n\nor something.\n\n> Modify tests to check the following and expect a failure:\n>   - `git config list --global` should include contents from both the\n>      home and XDG config locations (assuming they are readable), not\n>      just the former.\n>\n>   - `--show-origin` should print correct paths to both config files,\n>     assuming they exist.\n\nTesting these two combinations is a good thing, but \"expect a\nfailure\"?  There doesn't seem to be any test that is marked as\n\"test_expect_failure\" in this patch.  Confused?\n\n    Side note: we generally do not want test_expect_failure tests in\n    one patch, followed by a code fix with changes to tests that\n    flip s/test_expect_failure/test_expect_success/' in another\n    patch, though.  The reason is primarily that such a two-patch\n    series makes it harder to review the step that has the fix, by\n    hiding the body of the test whose earlier failure gets fixed by\n    the code change.\n\n> Also, add tests to ensure subsequent patches do not introduce\n> regressions to `git config list`. Specifically, check that:\n>   - The home config should take precedence over the XDG config.\n>\n>   - Without `--global`, it should not bail on unreadable/non-existent\n>     global config files.\n>\n>   - With `--global`, it should bail when both `$HOME/.gitconfig` and\n>     `$XDG_CONFIG_HOME/git/config` are unreadable. It should not bail if\n>     at least one of them is readable.\n\nGood.\n\n> The next patch, config: read global scope via config_sequence, will\n> implement a fix to include both config files when `--global` is\n> specified.\n>\n> Reported-by: Jade Lovelace <lists@jade.fyi>\n> Helped-by: Derrick Stolee <stolee@gmail.com>\n> Signed-off-by: Delilah Ashley Wu <delilahwu@microsoft.com>\n> Reviewed-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  t/t1300-config.sh    | 65 ++++++++++++++++++++++++++++++++++++++++++++\n>  t/t1306-xdg-files.sh |  5 ++--\n>  2 files changed, 68 insertions(+), 2 deletions(-)\n>\n> diff --git a/t/t1300-config.sh b/t/t1300-config.sh\n> index f856821839..5fa0111bd9 100755\n> --- a/t/t1300-config.sh\n> +++ b/t/t1300-config.sh\n> @@ -2367,6 +2367,71 @@ test_expect_success '--show-scope with --default' '\n>  \ttest_cmp expect actual\n>  '\n>  \n> +test_expect_success 'list with nonexistent global config' '\n> +\trm -rf \"$HOME\"/.gitconfig \"$HOME\"/.config/git/config &&\n> +\tgit config ${mode_prefix}list --show-scope\n> +'\n\nDo we expect an empty output, or are we happy as long as \"git\nconfig\" does not segfault, even if it spews anything?  I guess that\nat this late point in the test we have per-repository or system-wide\nconfiguration files with something in them to test, so there would\nbe some output but we do not care?  If that is the case, not\nchecking the output, like this patch does, is the right thing.\n\n> +test_expect_success 'list --global with nonexistent global config' '\n> +\trm -rf \"$HOME\"/.gitconfig \"$HOME\"/.config/git/config &&\n> +\ttest_must_fail git config ${mode_prefix}list --global --show-scope\n> +'\n\nOK.  Do we require --show-scope to fail this, or do we fail with and\nwithout --show-scope as long as --global is in effect?  If the latter,\ntest both ...\n\n\trm -f \"$HOME/.gitconfig\" \"$HOME/.config/git/config\" &&\n\ttest_must_fail git config ${mode_prefix}list --global &&\n\ttest_must_fail git config ${mode_prefix}list --global --show-scope\n\n... like this, perhaps?  Also, don't overuse '-r' with 'rm' (applies\nother tests in this patch) when you know what you are removing\nshould not be a directory.\n\n> +test_expect_success 'list --global with only home' '\n> +\trm -rf \"$HOME\"/.config/git/config &&\n\nLose \"r\" from \"-rf\" or lose \"/config\".\n\n> +\ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n> +\tcat >\"$HOME\"/.gitconfig <<-EOF &&\n> +\t[home]\n> +\t\tconfig = true\n> +\tEOF\n> +\n> +\tcat >expect <<-EOF &&\n> +\tglobal\thome.config=true\n> +\tEOF\n> +\tgit config ${mode_prefix}list --global --show-scope >output &&\n> +\ttest_cmp expect output\n> +'\n\nOK.\n\n> +test_expect_success 'list --global with only xdg' '\n> +\trm -f \"$HOME\"/.gitconfig &&\n> +\n> +\ttest_when_finished rm -rf \\\"\\$HOME\\\"/.config/git &&\n> +\tmkdir -p \"$HOME\"/.config/git &&\n> +\tcat >\"$HOME\"/.config/git/config <<-EOF &&\n> +\t[xdg]\n> +\t\tconfig = true\n> +\tEOF\n> +\n> +\tcat >expect <<-EOF &&\n> +\tglobal\txdg.config=true\n> +\tEOF\n> +\tgit config ${mode_prefix}list --global --show-scope >output &&\n> +\ttest_cmp expect output\n> +'\n\nOK.\n\n> +test_expect_success 'list --global with both home and xdg' '\n> +\ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n> +\tcat >\"$HOME\"/.gitconfig <<-EOF &&\n> +\t[home]\n> +\t\tconfig = true\n> +\tEOF\n> +\n> +\ttest_when_finished rm -rf \\\"\\$HOME\\\"/.config/git &&\n> +\tmkdir -p \"$HOME\"/.config/git &&\n> +\tcat >\"$HOME\"/.config/git/config <<-EOF &&\n> +\t[xdg]\n> +\t\tconfig = true\n> +\tEOF\n> +\n> +\tcat >expect <<-EOF &&\n> +\tglobal\tfile:$HOME/.config/git/config\txdg.config=true\n> +\tglobal\tfile:$HOME/.gitconfig\thome.config=true\n> +\tEOF\n> +\tgit config ${mode_prefix}list --global --show-scope --show-origin >output &&\n> +\t! test_cmp expect output\n> +'\n\nDo not write a test this way.  If you want to document an existing\nand unfixed breakage, instead of saying \"we do want to see what is\nin this expect file, but we know output does not unfortunately match\nit\", which is how the above test expresses it, start the whole thing\nwith \"test_expect_failure\" (instead of \"test_expect_success\"), and\nhave the body of the test express what you really want to see.  I.e.\nthe last steps should say\n\n\tgit config ${mode_prefix}list --global --show-scope --show-origin >actual &&\n\ttest_cmp expect actual\n\nBut an earier side note applies.  If \"git config list --global\" gets\ncorrected, this test will see update to turn \"! test_cmp\" into\n\"test_cmp\" (or \"test_expect_success\" to \"test_expect_failure\"), and\nsuch a patch that comes with the code fix will not show what is\nbeing tested and forcing the reviewer to go back to the previous\nstep to see what the change is really about.  A test that\ndemonstrates and protects the behaviour corrected by the code change\nis best added in the same patch as the code change.\n\n> diff --git a/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh\n> index 40d3c42618..0318755799 100755\n> --- a/t/t1306-xdg-files.sh\n> +++ b/t/t1306-xdg-files.sh\n> @@ -68,9 +68,10 @@ test_expect_success 'read with --list: xdg file exists and ~/.gitconfig exists'\n>  \t>.gitconfig &&\n>  \techo \"[user]\" >.gitconfig &&\n>  \techo \"\tname = read_gitconfig\" >>.gitconfig &&\n> -\techo user.name=read_gitconfig >expected &&\n> +\techo user.name=read_config >expected &&\n> +\techo user.name=read_gitconfig >>expected &&\n>  \tgit config --global --list >actual &&\n> -\ttest_cmp expected actual\n> +\t! test_cmp expected actual\n>  '\n\nI cannot quite tell from only half the test, but I suspect that this\nshares exactly the same problem with the last one in the other file\nI commented above?\n\nThanks.\n"},{"id":"531001","messageId":"xmqqms4hc1ek.fsf@gitster.g","threadId":"64290","inReplyTo":"9d8af4e6164002b8096fc03fa8189a670133bc77.1760058849.git.gitgitgadget@gmail.com","subject":"Re: [PATCH/RFC 3/4] config: read global scope via config_sequence","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-19T18:39:31Z","receivedAt":"2025-11-19T18:39:34Z","isPatch":true,"body":"\"Delilah Ashley Wu via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Delilah Ashley Wu <delilahwu@microsoft.com>\n>\n> The output of `git config list --global` should include both the home\n> (`$HOME/.gitconfig`) and XDG (`$XDG_CONFIG_HOME/git/config`) configs,\n> but it only reads from the former.\n\n\", but\" -> \"to match the information given by the command without\n--global, but\".\n\n> This patch introduces a regression. If both global config files are\n> unreadable, then `git config list --global` should exit non-zero. This\n> is no longer the case, so mark the corresponding test as a \"TODO known\n> breakage\" and address the issue in the next patch, config: keep bailing\n> on unreadable global files.\n\nThat is rather unfortunate, as we do try hard to avoid deliberate\nregressions in our history.  The reason why this step cannot be done\nwithout first introducing a regression is...?\n\nIf the reason is \"it would make a single patch too big\", perhaps we\ncan do it in two steps, one preliminary \"git_config_sequence() learns\nan extra barf-if-no-input parameter that causes it to return error if\nno files in the specified sequence exists\" step, followed by this\nchange that starts using git_config_sequence() to handle \"--global\",\nwhich uses that new flag to ensure that there won't be a regression?\n\n>  \tif (opts->use_global_config) {\n> +\t\t/*\n> +\t\t * Since global config is sourced from more than one location,\n> +\t\t * use `config.c#do_git_config_sequence()` with `opts->options`\n> +\t\t * to read it. However, writing global config should point to a\n> +\t\t * single destination, set in `opts->source.file`.\n> +\t\t */\n> +\t\topts->options.ignore_repo = 1;\n> +\t\topts->options.ignore_cmdline= 1;\n> +\t\topts->options.ignore_worktree = 1;\n> +\t\topts->options.ignore_system = 1;\n> +\t\topts->source.scope = CONFIG_SCOPE_GLOBAL;\n\nVery nicely done.\n\nThanks.\n"},{"id":"531157","messageId":"20251122013659.GA3947@linuxonhyperv3.guj3yctzbm1etfxqx2vob5hsef.xx.internal.cloudapp.net","threadId":"64290","inReplyTo":"46aa7034-b186-4adc-ad0f-8c7a4d799687@app.fastmail.com","subject":"Re: [PATCH/RFC 0/4] config: read both home and xdg files for --global","fromName":"Delilah Ashley Wu","fromEmail":"delilahwu@linux.microsoft.com","sentAt":"2025-11-22T01:36:59Z","receivedAt":"2025-11-22T01:37:05Z","isPatch":true,"body":"On Fri, Oct 10, 2025 at 03:27:24AM +0200, Kristoffer Haugsbakk wrote:\n> On Fri, Oct 10, 2025, at 03:14, Delilah Ashley Wu via GitGitGadget wrote:\n> > As reported in [1]: `$HOME/.gitconfig` and `$XDG_CONFIG_HOME/git/config` are\n> > both valid global config locations, but `git config list --global` only\n> > includes the former in its output.\n> \n> Note only if both files exist.\n\nThanks for the clarification, I'll be sure to note this in my v2 cover\nletter and commit messages.\n\nDelilah =)\n"},{"id":"531158","messageId":"20251122020047.GB3947@linuxonhyperv3.guj3yctzbm1etfxqx2vob5hsef.xx.internal.cloudapp.net","threadId":"64290","inReplyTo":"xmqqbjkyccae.fsf@gitster.g","subject":"Re: [PATCH/RFC 0/4] config: read both home and xdg files for --global","fromName":"Delilah Ashley Wu","fromEmail":"delilahwu@linux.microsoft.com","sentAt":"2025-11-22T02:00:47Z","receivedAt":"2025-11-22T02:00:48Z","isPatch":true,"body":"On Wed, Nov 19, 2025 at 06:44:25AM -0800, Junio C Hamano wrote:\n> \"Delilah Ashley Wu via GitGitGadget\" <gitgitgadget@gmail.com>\n> writes:\n> \n> > As reported in [1]: `$HOME/.gitconfig` and `$XDG_CONFIG_HOME/git/config` are\n> > both valid global config locations, but `git config list --global` only\n> > includes the former in its output.\n> \n> ... while \"git config list\" includes both, which is an inconsistency\n> without good reason.\n\nGood point! Will add above to the v2 cover letter.\n\n> What is the reason behind [RFC] in the title?  Are there things that\n> are iffy yourself in the patches that reviewers want to pay special\n> attention to?\n\nThere wasn't any reason; I accidentally left the GitHub PR in draft\nmode when I submitted it. I'll drop the [RFC] in v2.\n\nAnd thanks for the review! You covered the points that I also felt\niffy about, e.g. introducing a regression in the middle of the patch\nseries. I'll address your feedback in v2.\n\nDelilah :)\n"},{"id":"534288","messageId":"xmqq1pjkyq25.fsf@gitster.g","threadId":"64290","inReplyTo":"20251122013659.GA3947@linuxonhyperv3.guj3yctzbm1etfxqx2vob5hsef.xx.internal.cloudapp.net","subject":"Re: [PATCH/RFC 0/4] config: read both home and xdg files for --global","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-20T20:41:54Z","receivedAt":"2026-01-20T20:41:58Z","isPatch":true,"body":"Delilah Ashley Wu <delilahwu@linux.microsoft.com> writes:\n\n> On Fri, Oct 10, 2025 at 03:27:24AM +0200, Kristoffer Haugsbakk wrote:\n>> On Fri, Oct 10, 2025, at 03:14, Delilah Ashley Wu via GitGitGadget wrote:\n>> > As reported in [1]: `$HOME/.gitconfig` and `$XDG_CONFIG_HOME/git/config` are\n>> > both valid global config locations, but `git config list --global` only\n>> > includes the former in its output.\n>> \n>> Note only if both files exist.\n>\n> Thanks for the clarification, I'll be sure to note this in my v2 cover\n> letter and commit messages.\n\nAfter this and [*] the discussion stopped and the topic has been\ndormant since then for full two months.  I'd drop the topic from\n'seen' soonish but that does not mean an improved version of this\npatch is unwelcome.\n\nThanks.\n\n\n[References]\n * https://lore.kernel.org/git/20251122020047.GB3947@linuxonhyperv3.guj3yctzbm1etfxqx2vob5hsef.xx.internal.cloudapp.net/\n"},{"id":"551060","messageId":"20260823-fix-config-list-global-home-and-xdg-v2-0-b29cc63f017b@microsoft.com","threadId":"64290","inReplyTo":"pull.1938.git.1760058849.gitgitgadget@gmail.com","subject":"[PATCH v2 0/3] config: read both home and xdg files for --global","fromName":"Delilah Ashley Wu","fromEmail":"delilahwu@linux.microsoft.com","sentAt":"2026-08-23T10:28:25Z","receivedAt":"2026-08-23T10:30:57Z","isPatch":true,"body":"Hi all, thanks for your patience. Here's my reroll.\n\nAs reported in [1], `$HOME/.gitconfig` and `$XDG_CONFIG_HOME/git/config`\nare both valid global configuration locations. However, when both files\nexist, `git config list --global` only reads from the former location\nwhereas `git config list` (without `--global`) reads from both. The same\nissue was reported for `git config get` in [2]. This inconsistency has\nno good justification and contradicts the documented behaviour.\n\nSuppose that `$HOME/.gitconfig` contains:\n    [home]\n        config = true\n\nand `$XDG_CONFIG_HOME/git/config` contains:\n    [xdg]\n        config = true\n\nThen, listing with `--global` shows only the home config:\n    $ git config list --global --show-scope --show-origin\n    global  file:/Users/delilah/.gitconfig    home.config=true\n\nand getting the XDG configuration entry with `--global` will fail:\n    $ git config get --global xdg.config; echo $?\n    1\n\nGit still reads the XDG config as part of its effective configuration,\nas shown by listing the configuration without `--global`:\n    $ git config list --show-scope --show-origin\n    global  file:/Users/delilah/.config/git/config    xdg.config=true\n    global  file:/Users/delilah/.gitconfig            home.config=true\n\nThe documentation, quoted in [1] and [2], states that `--global` should\nread from both files, so its output should be the same as above. Here's\nthe relevant excerpt:\n\n> OPTIONS\n>     --global::\n>         For writing options: write to global `~/.gitconfig` file\n>         rather than the repository `.git/config`, write to\n>         `$XDG_CONFIG_HOME/git/config` file if this file exists and the\n>         `~/.gitconfig` file doesn't.\n>\n>         For reading options: read only from global `~/.gitconfig` and from\n>         `$XDG_CONFIG_HOME/git/config` rather than from all available files.\n\nTo be consistent with the documentation and the behaviour without\n`--global`, we should read both configuration files when `--global` is\npassed. We do this in a few steps:\n\n - Patch 1 fixes slash normalisation on Windows paths. This is used for\n   `--show-origin` assertions in patch 3 tests.\n - Patch 2 modifies error handling when reading configuration files.\n   This is used to prevent a regression in patch 3.\n - Patch 3 reads both configuration files when `--global` is specified.\n\n[1]: https://lore.kernel.org/git/CAFA9we-QLQRzJdGMMCPatmfrk1oHeiUu9msMRXXk1MLE5HRxBQ@mail.gmail.com/\n[2]: https://lore.kernel.org/git/CAAdFe9yhBk-WecVzCTsjQ-4Z3AZAbpP+w+B076ouM3qX6d1WAg@mail.gmail.com/\n\nThanks again for your time!\nDelilah\n\n---\nChanges in v2:\n - Squash test-only patches into their corresponding implementation\n   patches.\n - Reorder patches to prevent a regression from being introduced and\n   then fixed in a later patch.\n - Narrow the scope of slash conversion to `xdg_config_home_for()` and\n   avoid modifying `cleanup_path()`, which could've broken callers that\n   do not expect normalised slashes.\n - Clarify that some tests only check the return code of a `git config`\n   command; we do not care about the output.\n - Link to v1: https://patch.msgid.link/pull.1938.git.1760058849.gitgitgadget@gmail.com/\n\n---\nDelilah Ashley Wu (3):\n      path: use forward slashes in XDG config on Windows\n      config: let sequence require a successful file\n      config: read global scope via config_sequence\n\n builtin/config.c     |  11 +++++\n config.c             |  76 +++++++++++++++++++++++-----------\n config.h             |   2 +\n path.c               |  16 ++++---\n t/t1300-config.sh    | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++\n t/t1306-xdg-files.sh |   5 ++-\n 6 files changed, 194 insertions(+), 31 deletions(-)\n\nRange-diff versus v1:\n\n1:  d9525d954e < -:  ---------- config: read both home and xdg files for --global\n2:  c24ed49bac < -:  ---------- cleanup_path: force forward slashes on Windows\n3:  51293ee827 < -:  ---------- config: test home and xdg files in `list --global`\n4:  26f3c46598 < -:  ---------- config: read global scope via config_sequence\n5:  b6ab7bfd67 < -:  ---------- config: keep bailing on unreadable global files\n-:  ---------- > 1:  2fa37d8aa7 path: use forward slashes in XDG config on Windows\n-:  ---------- > 2:  d90c9ae69f config: let sequence require a successful file\n-:  ---------- > 3:  a3b5599c8d config: read global scope via config_sequence\n\n---\nbase-commit: 2c78326f810173a4f3aefd8021f1e07575412481\nchange-id: 20260808-fix-config-list-global-home-and-xdg-9bcaac093a1b\n\n"},{"id":"551061","messageId":"20260823-fix-config-list-global-home-and-xdg-v2-2-b29cc63f017b@microsoft.com","threadId":"64290","inReplyTo":"20260823-fix-config-list-global-home-and-xdg-v2-0-b29cc63f017b@microsoft.com","subject":"[PATCH v2 2/3] config: let sequence require a successful file","fromName":"Delilah Ashley Wu","fromEmail":"delilahwu@linux.microsoft.com","sentAt":"2026-08-23T10:28:27Z","receivedAt":"2026-08-23T10:30:59Z","isPatch":true,"body":"From: Delilah Ashley Wu <delilahwu@microsoft.com>\n\nTeach `do_git_config_sequence()` to optionally report an error if no\nconfiguration files in the sequence were successfully processed. Gate\nthis new behaviour with a flag and keep it disabled for now.\n\nAdd tests to record existing behaviour and prevent regressions in the\nnext patch, \"config: read global scope via config_sequence\", which adds\na code path that enables the flag. When no global configuration file\nexists, `git config list` succeeds whereas `git config list --global`\nfails. The command output is irrelevant, so only check the exit code.\n\nSigned-off-by: Delilah Ashley Wu <delilahwu@microsoft.com>\n---\n config.c          | 57 ++++++++++++++++++++++++++++++++++++++-----------------\n t/t1300-config.sh | 12 ++++++++++++\n 2 files changed, 52 insertions(+), 17 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 1bdd702e7a..4c958f46bf 100644\n--- a/config.c\n+++ b/config.c\n@@ -1544,11 +1544,27 @@ int git_config_system(void)\n \treturn !git_env_bool(\"GIT_CONFIG_NOSYSTEM\", 0);\n }\n \n+static void attempt_git_config_from_file_with_options(config_fn_t fn,\n+\t\t\t\t\t\t      const char *filename,\n+\t\t\t\t\t\t      void *data,\n+\t\t\t\t\t\t      enum config_scope scope,\n+\t\t\t\t\t\t      const struct config_options *opts,\n+\t\t\t\t\t\t      int *success_count,\n+\t\t\t\t\t\t      int *cumulative_ret)\n+{\n+\tint ret = git_config_from_file_with_options(fn, filename, data,\n+\t\t\t\t\t\t    scope, opts);\n+\tif (!ret)\n+\t\t(*success_count)++;\n+\t*cumulative_ret += ret;\n+}\n+\n static int do_git_config_sequence(const struct config_options *opts,\n-\t\t\t\t  const struct repository *repo,\n-\t\t\t\t  config_fn_t fn, void *data)\n+\t\t\t\t  const struct repository *repo, config_fn_t fn,\n+\t\t\t\t  void *data, int require_successful_config)\n {\n \tint ret = 0;\n+\tint success_count = 0;\n \tchar *system_config = git_system_config();\n \tchar *xdg_config = NULL;\n \tchar *user_config = NULL;\n@@ -1574,32 +1590,35 @@ static int do_git_config_sequence(const struct config_options *opts,\n \tif (git_config_system() && system_config &&\n \t    !access_or_die(system_config, R_OK,\n \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n-\t\tret += git_config_from_file_with_options(fn, system_config,\n-\t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n-\t\t\t\t\t\t\t NULL);\n+\t\tattempt_git_config_from_file_with_options(fn, system_config, data,\n+\t\t\t\t\t\t\t  CONFIG_SCOPE_SYSTEM, NULL,\n+\t\t\t\t\t\t\t  &success_count, &ret);\n \n \tgit_global_config_paths(&user_config, &xdg_config);\n \n \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n-\t\tret += git_config_from_file_with_options(fn, xdg_config, data,\n-\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n+\t\tattempt_git_config_from_file_with_options(fn, xdg_config,\n+\t\t\t\t\t\t\t  data,\n+\t\t\t\t\t\t\t  CONFIG_SCOPE_GLOBAL,\n+\t\t\t\t\t\t\t  NULL, &success_count, &ret);\n \n \tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n-\t\tret += git_config_from_file_with_options(fn, user_config, data,\n-\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n+\t\tattempt_git_config_from_file_with_options(fn, user_config,\n+\t\t\t\t\t\t\t  data,\n+\t\t\t\t\t\t\t  CONFIG_SCOPE_GLOBAL,\n+\t\t\t\t\t\t\t  NULL, &success_count, &ret);\n \n \tif (!opts->ignore_repo && repo_config &&\n \t    !access_or_die(repo_config, R_OK, 0))\n-\t\tret += git_config_from_file_with_options(fn, repo_config, data,\n-\t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n+\t\tattempt_git_config_from_file_with_options(fn, repo_config, data,\n+\t\t\t\t\t\t\t  CONFIG_SCOPE_LOCAL, NULL, &success_count, &ret);\n \n \tif (!opts->ignore_worktree && worktree_config &&\n \t    repo && repo->repository_format_worktree_config &&\n-\t    !access_or_die(worktree_config, R_OK, 0)) {\n-\t\t\tret += git_config_from_file_with_options(fn, worktree_config, data,\n-\t\t\t\t\t\t\t\t CONFIG_SCOPE_WORKTREE,\n-\t\t\t\t\t\t\t\t NULL);\n-\t}\n+\t    !access_or_die(worktree_config, R_OK, 0))\n+\t\tattempt_git_config_from_file_with_options(fn, worktree_config, data,\n+\t\t\t\t\t\t\t  CONFIG_SCOPE_WORKTREE,\n+\t\t\t\t\t\t\t  NULL, &success_count, &ret);\n \n \tif (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)\n \t\tdie(_(\"unable to parse command-line config\"));\n@@ -1609,6 +1628,10 @@ static int do_git_config_sequence(const struct config_options *opts,\n \tfree(user_config);\n \tfree(repo_config);\n \tfree(worktree_config);\n+\n+\tif (require_successful_config && !success_count && !ret)\n+\t\tret = -1;\n+\n \treturn ret;\n }\n \n@@ -1644,7 +1667,7 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n \t\t\t\t\t       data, config_source->scope);\n \t} else {\n-\t\tret = do_git_config_sequence(opts, repo, fn, data);\n+\t\tret = do_git_config_sequence(opts, repo, fn, data, 0);\n \t}\n \n \tif (inc.remote_urls) {\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 329407a73d..2ce85b76ff 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2457,6 +2457,18 @@ test_expect_success '--show-scope with --default' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'list with nonexistent global config gracefully exits' '\n+\trm -f \"$HOME\"/.gitconfig \"$HOME\"/.config/git/config &&\n+\tgit config ${mode_prefix}list &&\n+\tgit config ${mode_prefix}list --show-scope\n+'\n+\n+test_expect_success 'list --global with nonexistent global config fails' '\n+\trm -f \"$HOME\"/.gitconfig \"$HOME\"/.config/git/config &&\n+\ttest_must_fail git config ${mode_prefix}list --global &&\n+\ttest_must_fail git config ${mode_prefix}list --global --show-scope\n+'\n+\n test_expect_success 'override global and system config' '\n \ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n \tcat >\"$HOME\"/.gitconfig <<-EOF &&\n\n-- \n2.54.0\n\n"},{"id":"551062","messageId":"20260823-fix-config-list-global-home-and-xdg-v2-1-b29cc63f017b@microsoft.com","threadId":"64290","inReplyTo":"20260823-fix-config-list-global-home-and-xdg-v2-0-b29cc63f017b@microsoft.com","subject":"[PATCH v2 1/3] path: use forward slashes in XDG config on Windows","fromName":"Delilah Ashley Wu","fromEmail":"delilahwu@linux.microsoft.com","sentAt":"2026-08-23T10:28:26Z","receivedAt":"2026-08-23T10:31:01Z","isPatch":true,"body":"From: Delilah Ashley Wu <delilahwu@microsoft.com>\n\nGit prefers forward slashes as directory separators across all\nplatforms. On Windows, the backslash is the native directory separator,\nbut all Windows versions supported by Git also accept the forward slash\nin all but rare circumstances. Our tests expect forward slashes. Git\ndisplays relative paths with forward slashes. Forward slashes are more\nconvenient to use in shell scripts.\n\nFor these reasons, we enforced forward slashes in `interpolate_path()`\nin 5ca6b7bb47b (config --show-origin: report paths with forward slashes,\n2016-03-23). However, other code paths may construct paths containing\nbackslashes. For example, `config --show-origin` prints the XDG config\npath with mixed slashes on Windows:\n\n    $ git config --list --show-origin\n    file:C:/Program Files/Git/etc/gitconfig         system.foo=bar\n    file:\"C:\\\\Users\\\\delilah/.config/git/config\"    xdg.foo=bar\n    file:C:/Users/delilah/.gitconfig                home.foo=bar\n    file:.git/config                                local.foo=bar\n\nThese mixed slashes occur because the `$HOME` and `$XDG_CONFIG_HOME`\nenvironment variables usually contain backslashes on Windows, and\n`xdg_config_home_for()` interpolates them into templates that use\nhardcoded forward slashes.\n\nSince callers of `xdg_config_home_for()` handle mixed slashes correctly,\nit is reasonable to assume that they can handle paths with only forward\nslashes. Let's enforce forward slashes in `xdg_config_home_for()` by\nusing `convert_slashes()` on Windows.\n\nAlso, there are no tests for the XDG path with `--show-origin`. Add a\ntest for slash conversion and a confidence check for the default path.\n\nSigned-off-by: Delilah Ashley Wu <delilahwu@microsoft.com>\n---\n path.c            | 16 ++++++++++------\n t/t1300-config.sh | 32 ++++++++++++++++++++++++++++++++\n 2 files changed, 42 insertions(+), 6 deletions(-)\n\ndiff --git a/path.c b/path.c\nindex c3a709a928..f17595fd1b 100644\n--- a/path.c\n+++ b/path.c\n@@ -1544,19 +1544,23 @@ int looks_like_command_line_option(const char *str)\n \n char *xdg_config_home_for(const char *subdir, const char *filename)\n {\n+\tchar *ret;\n \tconst char *home, *config_home;\n \n \tassert(subdir);\n \tassert(filename);\n \tconfig_home = getenv(\"XDG_CONFIG_HOME\");\n \tif (config_home && *config_home)\n-\t\treturn mkpathdup(\"%s/%s/%s\", config_home, subdir, filename);\n-\n-\thome = getenv(\"HOME\");\n-\tif (home)\n-\t\treturn mkpathdup(\"%s/.config/%s/%s\", home, subdir, filename);\n+\t\tret = mkpathdup(\"%s/%s/%s\", config_home, subdir, filename);\n+\telse if ((home = getenv(\"HOME\")))\n+\t\tret = mkpathdup(\"%s/.config/%s/%s\", home, subdir, filename);\n+\telse\n+\t\treturn NULL;\n \n-\treturn NULL;\n+#ifdef GIT_WINDOWS_NATIVE\n+\tconvert_slashes(ret);\n+#endif\n+\treturn ret;\n }\n \n char *xdg_config_home(const char *filename)\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex e3f8064889..329407a73d 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2350,6 +2350,38 @@ test_expect_success '--show-origin with --default' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'set up xdg config --show-origin tests' '\n+\tmkdir -p \"$HOME\"/.config/git &&\n+\tcat >\"$HOME\"/.config/git/config <<-EOF\n+\t[xdg]\n+\t\tconfig = true\n+\tEOF\n+'\n+\n+test_expect_success MINGW '--show-origin converts backslashes in xdg path to forward slashes on Windows' '\n+\tbackslash_home=\"$(echo \"$HOME\" | tr / \\\\\\\\)\" &&\n+\techo \"file:$HOME/.config/git/config\ttrue\" >expect &&\n+\n+\t(\n+\t\tsane_unset XDG_CONFIG_HOME &&\n+\t\tHOME=\"$backslash_home\" git config ${mode_get} --show-origin xdg.config >actual\n+\t) &&\n+\ttest_cmp expect actual &&\n+\n+\tXDG_CONFIG_HOME=\"$backslash_home\\\\.config\" git config ${mode_get} --show-origin xdg.config >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '--show-origin with default xdg path' '\n+\techo \"file:$HOME/.config/git/config\ttrue\" >expect &&\n+\tgit config ${mode_get} --show-origin xdg.config >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'clean up xdg config --show-origin tests' '\n+\trm -rf \"$HOME\"/.config/git\n+'\n+\n test_expect_success '--show-scope with --list' '\n \tcat >expect <<-EOF &&\n \tglobal\tuser.global=true\n\n-- \n2.54.0\n\n"},{"id":"551063","messageId":"20260823-fix-config-list-global-home-and-xdg-v2-3-b29cc63f017b@microsoft.com","threadId":"64290","inReplyTo":"20260823-fix-config-list-global-home-and-xdg-v2-0-b29cc63f017b@microsoft.com","subject":"[PATCH v2 3/3] config: read global scope via config_sequence","fromName":"Delilah Ashley Wu","fromEmail":"delilahwu@linux.microsoft.com","sentAt":"2026-08-23T10:28:28Z","receivedAt":"2026-08-23T10:31:04Z","isPatch":true,"body":"From: Delilah Ashley Wu <delilahwu@microsoft.com>\n\nWhen both `$HOME/.gitconfig` and `$XDG_CONFIG_HOME/git/config` exist,\n`git config list --global` and `git config get --global` read the home\nconfiguration file but ignore the XDG file. Bug reporters expected these\n`--global` scoped commands to read both files [1][2], which would be\nconsistent with the documentation and the behaviour of the unscoped\nvariants. For example, `git config list` and `git config get` (without\n`--global`) read from both files (in addition to system-wide and\nrepository-specific entries). We should address this inconsistency by\nrespecting both files during `--global` read operations.\n\nThe implementation assumes that each configuration scope corresponds to\na single file. So during `--global` read operations, Git selects one\nfile path to pass to `git_config_from_file_with_options(file)`. Because\nthe global scope can come from more than one file, we should use another\nmethod to read the global configuration.\n\nSince `git config list --show-scope --show-origin` reads both the home\nand XDG files, there must be existing code that respects both locations,\nnamely `do_git_config_sequence()` which reads from all scopes. Introduce\nflags to ignore all but the global scope (i.e. ignore system, local,\nworktree, and cmdline). Then, reuse the function to read only the global\nscope when `--global` is specified. This was the suggested solution [3]\nin the original bug report [1].\n\nModify tests to check that both configuration files are respected during\n`--global` read operations. Also, add additional tests to supplement the\nregression tests from the previous patch, \"config: let sequence require\na successful file\". The expected behaviour of `git config list` is:\n  - Without `--global`, it should not bail on unreadable/non-existent\n    global config files.\n\n  - With `--global`, it should bail when both `$HOME/.gitconfig` and\n    `$XDG_CONFIG_HOME/git/config` are unreadable. It should not bail\n    when one or more of them is readable.\n\nImplementation notes:\n  - The `ignore_global` flag is not set anywhere, so the\n    `if (!opts->ignore_global)` condition is always met. Include the\n    flag for completeness, but we can remove it if desired.\n\n  - Keep populating `opts->source.file` in `builtin/config.c` because it\n    is used as the destination config file for write operations. The\n    proposed changes could convolute the code because there is no single\n    source of truth for the config file locations in the global scope.\n    Add a comment to clarify this.\n\n[1] https://lore.kernel.org/git/CAFA9we-QLQRzJdGMMCPatmfrk1oHeiUu9msMRXXk1MLE5HRxBQ@mail.gmail.com/\n[2] https://lore.kernel.org/git/CAAdFe9yhBk-WecVzCTsjQ-4Z3AZAbpP+w+B076ouM3qX6d1WAg@mail.gmail.com/\n[3] https://lore.kernel.org/git/kl6ly1oze7wb.fsf@chooglen-macbookpro.roam.corp.google.com\n\nReported-by: Jade Lovelace <lists@jade.fyi>\nReported-by: Nils Fahldieck <nils@fahldieck.de>\nSuggested-by: Glen Choo <glencbz@gmail.com>\nHelped-by: Derrick Stolee <stolee@gmail.com>\nHelped-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Delilah Ashley Wu <delilahwu@microsoft.com>\n---\n builtin/config.c     | 11 ++++++++\n config.c             | 37 +++++++++++++++------------\n config.h             |  2 ++\n t/t1300-config.sh    | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n t/t1306-xdg-files.sh |  5 +++-\n 5 files changed, 109 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 0882899c3f..a7468e86d3 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -957,6 +957,17 @@ static void location_options_init(struct config_location_options *opts,\n \t}\n \n \tif (opts->use_global_config) {\n+\t\t/*\n+\t\t * Since global config is sourced from more than one location,\n+\t\t * read it using `do_git_config_sequence()` with other scopes\n+\t\t * ignored. However, writing global config should point to a\n+\t\t * single destination, set in `opts->source.file`.\n+\t\t */\n+\t\topts->options.ignore_repo = 1;\n+\t\topts->options.ignore_cmdline = 1;\n+\t\topts->options.ignore_worktree = 1;\n+\t\topts->options.ignore_system = 1;\n+\n \t\topts->source.file = opts->file_to_free = git_global_config();\n \t\tif (!opts->source.file)\n \t\t\t/*\ndiff --git a/config.c b/config.c\nindex 4c958f46bf..acad89102d 100644\n--- a/config.c\n+++ b/config.c\n@@ -1587,26 +1587,31 @@ static int do_git_config_sequence(const struct config_options *opts,\n \t\tworktree_config = NULL;\n \t}\n \n-\tif (git_config_system() && system_config &&\n+\tif (!opts->ignore_system && git_config_system() && system_config &&\n \t    !access_or_die(system_config, R_OK,\n \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n \t\tattempt_git_config_from_file_with_options(fn, system_config, data,\n \t\t\t\t\t\t\t  CONFIG_SCOPE_SYSTEM, NULL,\n \t\t\t\t\t\t\t  &success_count, &ret);\n \n-\tgit_global_config_paths(&user_config, &xdg_config);\n+\tif (!opts->ignore_global) {\n+\t\tgit_global_config_paths(&user_config, &xdg_config);\n \n-\tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n-\t\tattempt_git_config_from_file_with_options(fn, xdg_config,\n-\t\t\t\t\t\t\t  data,\n-\t\t\t\t\t\t\t  CONFIG_SCOPE_GLOBAL,\n-\t\t\t\t\t\t\t  NULL, &success_count, &ret);\n+\t\tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n+\t\t\tattempt_git_config_from_file_with_options(fn, xdg_config,\n+\t\t\t\t\t\t\t\t  data,\n+\t\t\t\t\t\t\t\t  CONFIG_SCOPE_GLOBAL,\n+\t\t\t\t\t\t\t\t  NULL, &success_count, &ret);\n \n-\tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n-\t\tattempt_git_config_from_file_with_options(fn, user_config,\n-\t\t\t\t\t\t\t  data,\n-\t\t\t\t\t\t\t  CONFIG_SCOPE_GLOBAL,\n-\t\t\t\t\t\t\t  NULL, &success_count, &ret);\n+\t\tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n+\t\t\tattempt_git_config_from_file_with_options(fn, user_config,\n+\t\t\t\t\t\t\t\t  data,\n+\t\t\t\t\t\t\t\t  CONFIG_SCOPE_GLOBAL,\n+\t\t\t\t\t\t\t\t  NULL, &success_count, &ret);\n+\n+\t\tfree(xdg_config);\n+\t\tfree(user_config);\n+\t}\n \n \tif (!opts->ignore_repo && repo_config &&\n \t    !access_or_die(repo_config, R_OK, 0))\n@@ -1624,8 +1629,6 @@ static int do_git_config_sequence(const struct config_options *opts,\n \t\tdie(_(\"unable to parse command-line config\"));\n \n \tfree(system_config);\n-\tfree(xdg_config);\n-\tfree(user_config);\n \tfree(repo_config);\n \tfree(worktree_config);\n \n@@ -1659,7 +1662,8 @@ int config_with_options(config_fn_t fn, void *data,\n \t */\n \tif (config_source && config_source->use_stdin) {\n \t\tret = git_config_from_stdin(fn, data, config_source->scope);\n-\t} else if (config_source && config_source->file) {\n+\t} else if (config_source && config_source->file &&\n+\t\t   config_source->scope != CONFIG_SCOPE_GLOBAL) {\n \t\tret = git_config_from_file_with_options(fn, config_source->file,\n \t\t\t\t\t\t\tdata, config_source->scope,\n \t\t\t\t\t\t\tNULL);\n@@ -1667,7 +1671,8 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n \t\t\t\t\t       data, config_source->scope);\n \t} else {\n-\t\tret = do_git_config_sequence(opts, repo, fn, data, 0);\n+\t\tret = do_git_config_sequence(opts, repo, fn, data,\n+\t\t\t\t\t     config_source && config_source->scope == CONFIG_SCOPE_GLOBAL);\n \t}\n \n \tif (inc.remote_urls) {\ndiff --git a/config.h b/config.h\nindex 31fe3e2961..eb2d7a2843 100644\n--- a/config.h\n+++ b/config.h\n@@ -87,6 +87,8 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n \n struct config_options {\n \tunsigned int respect_includes : 1;\n+\tunsigned int ignore_system : 1;\n+\tunsigned int ignore_global : 1;\n \tunsigned int ignore_repo : 1;\n \tunsigned int ignore_worktree : 1;\n \tunsigned int ignore_cmdline : 1;\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 2ce85b76ff..b6fd6e24ea 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2469,6 +2469,77 @@ test_expect_success 'list --global with nonexistent global config fails' '\n \ttest_must_fail git config ${mode_prefix}list --global --show-scope\n '\n \n+test_expect_success 'list and get --global with only home' '\n+\trm -f \"$HOME\"/.config/git/config &&\n+\n+\ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n+\tcat >\"$HOME\"/.gitconfig <<-EOF &&\n+\t[home]\n+\t\tconfig = true\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tglobal\thome.config=true\n+\tEOF\n+\tgit config ${mode_prefix}list --global --show-scope >actual &&\n+\ttest_cmp expect actual &&\n+\n+\techo true >expect &&\n+\tgit config ${mode_get} --global home.config >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'list and get --global with only xdg' '\n+\trm -f \"$HOME\"/.gitconfig &&\n+\n+\ttest_when_finished rm -rf \\\"\\$HOME\\\"/.config/git &&\n+\tmkdir -p \"$HOME\"/.config/git &&\n+\tcat >\"$HOME\"/.config/git/config <<-EOF &&\n+\t[xdg]\n+\t\tconfig = true\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tglobal\txdg.config=true\n+\tEOF\n+\tgit config ${mode_prefix}list --global --show-scope >actual &&\n+\ttest_cmp expect actual &&\n+\n+\techo true >expect &&\n+\tgit config ${mode_get} --global xdg.config >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'list and get --global with both home and xdg' '\n+\ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n+\tcat >\"$HOME\"/.gitconfig <<-EOF &&\n+\t[home]\n+\t\tconfig = home\n+\tEOF\n+\n+\ttest_when_finished rm -rf \\\"\\$HOME\\\"/.config/git &&\n+\tmkdir -p \"$HOME\"/.config/git &&\n+\tcat >\"$HOME\"/.config/git/config <<-EOF &&\n+\t[xdg]\n+\t\tconfig = xdg\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tglobal\tfile:$HOME/.config/git/config\txdg.config=xdg\n+\tglobal\tfile:$HOME/.gitconfig\thome.config=home\n+\tEOF\n+\tgit config ${mode_prefix}list --global --show-scope --show-origin >actual &&\n+\ttest_cmp expect actual &&\n+\n+\techo xdg >expect &&\n+\tgit config ${mode_get} --global xdg.config >actual &&\n+\ttest_cmp expect actual &&\n+\n+\techo home >expect &&\n+\tgit config ${mode_get} --global home.config >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'override global and system config' '\n \ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n \tcat >\"$HOME\"/.gitconfig <<-EOF &&\ndiff --git a/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh\nindex 40d3c42618..3a9a04bcc1 100755\n--- a/t/t1306-xdg-files.sh\n+++ b/t/t1306-xdg-files.sh\n@@ -52,6 +52,8 @@ test_expect_success 'read with --get: xdg file exists and ~/.gitconfig exists' '\n \techo \"\tname = read_gitconfig\" >>.gitconfig &&\n \techo read_gitconfig >expected &&\n \tgit config --get user.name >actual &&\n+\ttest_cmp expected actual &&\n+\tgit config --global --get user.name >actual &&\n \ttest_cmp expected actual\n '\n \n@@ -68,7 +70,8 @@ test_expect_success 'read with --list: xdg file exists and ~/.gitconfig exists'\n \t>.gitconfig &&\n \techo \"[user]\" >.gitconfig &&\n \techo \"\tname = read_gitconfig\" >>.gitconfig &&\n-\techo user.name=read_gitconfig >expected &&\n+\techo user.name=read_config >expected &&\n+\techo user.name=read_gitconfig >>expected &&\n \tgit config --global --list >actual &&\n \ttest_cmp expected actual\n '\n\n-- \n2.54.0\n\n"},{"id":"551069","messageId":"CAPx1GvcDNx4BUPQkVjbKxYLxTJ=StvLC43R0S_2=T0R8NKbZ7w@mail.gmail.com","threadId":"64290","inReplyTo":"20260823-fix-config-list-global-home-and-xdg-v2-0-b29cc63f017b@microsoft.com","subject":"Re: [PATCH v2 0/3] config: read both home and xdg files for --global","fromName":"Chris Torek","fromEmail":"chris.torek@gmail.com","sentAt":"2026-08-23T12:36:28Z","receivedAt":"2026-08-23T12:36:42Z","isPatch":true,"body":"On Sun, Aug 23, 2026 at 3:31 AM Delilah Ashley Wu\n<delilahwu@linux.microsoft.com> wrote:\n>\n> Hi all, thanks for your patience. Here's my reroll.\n>\n> As reported in [1], `$HOME/.gitconfig` and `$XDG_CONFIG_HOME/git/config`\n> are both valid global configuration locations. However, when both files\n> exist, `git config list --global` only reads from the former location\n> whereas `git config list` (without `--global`) reads from both. The same\n> issue was reported for `git config get` in [2]. This inconsistency has\n> no good justification and contradicts the documented behaviour.\n>\n> Suppose that `$HOME/.gitconfig` contains:\n>     [home]\n>         config = true\n>\n> and `$XDG_CONFIG_HOME/git/config` contains:\n>     [xdg]\n>         config = true\n>\n> Then, listing with `--global` shows only the home config:\n>     $ git config list --global --show-scope --show-origin\n>     global  file:/Users/delilah/.gitconfig    home.config=true\n>\n> and getting the XDG configuration entry with `--global` will fail:\n>     $ git config get --global xdg.config; echo $?\n>     1\n>\n> Git still reads the XDG config as part of its effective configuration,\n> as shown by listing the configuration without `--global`:\n>     $ git config list --show-scope --show-origin\n>     global  file:/Users/delilah/.config/git/config    xdg.config=true\n>     global  file:/Users/delilah/.gitconfig            home.config=true\n>\n> The documentation, quoted in [1] and [2], states that `--global` should\n> read from both files ...\n\nI have a related question: which of the global file(s) does\n\n    git config --global --edit\n\nedit? Which one(s) should it edit?\n\nChris\n"},{"id":"551100","messageId":"xmqqo6esti9o.fsf@gitster.g","threadId":"64290","inReplyTo":"CAPx1GvcDNx4BUPQkVjbKxYLxTJ=StvLC43R0S_2=T0R8NKbZ7w@mail.gmail.com","subject":"Re: [PATCH v2 0/3] config: read both home and xdg files for --global","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-24T01:32:51Z","receivedAt":"2026-08-24T01:32:55Z","isPatch":true,"body":"Chris Torek <chris.torek@gmail.com> writes:\n\n>> Git still reads the XDG config as part of its effective configuration,\n>> as shown by listing the configuration without `--global`:\n>>     $ git config list --show-scope --show-origin\n>>     global  file:/Users/delilah/.config/git/config    xdg.config=true\n>>     global  file:/Users/delilah/.gitconfig            home.config=true\n>>\n>> The documentation, quoted in [1] and [2], states that `--global` should\n>> read from both files ...\n>\n> I have a related question: which of the global file(s) does\n>\n>     git config --global --edit\n>\n> edit? Which one(s) should it edit?\n\nI _know_ that having git-config read per-user configuration from\nboth places was a deliberate design choice to help those who choose\nto migrate away from ~/.gitconfig to the XDG layout, while making\nsure we do not disrupt those who choose not to migrate.\n\nFor the write-out path of \"git config --global set var val\", we also\nchose accordingly, knowing that the majority of users back then had\ntheir per-user configuration in ~/.gitconfig and some, but not\nnecessarily all, wanted to migrate to the XDG layout, while avoiding\nwriting the same thing twice to different places.  Therefore, \"git\nconfig --global --edit\" should follow the choice in the same spirit\nas the existing write-out code path (and no, I do not think we want\nto open two files in users' editors).\n\nAs to the primary focus of this topic, I think \"git config --global\"\nfor the read path was not designed as carefully as the write-out\ncode path or the general \"git config\" sequence when we introduced\noptional support for the XDG layout.  Any discrepancy between \"git\nconfig\" when reading per-user values (to be overridden further by\nper-repository settings) and what \"git config --global\" reads from\nper-user files is very likely not due to any deliberate design\nchoice, but merely bugs caused by a slip of the mind.\n"},{"id":"551306","messageId":"xmqqecfkhify.fsf@gitster.g","threadId":"64290","inReplyTo":"20260823-fix-config-list-global-home-and-xdg-v2-1-b29cc63f017b@microsoft.com","subject":"Re: [PATCH v2 1/3] path: use forward slashes in XDG config on Windows","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-26T17:58:57Z","receivedAt":"2026-08-26T17:59:07Z","isPatch":true,"body":"Delilah Ashley Wu <delilahwu@linux.microsoft.com> writes:\n\n> From: Delilah Ashley Wu <delilahwu@microsoft.com>\n>\n> Git prefers forward slashes as directory separators across all\n> platforms. On Windows, the backslash is the native directory separator,\n> but all Windows versions supported by Git also accept the forward slash\n> in all but rare circumstances. Our tests expect forward slashes. Git\n> displays relative paths with forward slashes. Forward slashes are more\n> convenient to use in shell scripts.\n>\n> For these reasons, we enforced forward slashes in `interpolate_path()`\n> in 5ca6b7bb47b (config --show-origin: report paths with forward slashes,\n> 2016-03-23). However, other code paths may construct paths containing\n> backslashes. For example, `config --show-origin` prints the XDG config\n> path with mixed slashes on Windows:\n>\n>     $ git config --list --show-origin\n>     file:C:/Program Files/Git/etc/gitconfig         system.foo=bar\n>     file:\"C:\\\\Users\\\\delilah/.config/git/config\"    xdg.foo=bar\n>     file:C:/Users/delilah/.gitconfig                home.foo=bar\n>     file:.git/config                                local.foo=bar\n>\n> These mixed slashes occur because the `$HOME` and `$XDG_CONFIG_HOME`\n> environment variables usually contain backslashes on Windows, and\n> `xdg_config_home_for()` interpolates them into templates that use\n> hardcoded forward slashes.\n>\n> Since callers of `xdg_config_home_for()` handle mixed slashes correctly,\n> it is reasonable to assume that they can handle paths with only forward\n> slashes. Let's enforce forward slashes in `xdg_config_home_for()` by\n> using `convert_slashes()` on Windows.\n>\n> Also, there are no tests for the XDG path with `--show-origin`. Add a\n> test for slash conversion and a confidence check for the default path.\n\nIs this \"force forwared slashes to Windows users\" a required part of\nXDG/HOME global fix?  If not, please leave it out of the topic.\n\nEven if it is a good idea to always force forward slashes to Windows\nusers (I have no strong opinions on the topic), and if it is very\nunlikely to break existing Windows users (I do not have any clue if\nthat would be the case or not, as I do not do Windows), we would\nwant to make sure if we can get the same effect without sprinkling\n\"#ifdef\" in the platform agnostic part of the codebase like \"path.c\"\nfile.\n\nWhere would the slash in \"ret\" that is passed to convert_slashes()\nfunction come from?  If they come from environment variables like\nXDG_CONFIG_HOME and HOME, that is end-user's preference and we have\nno business forcing them which forms of slashes to use.  Does it\ncome from \"subdir\" or \"filename\" parameters?  It might be the job\nfor the callers to standardize slashes in the value they send in,\nbut as far as I can see, these do not have anything other than\nhardcoded constants that use no slashes (most of them) or one\nforward slash (\"systemd/user\").\n\nAgain, I do not see it explained why this change has to be part of\nthis series in the proposed log message, so...?\n\n> Signed-off-by: Delilah Ashley Wu <delilahwu@microsoft.com>\n> ---\n>  path.c            | 16 ++++++++++------\n>  t/t1300-config.sh | 32 ++++++++++++++++++++++++++++++++\n>  2 files changed, 42 insertions(+), 6 deletions(-)\n>\n> diff --git a/path.c b/path.c\n> index c3a709a928..f17595fd1b 100644\n> --- a/path.c\n> +++ b/path.c\n> @@ -1544,19 +1544,23 @@ int looks_like_command_line_option(const char *str)\n>  \n>  char *xdg_config_home_for(const char *subdir, const char *filename)\n>  {\n> +\tchar *ret;\n>  \tconst char *home, *config_home;\n>  \n>  \tassert(subdir);\n>  \tassert(filename);\n>  \tconfig_home = getenv(\"XDG_CONFIG_HOME\");\n>  \tif (config_home && *config_home)\n> -\t\treturn mkpathdup(\"%s/%s/%s\", config_home, subdir, filename);\n> -\n> -\thome = getenv(\"HOME\");\n> -\tif (home)\n> -\t\treturn mkpathdup(\"%s/.config/%s/%s\", home, subdir, filename);\n> +\t\tret = mkpathdup(\"%s/%s/%s\", config_home, subdir, filename);\n> +\telse if ((home = getenv(\"HOME\")))\n> +\t\tret = mkpathdup(\"%s/.config/%s/%s\", home, subdir, filename);\n> +\telse\n> +\t\treturn NULL;\n>  \n> -\treturn NULL;\n> +#ifdef GIT_WINDOWS_NATIVE\n> +\tconvert_slashes(ret);\n> +#endif\n> +\treturn ret;\n>  }\n>  \n>  char *xdg_config_home(const char *filename)\n> diff --git a/t/t1300-config.sh b/t/t1300-config.sh\n> index e3f8064889..329407a73d 100755\n> --- a/t/t1300-config.sh\n> +++ b/t/t1300-config.sh\n> @@ -2350,6 +2350,38 @@ test_expect_success '--show-origin with --default' '\n>  \ttest_cmp expect actual\n>  '\n>  \n> +test_expect_success 'set up xdg config --show-origin tests' '\n> +\tmkdir -p \"$HOME\"/.config/git &&\n> +\tcat >\"$HOME\"/.config/git/config <<-EOF\n> +\t[xdg]\n> +\t\tconfig = true\n> +\tEOF\n> +'\n> +\n> +test_expect_success MINGW '--show-origin converts backslashes in xdg path to forward slashes on Windows' '\n> +\tbackslash_home=\"$(echo \"$HOME\" | tr / \\\\\\\\)\" &&\n> +\techo \"file:$HOME/.config/git/config\ttrue\" >expect &&\n> +\n> +\t(\n> +\t\tsane_unset XDG_CONFIG_HOME &&\n> +\t\tHOME=\"$backslash_home\" git config ${mode_get} --show-origin xdg.config >actual\n> +\t) &&\n> +\ttest_cmp expect actual &&\n> +\n> +\tXDG_CONFIG_HOME=\"$backslash_home\\\\.config\" git config ${mode_get} --show-origin xdg.config >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success '--show-origin with default xdg path' '\n> +\techo \"file:$HOME/.config/git/config\ttrue\" >expect &&\n> +\tgit config ${mode_get} --show-origin xdg.config >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'clean up xdg config --show-origin tests' '\n> +\trm -rf \"$HOME\"/.config/git\n> +'\n> +\n>  test_expect_success '--show-scope with --list' '\n>  \tcat >expect <<-EOF &&\n>  \tglobal\tuser.global=true\n"},{"id":"551307","messageId":"xmqqy0dsg2vt.fsf@gitster.g","threadId":"64290","inReplyTo":"20260823-fix-config-list-global-home-and-xdg-v2-2-b29cc63f017b@microsoft.com","subject":"Re: [PATCH v2 2/3] config: let sequence require a successful file","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-26T18:20:22Z","receivedAt":"2026-08-26T18:20:30Z","isPatch":true,"body":"Delilah Ashley Wu <delilahwu@linux.microsoft.com> writes:\n\n> From: Delilah Ashley Wu <delilahwu@microsoft.com>\n>\n> Teach `do_git_config_sequence()` to optionally report an error if no\n> configuration files in the sequence were successfully processed. Gate\n> this new behaviour with a flag and keep it disabled for now.\n>\n> Add tests to record existing behaviour and prevent regressions in the\n> next patch, \"config: read global scope via config_sequence\", which adds\n> a code path that enables the flag. When no global configuration file\n> exists, `git config list` succeeds whereas `git config list --global`\n> fails. The command output is irrelevant, so only check the exit code.\n\nIt is not exactly 'irrelevant' as that is how the user learns what\ncaused the command to fail, e.g. \"fatal: unable to read config file <path>\".\n\nWhat you meant was that you are not interested in the exact message,\nyou only want to make sure it fails because of the missing file, and\nyou thought that it is a good way to do so to check the exit code.\n\n> Signed-off-by: Delilah Ashley Wu <delilahwu@microsoft.com>\n> ---\n>  config.c          | 57 ++++++++++++++++++++++++++++++++++++++-----------------\n>  t/t1300-config.sh | 12 ++++++++++++\n>  2 files changed, 52 insertions(+), 17 deletions(-)\n>\n> diff --git a/config.c b/config.c\n> index 1bdd702e7a..4c958f46bf 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -1544,11 +1544,27 @@ int git_config_system(void)\n>  \treturn !git_env_bool(\"GIT_CONFIG_NOSYSTEM\", 0);\n>  }\n\nPerhaps \"attempt\" -> \"try\" or something more clever can be used to\nmake sure we won't have to type so many characters.  \"try_config()\"\nshould be decriptive enough for the purpose, for example.\n\nFile scope static helper functions do not have to be and should not\nbe named with so many words.  Shorter names would also help to keep\nyour lines under ~70 column limit.\n\n> +static void attempt_git_config_from_file_with_options(config_fn_t fn,\n> +\t\t\t\t\t\t      const char *filename,\n> +\t\t\t\t\t\t      void *data,\n> +\t\t\t\t\t\t      enum config_scope scope,\n> +\t\t\t\t\t\t      const struct config_options *opts,\n> +\t\t\t\t\t\t      int *success_count,\n> +\t\t\t\t\t\t      int *cumulative_ret)\n> +{\n> +\tint ret = git_config_from_file_with_options(fn, filename, data,\n> +\t\t\t\t\t\t    scope, opts);\n> +\tif (!ret)\n> +\t\t(*success_count)++;\n> +\t*cumulative_ret += ret;\n> +}\n> +\n>  static int do_git_config_sequence(const struct config_options *opts,\n> -\t\t\t\t  const struct repository *repo,\n> -\t\t\t\t  config_fn_t fn, void *data)\n> +\t\t\t\t  const struct repository *repo, config_fn_t fn,\n> +\t\t\t\t  void *data, int require_successful_config)\n>  {\n>  \tint ret = 0;\n> +\tint success_count = 0;\n>  \tchar *system_config = git_system_config();\n>  \tchar *xdg_config = NULL;\n>  \tchar *user_config = NULL;\n> @@ -1574,32 +1590,35 @@ static int do_git_config_sequence(const struct config_options *opts,\n>  \tif (git_config_system() && system_config &&\n>  \t    !access_or_die(system_config, R_OK,\n>  \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n> -\t\tret += git_config_from_file_with_options(fn, system_config,\n> -\t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n> -\t\t\t\t\t\t\t NULL);\n> +\t\tattempt_git_config_from_file_with_options(fn, system_config, data,\n> +\t\t\t\t\t\t\t  CONFIG_SCOPE_SYSTEM, NULL,\n> +\t\t\t\t\t\t\t  &success_count, &ret);\n>  \n\nIf we are allowed to use system config, system_config is defined,\nand we can read the system config, we try to grab values from it,\nand record the fact that we did so successfully.\n\n>  \tgit_global_config_paths(&user_config, &xdg_config);\n\nWe grab paths to two files, as before.\n\n>  \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n> -\t\tret += git_config_from_file_with_options(fn, xdg_config, data,\n> -\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n> +\t\tattempt_git_config_from_file_with_options(fn, xdg_config,\n> +\t\t\t\t\t\t\t  data,\n> +\t\t\t\t\t\t\t  CONFIG_SCOPE_GLOBAL,\n> +\t\t\t\t\t\t\t  NULL, &success_count, &ret);\n\nIf xdg config is to be used (note: GIT_CONFIG_GLOBAL environment can\ndisable the use of it) and xdg file is available, we read and record\njust like we saw is done for the system config above.\n\n>  \tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n> -\t\tret += git_config_from_file_with_options(fn, user_config, data,\n> -\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n> +\t\tattempt_git_config_from_file_with_options(fn, user_config,\n> +\t\t\t\t\t\t\t  data,\n> +\t\t\t\t\t\t\t  CONFIG_SCOPE_GLOBAL,\n> +\t\t\t\t\t\t\t  NULL, &success_count, &ret);\n\nDitto fo user config.\n\n>  \tif (!opts->ignore_repo && repo_config &&\n>  \t    !access_or_die(repo_config, R_OK, 0))\n> -\t\tret += git_config_from_file_with_options(fn, repo_config, data,\n> -\t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n> +\t\tattempt_git_config_from_file_with_options(fn, repo_config, data,\n> +\t\t\t\t\t\t\t  CONFIG_SCOPE_LOCAL, NULL, &success_count, &ret);\n\nAnd the local one.\n\n>  \tif (!opts->ignore_worktree && worktree_config &&\n>  \t    repo && repo->repository_format_worktree_config &&\n> -\t    !access_or_die(worktree_config, R_OK, 0)) {\n> -\t\t\tret += git_config_from_file_with_options(fn, worktree_config, data,\n> -\t\t\t\t\t\t\t\t CONFIG_SCOPE_WORKTREE,\n> -\t\t\t\t\t\t\t\t NULL);\n> -\t}\n> +\t    !access_or_die(worktree_config, R_OK, 0))\n> +\t\tattempt_git_config_from_file_with_options(fn, worktree_config, data,\n> +\t\t\t\t\t\t\t  CONFIG_SCOPE_WORKTREE,\n> +\t\t\t\t\t\t\t  NULL, &success_count, &ret);\n\nAnd the per-worktree one.\n\n>  \tif (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)\n>  \t\tdie(_(\"unable to parse command-line config\"));\n> @@ -1609,6 +1628,10 @@ static int do_git_config_sequence(const struct config_options *opts,\n>  \tfree(user_config);\n>  \tfree(repo_config);\n>  \tfree(worktree_config);\n> +\n> +\tif (require_successful_config && !success_count && !ret)\n> +\t\tret = -1;\n\nIf we are asked to ensure that we successfully read at least one\nplace and we didn't, we assign -1 to ret but we do so ONLY when we\nhaven't seen any other errors (i.e., existing non-zero ret is\npreserved, which may not be -1).  OK.\n\n>  \treturn ret;\n>  }\n\nI am not convinced 100% that we need \"success_count\", either, until\nwe see how it is used in the later steps.  But from the way the\ntry_config() thing is used, I find it dubious that it now returns\nvoid.  It should just keep returning the error code as before, and\nthe caller should just keep accumulcating as the original code used\nto.  I.e.,\n\n\t\tret += try_config(fn, frotz_config, data,\n\t\t\t\t  CONFIG_SCOPE_FROTZ, NULL,\n                                  &success);\n"},{"id":"551308","messageId":"xmqqse40g22c.fsf@gitster.g","threadId":"64290","inReplyTo":"20260823-fix-config-list-global-home-and-xdg-v2-3-b29cc63f017b@microsoft.com","subject":"Re: [PATCH v2 3/3] config: read global scope via config_sequence","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-26T18:38:03Z","receivedAt":"2026-08-26T18:38:12Z","isPatch":true,"body":"Delilah Ashley Wu <delilahwu@linux.microsoft.com> writes:\n\n>  \tif (opts->use_global_config) {\n> +\t\t/*\n> +\t\t * Since global config is sourced from more than one location,\n> +\t\t * read it using `do_git_config_sequence()` with other scopes\n> +\t\t * ignored. However, writing global config should point to a\n> +\t\t * single destination, set in `opts->source.file`.\n> +\t\t */\n> +\t\topts->options.ignore_repo = 1;\n> +\t\topts->options.ignore_cmdline = 1;\n> +\t\topts->options.ignore_worktree = 1;\n> +\t\topts->options.ignore_system = 1;\n\nWe used to use ignore_repo, ignore_worktree, and ignore_cmdline\nmembers in the config_options, but to ignore system configuration,\nwe relied on git_config_system() that checks GIT_CONFIG_NOSYSTEM\nenvironment variable, and there was no way to ignore per-user\nconfiguration.  From that point of view, I find it sensible to make\nconfig_options the primary way to configure which parts of the\nconfiguration sequence is disabled.\n\nBut then we should go one step further, shouldn't we?  Either teach\ngit_config_system() to take config_options struct and pay attention\nto .ignore_system member in it, or get rid of git_config_system()\nand have the current users of that function take config_options and\npay attention to its .ignore_system member, so that we do not have\nto write an ugly conditional like this one:\n\n> -\tif (git_config_system() && system_config &&\n> +\tif (!opts->ignore_system && git_config_system() && system_config &&\n\n\n> +\tif (!opts->ignore_global) {\n\nIt is a bit misleading that this conditional is always taken.  No\ncaller will tell this function to skip the per-user configuration.\n\n> +\t\tgit_global_config_paths(&user_config, &xdg_config);\n> +\t\tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n> +\t\t\tattempt_git_config_from_file_with_options(fn, xdg_config,\n> +\t\t\t\t\t\t\t\t  data,\n> +\t\t\t\t\t\t\t\t  CONFIG_SCOPE_GLOBAL,\n> +\t\t\t\t\t\t\t\t  NULL, &success_count, &ret);\n> +\t\tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n> +\t\t\tattempt_git_config_from_file_with_options(fn, user_config,\n> +\t\t\t\t\t\t\t\t  data,\n> +\t\t\t\t\t\t\t\t  CONFIG_SCOPE_GLOBAL,\n> +\t\t\t\t\t\t\t\t  NULL, &success_count, &ret);\n> +\n> +\t\tfree(xdg_config);\n> +\t\tfree(user_config);\n> +\t}\n\n> @@ -1624,8 +1629,6 @@ static int do_git_config_sequence(const struct config_options *opts,\n>  \t\tdie(_(\"unable to parse command-line config\"));\n>  \n>  \tfree(system_config);\n> -\tfree(xdg_config);\n> -\tfree(user_config);\n>  \tfree(repo_config);\n>  \tfree(worktree_config);\n>  \n> @@ -1659,7 +1662,8 @@ int config_with_options(config_fn_t fn, void *data,\n>  \t */\n>  \tif (config_source && config_source->use_stdin) {\n>  \t\tret = git_config_from_stdin(fn, data, config_source->scope);\n> -\t} else if (config_source && config_source->file) {\n> +\t} else if (config_source && config_source->file &&\n> +\t\t   config_source->scope != CONFIG_SCOPE_GLOBAL) {\n>  \t\tret = git_config_from_file_with_options(fn, config_source->file,\n>  \t\t\t\t\t\t\tdata, config_source->scope,\n>  \t\t\t\t\t\t\tNULL);\n> @@ -1667,7 +1671,8 @@ int config_with_options(config_fn_t fn, void *data,\n>  \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n>  \t\t\t\t\t       data, config_source->scope);\n>  \t} else {\n> -\t\tret = do_git_config_sequence(opts, repo, fn, data, 0);\n> +\t\tret = do_git_config_sequence(opts, repo, fn, data,\n> +\t\t\t\t\t     config_source && config_source->scope == CONFIG_SCOPE_GLOBAL);\n>  \t}\n\n+100 column wide columns?  Please don't.\n\nThis sequence is a bit hard to read.  Instead of piggybacking on the\nexisting call to do the READL sequencing, add a new \"else if\" clause\nto deal specifically with the global case to the cascade would make\nthe result easier to follow, I suspect.  Something like this fix-up\non top of this patch, perhaps.\n\n config.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git c/config.c w/config.c\nindex acad89102d..bf77f847c3 100644\n--- c/config.c\n+++ w/config.c\n@@ -1663,7 +1663,9 @@ int config_with_options(config_fn_t fn, void *data,\n \tif (config_source && config_source->use_stdin) {\n \t\tret = git_config_from_stdin(fn, data, config_source->scope);\n \t} else if (config_source && config_source->file &&\n-\t\t   config_source->scope != CONFIG_SCOPE_GLOBAL) {\n+\t\t   config_source->scope == CONFIG_SCOPE_GLOBAL) {\n+\t\tret = do_git_config_sequence(opts, repo, fn, data, 1);\n+\t} else if (config_source && config_source->file) {\n \t\tret = git_config_from_file_with_options(fn, config_source->file,\n \t\t\t\t\t\t\tdata, config_source->scope,\n \t\t\t\t\t\t\tNULL);\n@@ -1671,8 +1673,7 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n \t\t\t\t\t       data, config_source->scope);\n \t} else {\n-\t\tret = do_git_config_sequence(opts, repo, fn, data,\n-\t\t\t\t\t     config_source && config_source->scope == CONFIG_SCOPE_GLOBAL);\n+\t\tret = do_git_config_sequence(opts, repo, fn, data, 0);\n \t}\n \n \tif (inc.remote_urls) {\n"},{"id":"552395","messageId":"aqIvJhLLcCSnyaL4-delilahwu@linux.microsoft.com","threadId":"64290","inReplyTo":"xmqqecfkhify.fsf@gitster.g","subject":"Re: [PATCH v2 1/3] path: use forward slashes in XDG config on Windows","fromName":"Delilah Ashley Wu","fromEmail":"delilahwu@linux.microsoft.com","sentAt":"2026-09-10T04:48:54Z","receivedAt":"2026-09-10T04:49:05Z","isPatch":true,"body":"Thanks for the reviews! I'm still working through the feedback for v3.\n\n\nOn Wed, Aug 26, 2026 at 10:58:57AM +1000, Junio C Hamano wrote:\n> Is this \"force forwared slashes to Windows users\" a required part of\n> XDG/HOME global fix?  If not, please leave it out of the topic. [...]\n> Again, I do not see it explained why this change has to be part of\n> this series in the proposed log message, so...?\n\nSorry, I forgot to explain in the log message that this change is\nsupposed to supplement the `--show-origin` tests added in patch 3\n(config: read global scope via config_sequence). Without it, the\n`--show-origin` would output a path with mixed slashes on Windows:\n\n    file:\"C:\\\\Users\\\\delilah/.config/git/config\"    xdg.foo=bar\n\nThe tests expect paths containing only forward slashes. So patch 3\nmodifies `t1300-config.sh` assertions to look like this:\n\n    echo \"file:$HOME/.config/git/config    xdg.config=xdg\" >expect\n    git config list --global --show-origin >actual\n    test_cmp expect actual\n\nwhere `$HOME` has been normalised to contain forward slashes only, as\nseen at `t1300-config.sh:2179`, which was introduced in 45bf329 (t1300:\nfix the new --show-origin tests on Windows):\n\n    HOME=\"$(pwd)\" # convert to Windows path\n\n\n> Even if it is a good idea to always force forward slashes to Windows\n> users (I have no strong opinions on the topic), and if it is very\n> unlikely to break existing Windows users (I do not have any clue if\n> that would be the case or not, as I do not do Windows), we would\n> want to make sure if we can get the same effect without sprinkling\n> \"#ifdef\" in the platform agnostic part of the codebase like \"path.c\"\n> file.\n\nI followed an existing usage of `#ifdef GIT_WINDOWS_NATIVE` and\n`convert_slashes()` in `path.c`, but if it's no longer allowed in the\nplatform agnostic part, we could do the slash conversion elsewhere. This\nassumes we want to keep converting the slashes, but we should reconsider\nfrom your points raised below.\n\n\n> Where would the slash in \"ret\" that is passed to convert_slashes()\n> function come from?  If they come from environment variables like\n> XDG_CONFIG_HOME and HOME, that is end-user's preference and we have\n> no business forcing them which forms of slashes to use.\n\nThe slash in `ret` would come from the environment variables, so perhaps\nwe should not modify the slashes in them at all. Instead, I could drop\nthis patch and change the tests in patch 3 to export a `XDG_CONFIG_HOME`\nvalue containing only forward slashes. This would satisfy the assumption\nthat paths in the tests will contain only forward slashes. What do you\nthink?\n\n\nThanks!\nDelilah =)\n"}]}