Skip to content

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:

git config --list --show-origin --show-scope

To query a specific key with its resolution origin:

git config --show-origin user.email

To list all defined values for a key across all scopes in resolution order:

git config --get-all --show-origin user.name

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/.git and ~/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:

[includeIf "gitdir/i:~/work/"]
    path = ~/.gitconfig-work

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:

git config --global --add safe.directory "$GITHUB_WORKSPACE"

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:

  1. Check path slashes. Ensure directory conditions end with a trailing / (e.g. gitdir:~/work/).
  2. Resolve symlinks. Git compares the canonical filesystem path. If ~/work symlinks to /data/work, use /data/work/ or wildcard **/work/.
  3. Check filesystem case sensitivity. Use gitdir/i: on macOS or Windows.
  4. Verify relative path resolution. The path inside includeIf resolves relative to the file containing the include, unless you supply an absolute path or ~/.