Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXVI · Git ConfigurationGitConfig

Include and conditional configs — one identity per repo, one set of aliases per team

Intermediate⏱ ~18 mingit

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

Not yet marked complete on this device.

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 .git directory matches the pattern. The pattern is matched against the absolute path with ~ and $HOME expanded. 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 of gitdir:. 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 a make setup target) is simpler than a shared includeIf block that has to match the path on every machine.

Production discipline

  1. Scope identity, signing keys, and remotes by repository. Use includeIf.gitdir: so the right config follows the right repository.
  2. Keep the shared file small and stable. A shared includeIf block is a contract; every engineer’s config depends on it.
  3. Version-control the shared file. A dotfiles repository or a path in the team’s monorepo both work; the file must be reviewable.
  4. 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

  1. 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`?

  2. 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.

  3. Q3. Name the three condition keywords `includeIf` accepts and describe the one case where each is the right choice.

  4. 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.