From: Nils Fahldieck Date: Thu, 30 Jul 2026 18:18:43 GMT Subject: [BUG] git config --global: doc and behaviour disagree when ~/.gitconfig and XDG config file coexist Message-ID: Hi, I ran into a confusing discrepancy between what the git-config(1) man page promises for --global and what the code actually does when both ~/.gitconfig and ~/.config/git/config exist. BACKGROUND ---------- Git recognises two "global" config files (described in the FILES section of git-config(1)): $XDG_CONFIG_HOME/git/config (falls back to ~/.config/git/config when $XDG_CONFIG_HOME is unset) ~/.gitconfig When --global is NOT passed, git config --get reads both files via do_git_config_sequence() in config.c. The XDG file is processed first, ~/.gitconfig second, so ~/.gitconfig takes precedence on any key that appears in both. This is correct and consistent with the documentation. The bug is in what happens when --global IS explicitly passed. WHAT THE DOCS CLAIM ------------------- From Documentation/git-config.adoc, the --global entry reads: For writing options: write to global ~/.gitconfig file rather than the repository .git/config, write to $XDG_CONFIG_HOME/git/config file if this file exists and the ~/.gitconfig file doesn't. For reading options: read only from global ~/.gitconfig and from $XDG_CONFIG_HOME/git/config rather than from all available files. WHAT THE CODE ACTUALLY DOES ---------------------------- Both read and write with --global go through the same function, git_global_config() in config.c (around line 1505), which returns exactly ONE path. That single path is then set as the only source file in builtin/config.c (around line 960). The selection logic in git_global_config() is: /* access_or_warn returns non-zero on FAILURE, zero on success */ if (access_or_warn(user_config, R_OK, 0) && xdg_config && !access_or_warn(xdg_config, R_OK, 0)) { return xdg_config; /* ~/.gitconfig unreadable AND XDG readable */ } else { return user_config; /* otherwise always return ~/.gitconfig */ } This means: 1. The writing claim is inaccurate. The docs say XDG is used when ~/.gitconfig "doesn't exist". The code tests READABILITY (R_OK), not existence. A zero-byte file created by "touch ~/.gitconfig" is readable, so access_or_warn returns 0 (success), the condition is false, and XDG is silently ignored even though ~/.gitconfig is empty. The condition should be described as "when ~/.gitconfig is not readable", not "when it doesn't exist". 2. The reading claim is outright wrong. The docs say --global reads from BOTH files. The code reads from ONE. git_global_config() selects a winner and frees the other path. There is no code path under --global that reads both files. REPRODUCER ---------- # Setup: only the XDG file exists and contains user.name = "My Name" $ ls ~/.config/git/config # exists, has user.name $ ls ~/.gitconfig # does not exist $ git config --get user.name My Name $ git config --global user.name My Name $ touch ~/.gitconfig # create empty but readable ~/.gitconfig $ git config --get user.name My Name # correct: reads both, XDG value survives $ git config --global user.name # BUG: empty output -- git_global_config() # returned ~/.gitconfig (readable but empty) # and silently discarded the XDG file $ rm ~/.gitconfig $ git config --global user.name My Name # back to normal: ~/.gitconfig gone, XDG used Tested on macOS with git version 2.55.0 built and installed via Homebrew. THE FIX -- TWO OPTIONS ---------------------- Option A -- Fix the code to match the documented intent (preferred): Make git config --global for reading behave like do_git_config_sequence(): read both global files when both are accessible and let later values take precedence (i.e. ~/.gitconfig wins over XDG, same as normal reads). This is what the documentation describes and what users expect. Option B -- Fix the docs to match the actual code: Document the real rule: "--global selects a single file: ~/.gitconfig if it is readable, otherwise the XDG file if it is readable." Option B alone closes the documentation bug but leaves the underlying asymmetry: "git config --get" and "git config --global" silently disagree whenever both global files coexist. That asymmetry is a usability bug in its own right regardless of what the docs say. I prefer Option A. RELEVANT CODE LOCATIONS ----------------------- config.c ~1505-1523 git_global_config() -- picks one file config.c ~1525-1537 git_global_config_paths() -- builds both paths config.c ~1580-1586 do_git_config_sequence() -- reads both correctly builtin/config.c ~960 --global wires git_global_config() as sole source path.c ~1545-1560 xdg_config_home_for() -- XDG fallback to ~/.config/ Documentation/git-config.adoc ~147-154 the inaccurate --global entry I am interested in actually contributing a patch once we agreed on a fix. If the behaviour is intended, though, I am interested in the reasoning. Maybe it is also a translation issue since English is not my native language. Kind regards Nils