Git, CI/CD & GitOpsXXVI · Git ConfigurationGitConfig
Include and conditional configs — one identity per repo, one set of aliases per team
What you'll learn
- Use include.path to pull another config file into the merged view
- Use includeIf.<condition>.path to apply a config only when a condition matches
- Distinguish the gitdir:, gitdir/i:, and hasconfig: conditions and what each matches
- Choose between conditional config and per-repo local config for a given use case
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
include.path pulls another file into Git’s merged configuration
view; includeIf.<condition>.path pulls the file in only when
the named condition matches. The two together are the mechanism
that makes a single global config file act differently in
different repositories, on different hosts, for different
identities. They are also the mechanism that lets a dotfiles
repository hold a team’s shared settings without forcing every
engineer to copy them by hand.
Plain include
include.path takes a path to another config file. Git reads the
included file as if its contents were appended to the including
file. Scope rules still apply: an included file’s values can be
overridden by values set at a more specific scope, but values
inside the included file do not change their own scope.
git config --global include.path "$HOME/.gitconfig.shared"
git config --global include.path "$HOME/work/gitconfig.customer"
The first line pulls in a shared team config every time the
engineer runs a Git command. The second pulls in a customer-
specific config that lives outside the dotfiles repository
(because the customer’s repos and policies are sensitive). Both
are included at every repository; the difference is that
include.path has no condition.
Conditional include
includeIf.<condition>.path is the production-grade form. The
condition is one of three values:
gitdir:<pattern>matches when the current repository’s.gitdirectory matches the pattern. The pattern is matched against the absolute path with~and$HOMEexpanded. A trailing/requires a directory;/<pattern>/matches only if the absolute path starts with<pattern>;<pattern>is otherwise treated as a shell glob.gitdir/i:<pattern>is the case-insensitive variant ofgitdir:. Useful on Windows and macOS where path case can vary.hasconfig:remote.*.url:<pattern>matches when one of the repository’s remotes has a URL matching the pattern. This is the right condition when the same repository path is used by multiple remotes (a fork and an upstream, for example).
git config --global includeIf.gitdir:~/work/customer/.path \
"$HOME/work/customer/.gitconfig"
git config --global includeIf.hasconfig:remote.*.url:*@github.com:acme/. \
"$HOME/.gitconfig.acme"
The first pulls in a customer-specific config whenever the
engineer is inside a repository under ~/work/customer/. The
second pulls in a different config whenever any of the current
repository’s remotes is on github.com/acme. The two conditions
compose: a single repository can match several includeIf blocks,
and each contributes its own values to the merged view.
flowchart TB
A["git config --global ..."] --> B["global file: $HOME/.gitconfig"]
B --> C{"includeIf conditions"}
C -->|"gitdir: ~/work/customer/*"| D["customer config"]
C -->|"hasconfig: remote.*.url: *@github.com:acme/*"| E["acme config"]
C -->|"gitdir: ~/.gitconfig.shared"| F["shared team config"]
B --> G["merged view"]
D --> G
E --> G
F --> G
G --> H["git command sees the right identity for this repo"]
Per-directory vs per-host
The two most common conditional-config patterns:
Per-directory (gitdir:):
the engineer’s workstations all have the same $HOME layout, so a
single condition that matches a directory applies on every
machine. Use this for repositories that need a fixed identity,
fixed signing key, or fixed remote URL regardless of which
machine the engineer is working on.
Per-host (gitdir/i: against a hostname-derived path, or a
script that writes a host-specific include):
a hostname-specific config can hold the SSH key path, the proxy
settings, or the remotes that only exist on a particular machine.
The condition is usually written against a directory that the
host’s bootstrap script creates (~/.gitconfig.<hostname>), so
the includeIf block on every workstation reads the same includeIf
declaration but only the matching host’s file exists.
hasconfig and chained includes
hasconfig:remote.*.url: is the right condition when the same
working directory can be associated with multiple remotes, or
when the path is not a reliable identifier (because the engineer
clones to different locations). The pattern is:
git config --global includeIf.hasconfig:remote.*.url:*@github.com:acme/*.git \
"$HOME/.gitconfig.acme"
The condition matches if any of the repository’s remotes has a
URL containing *@github.com:acme/*.git. This works on clones
under ~/work/, ~/scratch/, or anywhere else, because the
condition reads the remotes from .git/config rather than the
filesystem path.
includeIf blocks can be chained: an included file can itself
contain includeIf declarations. The pattern is useful when the
shared team config should pull in a project-specific override on
top of itself. The condition syntax is identical.
When not to use conditional config
Conditional config is the right tool when the same engineer needs different settings in different repositories. It is not the right tool when:
- The setting should always be the same. A name, an editor, a default branch name - these belong in plain global config.
- The setting is per-commit, not per-repository. A CI bot’s identity for one job belongs in environment variables, not in conditional config.
- The setting is one the team will set locally anyway. If
every team member needs the same per-repo config, putting it
in the repository’s own
.git/config(via a bootstrap script or amake setuptarget) is simpler than a sharedincludeIfblock that has to match the path on every machine.
Production discipline
- Scope identity, signing keys, and remotes by repository.
Use
includeIf.gitdir:so the right config follows the right repository. - Keep the shared file small and stable. A shared
includeIfblock is a contract; every engineer’s config depends on it. - Version-control the shared file. A dotfiles repository or a path in the team’s monorepo both work; the file must be reviewable.
- Document the conditions. A future engineer (or the same engineer in two years) should be able to read the global config and understand why a particular repository pulls in a particular file.
Cross-course references
- Linux for Production Sysadmins - Part XXXIV
(ConfigMgmt) covers the analogue for shell configuration:
.bashrc.d/snippets that are sourced conditionally on hostname, role, or environment. The pattern is the same. - Ansible for Production Sysadmins - Part XXXVII (RepoArch) covers per-environment inventory; the conditional pattern generalises.
Quiz
Knowledge check · 4 questions
Q1. An engineer runs `git config --global includeIf.gitdir:~/work/customer/.path ~/work/customer/.gitconfig`. In which repositories does Git pull in `~/work/customer/.gitconfig`?
Q2. `includeIf.hasconfig:remote.*.url:<pattern>` matches a repository when any of its remotes has a URL matching the pattern, regardless of where the repository is checked out on disk.
Q3. Name the three condition keywords `includeIf` accepts and describe the one case where each is the right choice.
Q4. Recommend a conditional-config layout for a consultant who works across three customer repositories with three different required identities.
A consultant checks out three customer repositories: `~/work/acme/infra`, `~/work/contoso/infra`, and `~/work/fabrikam/infra`. Each requires a different `user.email`. The consultant wants a single global config that applies the right identity automatically based on which repository is checked out, and that survives moving to a new laptop with a fresh global config.
Passing score: 75%. Answers are checked in this browser.