* [BUG] git config --global: doc and behaviour disagree when ~/.gitconfig and XDG config file coexist
@ 2026-07-30 18:18 Nils Fahldieck
2026-07-30 20:24 ` Junio C Hamano
0 siblings, 1 reply; 3+ messages in thread
From: Nils Fahldieck @ 2026-07-30 18:18 UTC (permalink / raw)
To: git
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
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [BUG] git config --global: doc and behaviour disagree when ~/.gitconfig and XDG config file coexist
2026-07-30 18:18 [BUG] git config --global: doc and behaviour disagree when ~/.gitconfig and XDG config file coexist Nils Fahldieck
@ 2026-07-30 20:24 ` Junio C Hamano
2026-07-31 0:14 ` Ben Knoble
0 siblings, 1 reply; 3+ messages in thread
From: Junio C Hamano @ 2026-07-30 20:24 UTC (permalink / raw)
To: Nils Fahldieck; +Cc: git
Nils Fahldieck <nils@fahldieck.de> writes:
> 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".
I do not understand this part. If you have a file that is not even
readable by you, it is not very useful and no better than the case
the file did not exist. Also, if the file exists and readable,
between a 0-byte and one liner ~/.gitconfig there shouldn't be any
difference in behaviour, no?
So, "when the file does not exist or even if the file exists is not
readble, then it is not used and the other file is used instead"
would probably be technically more correct, but I am not sure if
such a change has much value (unless you are trying to be very
pedantic).
> 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.
The documentation needs to be corrected, I think.
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [BUG] git config --global: doc and behaviour disagree when ~/.gitconfig and XDG config file coexist
2026-07-30 20:24 ` Junio C Hamano
@ 2026-07-31 0:14 ` Ben Knoble
0 siblings, 0 replies; 3+ messages in thread
From: Ben Knoble @ 2026-07-31 0:14 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Nils Fahldieck, git
> Le 31 juil. 2026 à 05:35, Junio C Hamano <gitster@pobox.com> a écrit :
>
> Nils Fahldieck <nils@fahldieck.de> writes:
>
>> 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".
>
> I do not understand this part. If you have a file that is not even
> readable by you, it is not very useful and no better than the case
> the file did not exist. Also, if the file exists and readable,
> between a 0-byte and one liner ~/.gitconfig there shouldn't be any
> difference in behaviour, no?
>
> So, "when the file does not exist or even if the file exists is not
> readble, then it is not used and the other file is used instead"
> would probably be technically more correct, but I am not sure if
> such a change has much value (unless you are trying to be very
> pedantic).
My thoughts as well. A readable 0-byte file is not a counterexample to the docs; if such a file is readable then it exists, and is used as documented, no?
>> 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.
>
> The documentation needs to be corrected, I think.
Agreed based on recent thread <20260720113402.0dc16abe@frustcomp.hnjs.home.arpa> (subject « git config: unintuitive behavior with - -global and - -no-includes » in case I have mis-transcribed the message ID, a necessity to maintain plain-text mail from my mobile phone, ahem).
Best,
Ben
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-31 0:14 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-30 18:18 [BUG] git config --global: doc and behaviour disagree when ~/.gitconfig and XDG config file coexist Nils Fahldieck
2026-07-30 20:24 ` Junio C Hamano
2026-07-31 0:14 ` Ben Knoble
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.