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