Git configuration cascading and conditional includes¶
Managing multiple identities, keys, and security controls across corporate, open-source, and client repositories presents operational risks. Committing to a corporate repository with a personal email breaks commit compliance gates. Signing an open-source commit with an enterprise hardware key leaks internal key identifiers. Hardcoding credentials in repository configurations causes configuration drift.
Git conditional configuration solves these issues. Git loads targeted configuration files dynamically based on repository paths, branch names, or remote URLs.
Configuration hierarchy and precedence¶
Git loads configuration files in a strict order. Higher-level scopes override lower-level scopes.
| Scope | Location | Flag | Purpose |
|---|---|---|---|
| System | /etc/gitconfig |
--system |
System-wide defaults for all users on the host. |
| Global | ~/.gitconfig or ~/.config/git/config |
--global |
User-specific defaults across all repositories. |
| Local | .git/config |
--local |
Repository-specific overrides. |
| Worktree | .git/worktrees/<name>/config.worktree |
--worktree |
Specific to an individual linked worktree. |
| Command | Runtime flag | -c <key>=<value> |
Overrides all file settings for a single command invocation. |
Inspecting active configuration and sources¶
To inspect which configuration file set a specific value, run:
To query a specific key with its resolution origin:
To list all defined values for a key across all scopes in resolution order:
Conditional include syntax¶
Git provides the [includeIf "<condition>"] directive inside ~/.gitconfig. If the condition matches the current repository context, Git loads the specified configuration file.
1. Matching by directory path (gitdir)¶
The gitdir: keyword matches the filesystem path of the repository .git directory.
# ~/.gitconfig
[user]
name = Alex Mercer
email = alex@personal-domain.com
# Apply work settings to any repository inside ~/work/
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig-work
# Apply client settings to any repository inside ~/clients/acme/
[includeIf "gitdir:~/clients/acme/"]
path = ~/.gitconfig-acme
Trailing slash requirement¶
A trailing slash tells Git to match all subdirectories:
"gitdir:~/work/"matches~/work/repo-a/.gitand~/work/subfolder/repo-b/.git."gitdir:~/work/**"explicitly uses glob syntax to match nested subdirectories.
Case-insensitive directory matching (gitdir/i)¶
Filesystems on macOS and Windows ignore casing. Use gitdir/i: to prevent matching failures caused by directory case mismatches:
2. Matching by branch name (onbranch)¶
The onbranch: keyword applies configuration files based on the currently checked-out branch.
# ~/.gitconfig
# Enforce strict verification and different push remotes on release branches
[includeIf "onbranch:release/*"]
path = ~/.gitconfig-release
# Enforce production deploy keys and hooks on production branch
[includeIf "onbranch:main"]
path = ~/.gitconfig-prod
3. Matching by remote URL (hasconfig:remote.*.url)¶
Available in Git 2.36 and later, hasconfig:remote.*.url: loads configuration files when a repository remote URL matches a pattern.
# ~/.gitconfig
# Match any repository cloned from internal corporate Forgejo/GitLab
[includeIf "hasconfig:remote.*.url:https://git.local.sneakysquid.xyz/**"]
path = ~/.gitconfig-corp
# Match SSH clones from GitHub corporate org
[includeIf "hasconfig:remote.*.url:git@github.com:corporate-org/**"]
path = ~/.gitconfig-corp
Enterprise DevOps patterns¶
Pattern 1. Identity and email separation¶
Configure identity profiles in separate files to keep personal and corporate identities distinct.
Base configuration (~/.gitconfig)¶
[user]
name = Alex Mercer
email = alex@personal-domain.com
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig-work
Corporate profile (~/.gitconfig-work)¶
[user]
name = Alex Mercer
email = alex.mercer@enterprise.corp
[core]
# Enforce corporate SSH key for all work repos
sshCommand = ssh -i ~/.ssh/id_ed25519_work -o IdentitiesOnly=yes
Pattern 2. Directory-scoped commit signing¶
Sign corporate commits with an enterprise SSH or GPG key without altering personal repository configurations.
# ~/.gitconfig-work
[user]
signingkey = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG... corporate-key
[gpg]
format = ssh
[commit]
gpgsign = true
[tag]
gpgsign = true
Pattern 3. URL rewrites with insteadOf¶
Translate clone and push URLs automatically. This pattern avoids editing scripts that reference public HTTPS URLs when local runners must use SSH or private internal mirrors.
# ~/.gitconfig
# Rewrite all HTTPS GitHub clone requests to use SSH
[url "git@github.com:"]
insteadOf = https://github.com/
# Rewrite public npm dependency git repos to an internal mirror
[url "https://git.local.sneakysquid.xyz/mirror/"]
insteadOf = https://github.com/external-vendor/
# Separate push URL from fetch URL
[url "git@git.local.sneakysquid.xyz:"]
pushInsteadOf = "https://git.local.sneakysquid.xyz/"
Pattern 4. Scoped proxy routing¶
Route Git traffic through corporate proxies only when connecting to internal infrastructure or external vendor endpoints.
# ~/.gitconfig
# Default: no proxy
[http]
proxy = ""
# Route internal enterprise domain through corporate proxy
[http "https://git.internal.corp"]
proxy = "http://proxy.corp.internal:8080"
sslVerify = true
sslCAInfo = /etc/ssl/certs/corp-ca.pem
Pattern 5. Safe directory handling in containerized CI¶
When running Git commands inside Docker containers where host file ownership differs from the container user ID, Git throws a fatal dubious ownership error.
Configure safe.directory at the system or global level:
# /etc/gitconfig or ~/.gitconfig inside CI runner container
[safe]
# Whitelist specific build workspace
directory = /workspace
directory = /runner/_work/*
# Whitelist all directories in ephemeral throwaway CI containers
directory = *
Run from shell scripts inside CI containers:
Pattern 6. Worktree-specific configuration¶
By default, Git shares .git/config across all linked worktrees. Enabling extensions.worktreeConfig allows each worktree to maintain independent settings for sparse-checkout, hooks, or branches.
# Enable per-worktree configuration in the repository
git config extensions.worktreeConfig true
# Set a setting for the current worktree only
git config --worktree user.email worktree-worker@corp.internal
git config --worktree core.sparseCheckout true
Git stores the worktree-specific settings in .git/worktrees/<worktree-name>/config.worktree.
Environment variable overrides in CI/CD¶
CI pipelines often need temporary Git configuration changes without modifying persistent configuration files on runner hosts.
1. Command-line flag -c¶
Pass arbitrary configuration entries directly to any Git command:
git -c user.name="CI Bot" -c user.email="ci@corp.internal" commit -m "chore(ci): update build stamp"
2. GIT_CONFIG_PARAMETERS¶
Set multiple configuration pairs in a single environment variable:
export GIT_CONFIG_PARAMETERS="'user.name=DeployBot' 'user.email=deploy@corp.internal' 'commit.gpgsign=false'"
git commit -m "release: v1.4.0"
3. Pointing to custom configuration files¶
Isolate CI runners from host configuration:
# Ignore system-level /etc/gitconfig
export GIT_CONFIG_NOSYSTEM=1
# Use a dedicated global configuration file
export GIT_CONFIG_GLOBAL="/tmp/ci-gitconfig"
Troubleshooting and configuration verification¶
Tracing conditional include execution¶
To test if an includeIf block activated for the current repository:
# Check resolved email in the current directory
git config user.email
# Trace the exact file that provided the resolved value
git config --show-origin user.email
If the value resolves to the default rather than the included file:
- Check path slashes. Ensure directory conditions end with a trailing
/(e.g.gitdir:~/work/). - Resolve symlinks. Git compares the canonical filesystem path. If
~/worksymlinks to/data/work, use/data/work/or wildcard**/work/. - Check filesystem case sensitivity. Use
gitdir/i:on macOS or Windows. - Verify relative path resolution. The
pathinsideincludeIfresolves relative to the file containing the include, unless you supply an absolute path or~/.