{"thread":{"id":"66091","subject":"[BUG] git config --global: doc and behaviour disagree when ~/.gitconfig and XDG config file coexist","startedAt":"2026-07-30T19:45:36Z","lastAt":"2026-08-22T09:38:44Z","messageCount":7,"participants":["Nils Fahldieck","Junio C Hamano","Ben Knoble","Delilah Ashley Wu"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"549322","messageId":"CAAdFe9yhBk-WecVzCTsjQ-4Z3AZAbpP+w+B076ouM3qX6d1WAg@mail.gmail.com","threadId":"66091","inReplyTo":null,"subject":"[BUG] git config --global: doc and behaviour disagree when ~/.gitconfig and XDG config file coexist","fromName":"Nils Fahldieck","fromEmail":"nils@fahldieck.de","sentAt":"2026-07-30T18:18:43Z","receivedAt":"2026-07-30T19:45:36Z","isPatch":false,"body":"Hi,\n\nI ran into a confusing discrepancy between what the git-config(1) man\npage promises for --global and what the code actually does when both\n~/.gitconfig and ~/.config/git/config exist.\n\n\nBACKGROUND\n----------\n\nGit recognises two \"global\" config files (described in the FILES\nsection of git-config(1)):\n\n  $XDG_CONFIG_HOME/git/config\n    (falls back to ~/.config/git/config when $XDG_CONFIG_HOME is unset)\n\n  ~/.gitconfig\n\nWhen --global is NOT passed, git config --get reads both files via\ndo_git_config_sequence() in config.c.  The XDG file is processed first,\n~/.gitconfig second, so ~/.gitconfig takes precedence on any key that\nappears in both.  This is correct and consistent with the documentation.\n\nThe bug is in what happens when --global IS explicitly passed.\n\n\nWHAT THE DOCS CLAIM\n-------------------\n\nFrom Documentation/git-config.adoc, the --global entry reads:\n\n  For writing options: write to global ~/.gitconfig file rather than\n  the repository .git/config, write to $XDG_CONFIG_HOME/git/config\n  file if this file exists and the ~/.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\nWHAT THE CODE ACTUALLY DOES\n----------------------------\n\nBoth read and write with --global go through the same function,\ngit_global_config() in config.c (around line 1505), which returns\nexactly ONE path.  That single path is then set as the only source\nfile in builtin/config.c (around line 960).\n\nThe selection logic in git_global_config() is:\n\n  /* access_or_warn returns non-zero on FAILURE, zero on success */\n  if (access_or_warn(user_config, R_OK, 0) && xdg_config &&\n      !access_or_warn(xdg_config, R_OK, 0)) {\n      return xdg_config;   /* ~/.gitconfig unreadable AND XDG readable */\n  } else {\n      return user_config;  /* otherwise always return ~/.gitconfig */\n  }\n\nThis means:\n\n1. The writing claim is inaccurate.\n\n   The docs say XDG is used when ~/.gitconfig \"doesn't exist\".  The\n   code tests READABILITY (R_OK), not existence.  A zero-byte file\n   created by \"touch ~/.gitconfig\" is readable, so access_or_warn\n   returns 0 (success), the condition is false, and XDG is silently\n   ignored even though ~/.gitconfig is empty.\n\n   The condition should be described as \"when ~/.gitconfig is not\n   readable\", not \"when it doesn't exist\".\n\n2. The reading claim is outright wrong.\n\n   The docs say --global reads from BOTH files.  The code reads from\n   ONE.  git_global_config() selects a winner and frees the other\n   path.  There is no code path under --global that reads both files.\n\n\nREPRODUCER\n----------\n\n  # Setup: only the XDG file exists and contains user.name = \"My Name\"\n  $ ls ~/.config/git/config     # exists, has user.name\n  $ ls ~/.gitconfig             # does not exist\n\n  $ git config --get user.name\n  My Name\n  $ git config --global user.name\n  My Name\n\n  $ touch ~/.gitconfig          # create empty but readable ~/.gitconfig\n\n  $ git config --get user.name\n  My Name                       # correct: reads both, XDG value survives\n\n  $ git config --global user.name\n                                # BUG: empty output -- git_global_config()\n                                # returned ~/.gitconfig (readable but empty)\n                                # and silently discarded the XDG file\n\n  $ rm ~/.gitconfig\n\n  $ git config --global user.name\n  My Name                       # back to normal: ~/.gitconfig gone, XDG used\n\nTested on macOS with git version 2.55.0 built and installed via Homebrew.\n\n\nTHE FIX -- TWO OPTIONS\n----------------------\n\nOption A -- Fix the code to match the documented intent (preferred):\n\n  Make git config --global for reading behave like do_git_config_sequence():\n  read both global files when both are accessible and let later values\n  take precedence (i.e. ~/.gitconfig wins over XDG, same as normal reads).\n  This is what the documentation describes and what users expect.\n\nOption B -- Fix the docs to match the actual code:\n\n  Document the real rule: \"--global selects a single file: ~/.gitconfig\n  if it is readable, otherwise the XDG file if it is readable.\"\n\n  Option B alone closes the documentation bug but leaves the underlying\n  asymmetry: \"git config --get\" and \"git config --global\" silently\n  disagree whenever both global files coexist.  That asymmetry is a\n  usability bug in its own right regardless of what the docs say.\n\nI prefer Option A.\n\n\nRELEVANT CODE LOCATIONS\n-----------------------\n\n  config.c ~1505-1523    git_global_config() -- picks one file\n  config.c ~1525-1537    git_global_config_paths() -- builds both paths\n  config.c ~1580-1586    do_git_config_sequence() -- reads both correctly\n  builtin/config.c ~960     --global wires git_global_config() as sole source\n  path.c ~1545-1560    xdg_config_home_for() -- XDG fallback to ~/.config/\n\n  Documentation/git-config.adoc ~147-154   the inaccurate --global entry\n\nI am interested in actually contributing a patch once we agreed on a fix. If the\nbehaviour is intended, though, I am interested in the reasoning. Maybe it is\nalso a translation issue since English is not my native language.\n\nKind regards\nNils\n"},{"id":"549324","messageId":"xmqqo6fojkds.fsf@gitster.g","threadId":"66091","inReplyTo":"CAAdFe9yhBk-WecVzCTsjQ-4Z3AZAbpP+w+B076ouM3qX6d1WAg@mail.gmail.com","subject":"Re: [BUG] git config --global: doc and behaviour disagree when ~/.gitconfig and XDG config file coexist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-30T20:24:47Z","receivedAt":"2026-07-30T20:24:49Z","isPatch":false,"body":"Nils Fahldieck <nils@fahldieck.de> writes:\n\n> 1. The writing claim is inaccurate.\n>\n>    The docs say XDG is used when ~/.gitconfig \"doesn't exist\".  The\n>    code tests READABILITY (R_OK), not existence.  A zero-byte file\n>    created by \"touch ~/.gitconfig\" is readable, so access_or_warn\n>    returns 0 (success), the condition is false, and XDG is silently\n>    ignored even though ~/.gitconfig is empty.\n>\n>    The condition should be described as \"when ~/.gitconfig is not\n>    readable\", not \"when it doesn't exist\".\n\nI do not understand this part.  If you have a file that is not even\nreadable by you, it is not very useful and no better than the case\nthe file did not exist.  Also, if the file exists and readable,\nbetween a 0-byte and one liner ~/.gitconfig there shouldn't be any\ndifference in behaviour, no?\n\nSo, \"when the file does not exist or even if the file exists is not\nreadble, then it is not used and the other file is used instead\"\nwould probably be technically more correct, but I am not sure if\nsuch a change has much value (unless you are trying to be very\npedantic).\n\n> 2. The reading claim is outright wrong.\n>\n>    The docs say --global reads from BOTH files.  The code reads from\n>    ONE.  git_global_config() selects a winner and frees the other\n>    path.  There is no code path under --global that reads both files.\n\nThe documentation needs to be corrected, I think.\n"},{"id":"549327","messageId":"336EEC18-98D3-4068-8C5C-476749959814@gmail.com","threadId":"66091","inReplyTo":"xmqqo6fojkds.fsf@gitster.g","subject":"Re: [BUG] git config --global: doc and behaviour disagree when ~/.gitconfig and XDG config file coexist","fromName":"Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-07-31T00:14:07Z","receivedAt":"2026-07-31T00:14:20Z","isPatch":false,"body":"\n> Le 31 juil. 2026 à 05:35, Junio C Hamano <gitster@pobox.com> a écrit :\n> \n> ﻿Nils Fahldieck <nils@fahldieck.de> writes:\n> \n>> 1. The writing claim is inaccurate.\n>> \n>>   The docs say XDG is used when ~/.gitconfig \"doesn't exist\".  The\n>>   code tests READABILITY (R_OK), not existence.  A zero-byte file\n>>   created by \"touch ~/.gitconfig\" is readable, so access_or_warn\n>>   returns 0 (success), the condition is false, and XDG is silently\n>>   ignored even though ~/.gitconfig is empty.\n>> \n>>   The condition should be described as \"when ~/.gitconfig is not\n>>   readable\", not \"when it doesn't exist\".\n> \n> I do not understand this part.  If you have a file that is not even\n> readable by you, it is not very useful and no better than the case\n> the file did not exist.  Also, if the file exists and readable,\n> between a 0-byte and one liner ~/.gitconfig there shouldn't be any\n> difference in behaviour, no?\n> \n> So, \"when the file does not exist or even if the file exists is not\n> readble, then it is not used and the other file is used instead\"\n> would probably be technically more correct, but I am not sure if\n> such a change has much value (unless you are trying to be very\n> pedantic).\n\nMy 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?\n\n>> 2. The reading claim is outright wrong.\n>> \n>>   The docs say --global reads from BOTH files.  The code reads from\n>>   ONE.  git_global_config() selects a winner and frees the other\n>>   path.  There is no code path under --global that reads both files.\n> \n> The documentation needs to be corrected, I think.\n\nAgreed 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).\n\nBest,\nBen\n\n"},{"id":"549361","messageId":"CAAdFe9z7hE1Av=d4u6vUmvJjaDD0JXJpHb9UVd7oEiMpFV0PQg@mail.gmail.com","threadId":"66091","inReplyTo":"336EEC18-98D3-4068-8C5C-476749959814@gmail.com","subject":"Re: [BUG] git config --global: doc and behaviour disagree when ~/.gitconfig and XDG config file coexist","fromName":"Nils Fahldieck","fromEmail":"nils@fahldieck.de","sentAt":"2026-07-31T18:19:06Z","receivedAt":"2026-07-31T18:19:51Z","isPatch":false,"body":"Hey there!\n\n> 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).\n\nThank you very much for the other thread, I have read and understand\nthe origin of `--global` way better now. This understanding reinforced\na PR that I raised for the git-get helper binary. The origin of my\nproblem is git-get's configuration, which reads its config via `git\nconfig --global key` instead of `git config --get key`. This is the PR\nfor reference: https://github.com/grdl/git-get/pull/45\n\n> > So, \"when the file does not exist or even if the file exists is not\n> > readble, then it is not used and the other file is used instead\"\n> > would probably be technically more correct, but I am not sure if\n> > such a change has much value (unless you are trying to be very\n> > pedantic).\n>\n> 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?\n\nI agree and do not want to be pedantic about the implementation. I\njust tried to understand the code and added that to the report.\n\nI still see a discrepancy between `git config --get` and `git config\n--global`, though. As a user it would make sense that if we have both\n`~/.gitconfig` and `~/.config/git/config` only one file is read and\nwritten in every case. Or in other words, with your explained expected\nbehaviour, `git config --get` should not return a value. It is\noff-putting that some commands read both files where some do not. I am\nhappy to understand the reasoning a little bit better, though. Maybe I\nhave a missing connection in my head.\n\nAnyway, I still feel that the documentation must be updated (at least)\nsince Git is executing differently to it. I am happy to provide a\npatch for it. I just need to read up about how I contribute it for Git\nitself and the git-scm.com website including all i18n versions. Any\nhints are appreciated, my starting point would be\nhttps://git-scm.com/community though.\n\nBest\nNils\n"},{"id":"550470","messageId":"an1dekUM_fsM_l8d-delilahwu@linux.microsoft.com","threadId":"66091","inReplyTo":"336EEC18-98D3-4068-8C5C-476749959814@gmail.com","subject":"Re: [BUG] git config --global: doc and behaviour disagree when ~/.gitconfig and XDG config file coexist","fromName":"Delilah Ashley Wu","fromEmail":"delilahwu@linux.microsoft.com","sentAt":"2026-08-13T06:07:08Z","receivedAt":"2026-08-13T06:07:43Z","isPatch":false,"body":"On Fri, Jul 31, 2026 at 09:14:07AM +1000, Ben Knoble wrote:\n>> Le 31 juil. 2026 à 05:35, Junio C Hamano <gitster@pobox.com> a écrit :\n>> ﻿Nils Fahldieck <nils@fahldieck.de> writes:\n>>> 2. The reading claim is outright wrong.\n>>> \n>>>   The docs say --global reads from BOTH files.  The code reads from\n>>>   ONE.  git_global_config() selects a winner and frees the other\n>>>   path.  There is no code path under --global that reads both files.\n>> \n>> The documentation needs to be corrected, I think.\n> \n> Agreed based on recent thread <20260720113402.0dc16abe@frustcomp.hnjs.home.arpa>\n> (subject « git config: unintuitive behavior with - -global and - -no-includes »\n\nA previous thread [1] said this behavior could be a bug. So, last year,\nI submitted a patch series [2] to align behavior with documentation,\nchanging `git config` to read from both files. We gave time for the\ncommunity to comment on whether we should change the behavior or the\ndocumentation, but there weren't any strong opinions [3]. The only\nresponses were a correction to my cover letter and a technical review of\nmy patches.\n\nSince then, I've addressed most of the review feedback. I have a draft\nv2 series [4] almost ready for reroll. I only work on it during \"side\nproject time\" [5] at my workplace, but I'll try to have v2 ready soon\nfor further discussion/review.\n\n[1] https://lore.kernel.org/git/xmqqmt5lezi3.fsf@gitster.g/\n[2] https://lore.kernel.org/git/pull.1938.git.1760058849.gitgitgadget@gmail.com/\n[3] https://lore.kernel.org/git/xmqqtsysfakt.fsf@gitster.g/\n[4] https://github.com/gitgitgadget/git/pull/2196\n[5] https://en.wikipedia.org/wiki/Side_project_time\n"},{"id":"551007","messageId":"CAAdFe9z92-h8eQmwxzZ=1cCB_8ptTHKnL1QMWAO=VEJVHe07OQ@mail.gmail.com","threadId":"66091","inReplyTo":"an1dekUM_fsM_l8d-delilahwu@linux.microsoft.com","subject":"Re: [BUG] git config --global: doc and behaviour disagree when ~/.gitconfig and XDG config file coexist","fromName":"Nils Fahldieck","fromEmail":"nils@fahldieck.de","sentAt":"2026-08-21T09:21:02Z","receivedAt":"2026-08-21T12:27:48Z","isPatch":false,"body":"Hi Delilah, thank you for chiming into this thread. I am very excited\nto be reading about your progress, patches and everything you've\ncontributed so far.\n\n> Since then, I've addressed most of the review feedback. I have a draft\n> v2 series [4] almost ready for reroll.\n\nWhen you have the v2 of your patch ready, please let me know and I am\nhappy to provide any feedback. I really like to see this change\nhappening.\n\nAm Do., 13. Aug. 2026 um 08:07 Uhr schrieb Delilah Ashley Wu\n<delilahwu@linux.microsoft.com>:\n>\n> On Fri, Jul 31, 2026 at 09:14:07AM +1000, Ben Knoble wrote:\n> >> Le 31 juil. 2026 à 05:35, Junio C Hamano <gitster@pobox.com> a écrit :\n> >> ﻿Nils Fahldieck <nils@fahldieck.de> writes:\n> >>> 2. The reading claim is outright wrong.\n> >>>\n> >>>   The docs say --global reads from BOTH files.  The code reads from\n> >>>   ONE.  git_global_config() selects a winner and frees the other\n> >>>   path.  There is no code path under --global that reads both files.\n> >>\n> >> The documentation needs to be corrected, I think.\n> >\n> > Agreed based on recent thread <20260720113402.0dc16abe@frustcomp.hnjs.home.arpa>\n> > (subject « git config: unintuitive behavior with - -global and - -no-includes »\n>\n> A previous thread [1] said this behavior could be a bug. So, last year,\n> I submitted a patch series [2] to align behavior with documentation,\n> changing `git config` to read from both files. We gave time for the\n> community to comment on whether we should change the behavior or the\n> documentation, but there weren't any strong opinions [3]. The only\n> responses were a correction to my cover letter and a technical review of\n> my patches.\n>\n> Since then, I've addressed most of the review feedback. I have a draft\n> v2 series [4] almost ready for reroll. I only work on it during \"side\n> project time\" [5] at my workplace, but I'll try to have v2 ready soon\n> for further discussion/review.\n>\n> [1] https://lore.kernel.org/git/xmqqmt5lezi3.fsf@gitster.g/\n> [2] https://lore.kernel.org/git/pull.1938.git.1760058849.gitgitgadget@gmail.com/\n> [3] https://lore.kernel.org/git/xmqqtsysfakt.fsf@gitster.g/\n> [4] https://github.com/gitgitgadget/git/pull/2196\n> [5] https://en.wikipedia.org/wiki/Side_project_time\n"},{"id":"551051","messageId":"aoln1Q33kxaTaq60-delilahwu@linux.microsoft.com","threadId":"66091","inReplyTo":"CAAdFe9z92-h8eQmwxzZ=1cCB_8ptTHKnL1QMWAO=VEJVHe07OQ@mail.gmail.com","subject":"Re: [BUG] git config --global: doc and behaviour disagree when ~/.gitconfig and XDG config file coexist","fromName":"Delilah Ashley Wu","fromEmail":"delilahwu@linux.microsoft.com","sentAt":"2026-08-22T09:38:32Z","receivedAt":"2026-08-22T09:38:44Z","isPatch":false,"body":"On Fri, Aug 21, 2026 at 11:21:02AM +1000, Nils Fahldieck wrote:\n> I am very excited to be reading about your progress, patches and\n> everything you've contributed so far.\n\nThanks for your support, Nils!\n\n> When you have the v2 of your patch ready, please let me know and I am\n> happy to provide any feedback.\n\nI realised from your bug report that I need to test `git config --get\n--global` in addition to `git config list --global`. Once I have that\nworking, I'll cc you in v2 =)\n"}]}