Git Config Precedence Tester
Paste your git config files and see which value wins for every key, the scope, file and line it came from, what it overrode, and which includeIf fired.
gitdir: conditions are matched against <repository>/.git, onbranch: against the branch name. hasconfig: needs neither — it is answered from the config itself.
Files read
6
Keys resolved
10
Keys with a loser
3
Includes followed
2
Findings
1
The winner is the last entry read — this column is git config --get. The overridden column is the rest of --get-all, in read order.
| Key | Value | Scope · file:line | As bool / int | Overridden |
|---|---|---|---|---|
| user.name | Ada L. | local .git/config:13 | bool neither · int neither | 1 earlier candidate(s), oldest first:
|
| user.email | ada@release.example | worktree .git/config.worktree:2 | bool neither · int neither | 2 earlier candidate(s), oldest first:
|
| core.autocrlf | input | system /etc/gitconfig:2 | bool neither · int neither | only candidate |
| core.pager | delta | global ~/.gitconfig:5 | bool neither · int neither | only candidate |
| core.bigfilethreshold | 20m | global ~/.gitconfig:6 | bool true · int 20971520 | only candidate |
| commit.gpgsign | true | global ~/.gitconfig-work:4 | bool true · int neither | only candidate |
| push.default | current | global ~/.gitconfig-feature:2 | bool neither · int neither | only candidate |
| remote.origin.url | git@github.com:acme/app.git | local .git/config:9 | bool neither · int neither | only candidate |
| remote.origin.fetch | +refs/tags/*:refs/tags/* | local .git/config:11 | bool neither · int neither | 1 earlier candidate(s), oldest first:
|
| init.defaultbranch | main | system /etc/gitconfig:4 | bool neither · int neither | only candidate |
An indented file was pulled in by the one above it. A struck-through line is a condition that did not fire.
- system /etc/gitconfig · read as this scope
- global ~/.gitconfig · read as this scope
- global ~/.gitconfig-work · includeIf gitdir:~/work/ · matched
- global ~/.gitconfig-oss · includeIf gitdir:~/oss/ · skipped — the pattern does not match
- global ~/.gitconfig-feature · includeIf onbranch:feature/ · matched
- local .git/config · read as this scope
- worktree .git/config.worktree · read as this scope
The equivalent of git config --list --show-scope --show-origin, with each include spliced in where its directive sits.
| Scope | Scope · file:line | Key | Value |
|---|---|---|---|
| system | /etc/gitconfig:2 | core.autocrlf | input |
| system | /etc/gitconfig:4 | init.defaultbranch | main |
| global | ~/.gitconfig:2 | user.name | Ada Lovelace |
| global | ~/.gitconfig:3 | user.email | ada@personal.example |
| global | ~/.gitconfig:5 | core.pager | delta |
| global | ~/.gitconfig:6 | core.bigfilethreshold | 20m |
| global | ~/.gitconfig:8 | includeif.gitdir:~/work/.path | ~/.gitconfig-work |
| global | ~/.gitconfig-work:2 | user.email | ada@corp.example |
| global | ~/.gitconfig-work:4 | commit.gpgsign | true |
| global | ~/.gitconfig:10 | includeif.gitdir:~/oss/.path | ~/.gitconfig-oss |
| global | ~/.gitconfig:12 | includeif.onbranch:feature/.path | ~/.gitconfig-feature |
| global | ~/.gitconfig-feature:2 | push.default | current |
| local | .git/config:2 | core.repositoryformatversion | 0 |
| local | .git/config:3 | core.filemode | true |
| local | .git/config:4 | core.bare | false |
| local | .git/config:5 | core.logallrefupdates | true |
| local | .git/config:7 | extensions.worktreeconfig | true |
| local | .git/config:9 | remote.origin.url | git@github.com:acme/app.git |
| local | .git/config:10 | remote.origin.fetch | +refs/heads/*:refs/remotes/origin/* |
| local | .git/config:11 | remote.origin.fetch | +refs/tags/*:refs/tags/* |
| local | .git/config:13 | user.name | Ada L. |
| worktree | .git/config.worktree:2 | user.email | ada@release.example |
Everything below is something git does silently, or refuses to do at all.
- extensions.worktreeConfig is on while core.repositoryFormatVersion is still 0 — git 2.43 reads the worktree file anyway, older versions may not: .git/config
Everything is worked out in your browser. Nothing is uploaded, and no repository is read or written.
How to use
- Paste every config file into the first box, one after another, each introduced by a marker line:
# system: /etc/gitconfig,# global: ~/.gitconfig,# local: .git/config,# worktree: .git/config.worktree. Runninggit config --list --show-origin --show-scopeon the machine in question tells you exactly which files exist and where they live. - Add the files your includes point at with
# file: <path>. Those are not a scope of their own — they inherit the scope of whatever file included them, which is how git reports them too — and a relativeinclude.pathis resolved against the directory of the file containing it, so../work.gitconfiginside.git/configmeans<repo>/work.gitconfig, not the directory above the repository. - List the keys you care about in the second box, one per line:
user.email,remote.origin.url,core.autocrlf. Leave it empty to resolve every key the pasted tree defines, which is the fastest way to spot a setting you did not know you had. - Fill in the repository directory and the checked-out branch. Those two are the only things a pasted file cannot carry, and no
gitdir:oronbranch:condition can be judged without them; the file list says so in as many words when either box is blank. - Read the table: the winning value, its scope, file and line, and under it every candidate it beat in read order. Then read the file list for the include chain and why each condition did or did not fire, and the findings panel for includes that point nowhere, files nothing pulls in, and a worktree file git is quietly ignoring.
About this tool
ShowHide
git config --get user.email answers in a tenth of a second on the machine in front of you. On a CI runner, inside a container, on a colleague's laptop or in a support ticket that is nothing but pasted files, it does not — and the guessing starts. Paste the files here instead, each introduced by a # system:, # global:, # local: or # worktree: marker, and this page replays git's own resolution: every entry in the order git reads it, the winner for each key you ask about, the file and line that winner came from, and the candidates it beat.
The order is the whole story, and it is blunter than people expect. Files are read system, then global, then local, then worktree, top to bottom inside each one, and the last entry read for a key simply is the value. There is no merge step and no idea of a more specific setting winning: a second [user] section at the bottom of .git/config beats the one at the top, and a key set after an include beats the file that include pulled in, because an include is spliced in where the directive sits rather than appended at the end. The same fact is why --get and --get-all differ — the first hands you the last candidate, the second hands you all of them in read order, which is what remote.origin.fetch, safe.directory and include.path are built on.
All four includeIf conditions are evaluated and each is shown with its verdict. gitdir: and gitdir/i: are matched against the repository's .git directory as a whole-path glob, so a pattern ending in / gains an implicit /**, one starting with ~/ is home-relative, one starting with ./ is relative to the file that holds it, and anything else gets **/ in front — which is why gitdir:~/work without the trailing slash matches nothing at all. onbranch: is matched against the short branch name with the same slash-aware globbing, so onbranch:* does not fire on feature/new-ui while onbranch:** does. hasconfig:remote.*.url: is answered from a scan of the entire configuration with every other hasconfig: condition taken as true (gitdir: and onbranch: are still evaluated), so a remote declared further down the same file, or in a file an unconditional include pulls in, still counts.
Two traps get an answer of their own. .git/config.worktree is read only when extensions.worktreeConfig is true in .git/config itself; setting it from an included file changes nothing, because the repository format is read from that one file without following includes. And a file git cannot parse is fatal for the whole command rather than for one line: an unterminated [section header, a space inside a key name or an unmatched quote makes git config --list print nothing at all, which is reported here with the file and the line instead of a half-read answer. Beside each winning value you also get what --type=bool and --type=int make of it, including the k/m/g suffixes (20m is 20971520) and the fact that a key written with no = is the boolean true.
What this page cannot see is the command line. git -c user.name=…, the GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n pairs and options a porcelain command passes to itself all sit above every file scope and never appear in a paste, so a value that still refuses to change here is worth chasing there. It has no filesystem either: an include.path pointing at a file you did not paste is reported as unresolved rather than guessed at, and a gitdir: verdict is only as good as the repository path you type into the box.
Frequently asked questions
Why does my .git/config.worktree do nothing?
Because extensions.worktreeConfig is not true in .git/config — and, less obviously, because setting it from a file that .git/config includes does not count either. git reads the repository format from that one file without following includes, so the extension has to be written into .git/config literally. git config --worktree and git worktree do that for you, which is why the file usually works when you did not create it by hand. The documentation also tells you to raise core.repositoryFormatVersion to 1 alongside it; git 2.43 reads the worktree file even at version 0, so a version-0 repository with the extension set is not the bug you are hunting. This page reproduces that behaviour and flags the version mismatch separately, because an older git on another machine may well ignore the file.
What order are the scopes read in, and where is the command line?
System, then global, then local, then worktree, then the command line. This page covers the four file scopes. The command line is git -c key=value …, the GIT_CONFIG_KEY_n and GIT_CONFIG_VALUE_n environment pairs, and options a porcelain command passes to itself; none of that lives in a file you can paste, and git config --list --show-scope labels it command line. If a key resolves one way here and another way on the real machine, that gap is the first place to look. GIT_CONFIG_SYSTEM, GIT_CONFIG_GLOBAL and GIT_CONFIG_NOSYSTEM are a different mechanism again: they replace or suppress a file scope rather than adding a new one on top.
Is [remote "Origin"] the same as [remote "origin"]?
No, and that is a real way to end up with two remotes. Section and key names are case-insensitive and git prints them folded to lower case, so [CORE] BigFileThreshold comes back as core.bigfilethreshold. A subsection in double quotes is compared byte for byte, so [remote "Origin"] and [remote "origin"] are two different remotes and git remote -v lists both. The deprecated dotted form [section.SubSection] is the exception: the whole header is read as one token and folded, so it becomes section.subsection and answers to a lower-case query. This page implements both, which is why a config using the dotted form resolves where the quoted form does not.
My includeIf "gitdir:~/work" never fires. What is wrong with it?
A gitdir: pattern is matched against the .git directory path, not the working tree, and the match is a glob over the whole path rather than a prefix test — so ~/work matches only a git directory literally called ~/work. You want ~/work/, where the trailing slash expands to ~/work/** and catches everything underneath. Three more rules are worth memorising: a pattern that is neither absolute nor ~/ nor ./ gets **/ prepended, so gitdir:acme/ matches an acme directory anywhere on the disk; ./ is relative to the file holding the condition, which is what makes a .gitconfig dropped into a projects folder self-contained; and gitdir/i: is the same match without case, which is what you want on macOS and Windows.
Why does --get return one value when --get-all returns three?
Because a key can be set more than once and git keeps every occurrence in read order. --get hands you the last one, which is what nearly all of git uses; --get-all hands you the whole list. Some keys are meaningless without the list: remote.origin.fetch is normally two or three refspecs, safe.directory is a list by design, and include.path is however many files you chained. That is also why writing such a key with git config remote.origin.fetch … replaces a single entry instead of adding one, and why --add, --replace-all and --unset-all exist at all. The overridden column here shows every earlier candidate with the file and line it came from, so you can see whether a value was replaced or merely joined.
It says the whole file is an error. Can it not just skip the bad line?
Neither can git, and that is the point of showing it this way. A config file is parsed as one stream, and any byte the parser cannot place — an unterminated [section header, a space inside a key name, an unmatched double quote, an unknown backslash escape — makes the entire read fatal: git config --list prints nothing, and every command that reads configuration fails the same way, usually complaining about a file you were not editing. The line reported is where the offending statement began. The one thing that looks broken and is not is a key written before any section header: orphan = 1 at the top of a file parses fine and appears in --list as a bare orphan, but git config --get orphan refuses it because a query has to name a section, so the value is unreachable rather than invalid.
Related tools
.gitignore Tester
Paste a .gitignore and a list of paths to see which are ignored and exactly which line decided — the answer git check-ignore -v gives.
Developer
.mailmap Tester
Paste a .mailmap and a list of author identities to see what each one resolves to, which entry did it, and which people finally merge into one.
Developer
Git Index Inspector
Read .git/index in your browser: every staged entry with its blob hash, mode and flags, conflict stages, extensions and the trailing checksum.
File
XPath Tester
Run an XPath expression against XML or HTML in your browser and see every node it matches, with namespace prefixes and parse errors reported.
Developer
EditorConfig Tester
Paste an .editorconfig and some file paths: see the property set each file resolves to, and which section on which line decided every value.
Developer
HTTP Methods Reference
Quick card per HTTP method (GET, POST, PUT, DELETE, PATCH, …) — safety, idempotence, cacheability, and whether request / response bodies are allowed.
Developer