Auditing local Git configuration drift Jump to heading
Configuration that was correct in March is not necessarily correct in September. Machines get rebuilt, tools rewrite files, a pairing session leaves someone elseβs identity behind, and a repository cloned in a hurry never got the setup command. None of it announces itself β the first symptom is usually a hook that did not run or a commit with the wrong author. An audit turns that into something noticed at the right time, within Git configuration management at scale.
When to use this approach Jump to heading
- Team configuration exists and you want to know whether it is in effect.
- Hooks are relied upon and you cannot tell who has them installed.
- Someoneβs commits recently arrived with the wrong identity.
- Machines are rebuilt or reimaged regularly.
- If nothing in your setup depends on local configuration, there is nothing to drift.
Step 1 β Decide what is policy and what is preference Jump to heading
Auditing preferences is how an audit gets ignored. Check only the settings where a wrong value produces a wrong result.
# scripts/audit-config β the policy set, and nothing else.
POLICY="
core.hooksPath|.githooks|hooks would not run
merge.conflictStyle|zdiff3|conflicts hide the common ancestor
fetch.prune|true|deleted branches linger as stale refs
rebase.updateRefs|true|stacked branches break on rebase
" # Verification: every entry answers 'what goes wrong if this is unset?'
printf '%s\n' "$POLICY" | awk -F'|' 'NF==3 {print $1 " β " $3}' Step 2 β Write a check that reports rather than nags Jump to heading
#!/usr/bin/env sh
# scripts/audit-config β prints a short report; exits non-zero only on real problems.
set -eu
fail=0
check() {
key=$1; want=$2; why=$3
have=$(git config --get "$key" 2>/dev/null || echo 'UNSET')
if [ "$have" = "$want" ]; then
printf ' ok %-22s %s\n' "$key" "$have"
else
printf ' DRIFT %-22s %-12s (want %s β %s)\n' "$key" "$have" "$want" "$why"
fail=1
fi
}
echo "configuration audit for $(git rev-parse --show-toplevel)"
check core.hooksPath .githooks "hooks would not run"
check merge.conflictStyle zdiff3 "conflicts hide the common ancestor"
check fetch.prune true "stale remote refs accumulate"
check rebase.updateRefs true "stacked branches break on rebase"
email=$(git config --get user.email || echo UNSET)
case "$email" in
*@acme.com|*@users.noreply.github.com) printf ' ok %-22s %s\n' user.email "$email" ;;
*) printf ' DRIFT %-22s %s (expected a work address)\n' user.email "$email"; fail=1 ;;
esac
[ "$fail" -eq 0 ] && echo " all policy settings in effect" || echo " run ./scripts/setup to fix"
exit "$fail" # Verification: the audit passes on a correctly configured clone
sh scripts/audit-config Step 3 β Run it where people already are Jump to heading
An audit nobody runs finds nothing. The two places it costs nothing are a hook and the pipeline.
# .githooks/pre-push β cheap, occasional, and at a moment where a fix is convenient
if ! sh "$(git rev-parse --show-toplevel)/scripts/audit-config" >/tmp/audit.txt 2>&1; then
cat /tmp/audit.txt >&2
echo "pre-push: configuration drift detected β pushing anyway." >&2
# Deliberately does not block: drift is a problem, not an emergency.
fi # Verification: the report appears on push without blocking it
git push --dry-run origin HEAD 2>&1 | head Not blocking is a deliberate choice. Blocking a push over a configuration setting produces a rushed workaround; reporting it at a moment when the person has thirty seconds produces a fix.
Step 4 β Report the fleet, not just one clone Jump to heading
One personβs clone is a sample of one. A short aggregation tells you whether the setup process is working.
# Each machine appends one line to a shared file, via the pre-push hook
report=$(printf '%s|%s|%s|%s' \
"$(git config --get user.email || echo unset)" \
"$(git --version | awk '{print $3}')" \
"$(git config --get core.hooksPath || echo unset)" \
"$(date -u +%Y-%m-%d)")
echo "$report" >> "$HOME/.cache/git-audit.log" # Or, without collecting anything centrally: ask once, tally the answers
# A one-command report people can paste:
printf '%s | git %s | hooks=%s | style=%s\n' \
"$(git config --get user.email)" "$(git --version | awk '{print $3}')" \
"$(git config --get core.hooksPath || echo unset)" \
"$(git config --get merge.conflictStyle || echo unset)" SAFETY WARNING β do not collect configuration reports centrally without saying so. A Git configuration contains identities, key paths, remote URLs and sometimes credential helper settings, so quietly gathering it from developer machines is surveillance regardless of intent. Ask people to run a command and share the output, or collect only an anonymous pass/fail count.
Step 5 β Fix the cause, not just the instance Jump to heading
Recurring drift means the setup process has a gap. Treat each finding as evidence about the process.
# The three questions worth asking about any recurring drift:
# Was the setup step ever run on this clone? β make setup unavoidable
# Did a tool overwrite the value? β move it to attributes
# Did the value change and nobody re-ran setup? β make the audit noisier # Making setup harder to skip: run it from the package manager's install step
npm pkg set scripts.prepare='./scripts/setup' # Verification: a fresh clone is configured after a plain dependency install
git clone "$(git remote get-url origin)" /tmp/fresh && (cd /tmp/fresh && npm ci >/dev/null && sh scripts/audit-config) Hooking setup to the dependency install is the single most effective change available, because it turns a step people must remember into one that happens as a side effect of something they already do. It is also how hook managers install themselves, which is why hooks tend to be present on machines where other configuration is not.
Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Should the audit fail a build? Jump to heading
Only for settings whose absence means a guarantee you rely on is not holding β an unset hooks path, for example, means local checks are not running at all. For everything else, a report is more effective, because a failing build over a cosmetic setting teaches people to look for the flag that disables the audit.
How often should it run? Jump to heading
On push is frequent enough to catch drift within a day and rare enough to stay unobtrusive. Running on every commit produces a message people stop reading within a week, which is the outcome to avoid.
What if someone disagrees with a policy setting? Jump to heading
Then it is a pull request against the committed configuration, with a reason. That is the main advantage of keeping policy in a file rather than in documentation: disagreement produces a discussion and a decision rather than a quiet local override nobody knows about.
Related Jump to heading
- Git Configuration Management at Scale β the parent topic and what belongs where.
- Bootstrapping a Developer Machine for Git β the setup this audit verifies.
- Disabling Git Hooks Temporarily Without Breaking the Team β the legitimate reason a hooks path might be unset.