Shipping a team gitconfig with includeIf Jump to heading
Documentation that says โplease set these seven valuesโ produces a team where some people have set seven, some four and one has set none. Gitโs include mechanism turns that list into a file under version control: the settings are reviewed like code, changing them is a pull request, and a new machine picks them up with one command instead of seven. This recipe sets that up, including the conditional includes that keep work and personal identities apart, within Git configuration management at scale.
When to use this approach Jump to heading
- Different machines produce different results from the same repository.
- Your contributing documentation contains a list of
git configcommands. - Identity mistakes โ personal email on work commits โ happen occasionally.
- You want configuration changes to go through review.
- If your team is two people and everything already works, a short setup script is enough.
Step 1 โ Write the committed file, with reasons Jump to heading
Every line should have a justification, because the file will otherwise accumulate settings nobody can defend.
# .gitconfig-team โ settings that change what a commit or checkout contains.
# Personal preferences (aliases, pager, colours) deliberately stay out.
[core]
# Line endings are decided by .gitattributes, not by each machine.
autocrlf = false
# Hooks live in the repository so they are reviewed like code.
hooksPath = .githooks
[merge]
# Shows the common ancestor in a conflict: resolution becomes a decision.
conflictStyle = zdiff3
[diff]
# Produces noticeably more readable diffs on refactors.
algorithm = histogram
colorMoved = default
[pull]
# Avoids accidental merge commits from a routine pull.
rebase = true
[fetch]
# Removes remote-tracking refs for branches that are gone.
prune = true
[rebase]
# Keeps stacked branches intact when the base moves.
updateRefs = true
autosquash = true git add .gitconfig-team && git commit -m 'chore: add shared Git configuration' # Verification: the file is committed and syntactically valid
git config --file .gitconfig-team --list Step 2 โ Include it from each clone Jump to heading
Git will not read a repositoryโs configuration automatically, and that restriction is deliberate.
git config --local include.path ../.gitconfig-team # Verification: the values now resolve, and their origin is the committed file
git config --show-origin --get merge.conflictStyle
git config --show-origin --get core.hooksPath The path is relative to the repositoryโs .git directory, which is why it starts with ../. Getting that wrong fails silently โ the include is recorded, no file is found, and nothing applies, which is exactly why the verification above checks the origin rather than just the value.
SAFETY WARNING โ Git deliberately refuses to read configuration out of a cloned repository, because configuration can specify commands: pagers, diff drivers, credential helpers and hooks. Running the include command is the consent step that makes it safe. Never add it automatically from a script that runs on clone of an untrusted repository, and be sure you trust the repository whose configuration you are including.
Step 3 โ Use conditional includes for identity Jump to heading
Identity is personal, so it stays out of the repository โ but it can still be automatic.
# ~/.gitconfig
[user]
name = Ada Lovelace
email = [email protected]
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig-work
[includeIf "gitdir:~/oss/"]
path = ~/.gitconfig-oss # ~/.gitconfig-work
[user]
email = [email protected]
[commit]
gpgsign = true
[gpg]
format = ssh
[user]
signingkey = ~/.ssh/id_ed25519_signing.pub # Verification: identity differs by directory without any per-clone step
git -C ~/work/app config --get user.email
git -C ~/personal/notes config --get user.email # Conditional on the remote URL instead, for directories that are not tidy
# [includeIf "hasconfig:remote.*.url:[email protected]:acme/**"]
# path = ~/.gitconfig-work
git config --get-urlmatch user.email "$(git remote get-url origin)" Step 4 โ Make the include part of setup Jump to heading
#!/usr/bin/env sh
# scripts/setup โ idempotent; safe to run on every clone, repeatedly.
set -eu
root=$(git rev-parse --show-toplevel)
git -C "$root" config --local include.path ../.gitconfig-team
# Fail loudly if the include did not take effect.
test "$(git -C "$root" config --get merge.conflictStyle)" = "zdiff3" \
|| { echo "setup: include did not apply โ check the path" >&2; exit 1; }
echo "configured: $(git -C "$root" config --get user.email) / hooks: $(git -C "$root" config --get core.hooksPath)" # Verification: from a completely fresh clone
git clone "$(git remote get-url origin)" /tmp/fresh && (cd /tmp/fresh && ./scripts/setup) Step 5 โ Check it in CI, not just locally Jump to heading
# The settings that affect committed content should be verified on the server too
- name: Attributes must be present
run: git check-attr -a -- . >/dev/null && test -f .gitattributes
- name: Hooks directory must exist and be executable
run: test -d .githooks && test -x .githooks/pre-commit # And a local drift check people can run themselves
for k in core.hooksPath merge.conflictStyle pull.rebase fetch.prune; do
printf '%-24s %s\n' "$k" "$(git config --get "$k" || echo UNSET)"
done Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
What happens if someone does not run the include? Jump to heading
They keep whatever their machine had, which is the situation you are trying to leave. That is why the audit matters: the include cannot be forced, so the only reliable approach is to make it easy, check it, and enforce anything critical on the server where configuration does not apply at all.
Can the team file override a personal setting? Jump to heading
It can, and that is worth being careful about. An include read later in the resolution order wins, so a repository-local include beats a global setting. Keep the team file to settings where overriding is the intent, and never put identity in it.
Does this work with worktrees? Jump to heading
Yes, and repository-level configuration is shared across worktrees of the same repository, which is usually what you want. Where a worktree genuinely needs a different value, enable extensions.worktreeConfig and set it with --worktree โ covered in Git worktrees and parallel development.
Related Jump to heading
- Git Configuration Management at Scale โ the parent topic and the three mechanisms.
- Bootstrapping a Developer Machine for Git โ the first-run version of this setup.
- Auditing Local Git Configuration Drift โ finding the clones where the include never happened.