Detecting conflict-prone files from history Jump to heading
Git does not record conflicts. A merge commit looks the same whether it resolved cleanly or cost someone an hour, so the obvious query is unavailable and teams fall back on impressions β which are dominated by the most recent painful merge rather than by the pattern. There are good proxies, all computable from history alone, and together they identify collision points reliably enough to act on. This recipe builds the measurement, within conflict prevention by design.
When to use this approach Jump to heading
- You suspect a few files cause most of the pain but cannot name them.
- Before restructuring anything, so the change targets something real.
- After a remedy, to check whether it worked.
- When deciding where to apply attributes or a merge driver.
- If your repository is a few months old, there is not enough history for the signal to be meaningful.
Step 1 β Count appearances in merge commits Jump to heading
The bluntest proxy: files listed in the diff of a merge commit are files the merge had to reconcile.
git log --merges --since='1 year ago' --name-only --format='' \
| grep -v '^$' | sort | uniq -c | sort -rn | head -20 # Restrict to non-trivial merges, which is a better signal
git log --merges --since='1 year ago' --format='%H' | while read -r m; do
n=$(git show --name-only --format='' "$m" | grep -c .)
[ "$n" -gt 0 ] && git show --name-only --format='' "$m"
done | sort | uniq -c | sort -rn | head -20 # Verification: the top entries should be recognisable, not random A merge commitβs diff is empty when the merge was a clean fast-forward-style combination, so files appearing there are files where the merge machinery had to do work β not proof of a conflict, but strongly correlated.
Step 2 β Count distinct authors per file Jump to heading
The best single predictor. A file many people edit conflicts; a file one person edits does not, however large it is.
git log --since='1 year ago' --format='@%an' --name-only \
| awk '/^@/{a=substr($0,2); next} NF{print a "\t" $0}' \
| sort -u | cut -f2 | sort | uniq -c | sort -rn | head -20 # Combine with size, since a small file with many authors is the worst case
git log --since='1 year ago' --format='@%an' --name-only \
| awk '/^@/{a=substr($0,2); next} NF{print a "\t" $0}' | sort -u | cut -f2 \
| sort | uniq -c | sort -rn | head -10 | while read -r n f; do
[ -f "$f" ] && printf '%2s authors %5s lines %s\n' "$n" "$(wc -l < "$f")" "$f"
done Step 3 β Find files that change together Jump to heading
Co-change reveals coupling that the directory structure hides, and coupled files conflict as a group.
git log --since='1 year ago' --format='---' --name-only \
| awk '/^---/{if (n>1 && n<12) for (i=1;i<=n;i++) for (j=i+1;j<=n;j++) print f[i] "\t" f[j];
n=0; next}
NF{f[++n]=$0}' \
| sort | uniq -c | sort -rn | head -15 # Verification: the top pairs should make architectural sense Pairs that change together in most commits are effectively one unit. If they are also edited by many people, the pair is a collision point even though neither file looks remarkable alone.
Step 4 β Separate structural from circumstantial Jump to heading
A file that has always conflicted is structural. One that conflicted last quarter is a busy quarter.
# Split the window in half and compare the rankings
for window in '2 years ago::1 year ago' '1 year ago::now'; do
since=${window%%::*}; until=${window##*::}
echo "--- $since to $until"
git log --merges --since="$since" --until="$until" --name-only --format='' \
| grep -v '^$' | sort | uniq -c | sort -rn | head -5
done # Persistent across both windows AND across changing authors β structural
git log --since='2 years ago' --format='@%an' --name-only -- src/routes/index.ts \
| awk '/^@/{print substr($0,2)}' | sort -u | wc -l Step 5 β Turn it into a scheduled report Jump to heading
A measurement run once produces a conversation; run quarterly it produces a trend.
#!/usr/bin/env sh
# scripts/conflict-report β the three proxies, in one output.
set -eu
since=${1:-'1 year ago'}
echo "=== files in merge commits ==="
git log --merges --since="$since" --name-only --format='' \
| grep -v '^$' | sort | uniq -c | sort -rn | head -10
echo; echo "=== distinct authors per file ==="
git log --since="$since" --format='@%an' --name-only \
| awk '/^@/{a=substr($0,2); next} NF{print a "\t" $0}' \
| sort -u | cut -f2 | sort | uniq -c | sort -rn | head -10
echo; echo "=== attributes already in place ==="
git check-attr merge -- $(git log --merges --since="$since" --name-only --format='' \
| grep -v '^$' | sort | uniq -c | sort -rn | head -5 | awk '{print $2}') 2>/dev/null # Verification: the report runs in seconds and fits on a screen
sh scripts/conflict-report '6 months ago' | head -30 The last section of the report matters as much as the first two: a file at the top that already has a merge attribute is telling you the attribute is not covering the case, which is a different problem from a file nobody has addressed.
Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Why does Git not record conflicts? Jump to heading
Because a merge commit records a result, not the process that produced it β the same commit could have been created cleanly, by hand, or by a tool. The only durable trace is rerereβs cache, which exists solely on the machine that resolved the conflict and only when rerere is enabled.
Can rerereβs cache be used as a data source? Jump to heading
On one machine, yes: the number of entries in .git/rr-cache is a direct count of distinct conflicts that person resolved. It is not shared by default and does not aggregate across a team, though sharing rerere resolutions across a team describes a setup where it could be.
What about very large repositories where these commands are slow? Jump to heading
Restrict by path and by window, and run the report on a schedule rather than interactively. The author-count query is the expensive one; limiting it to the top twenty files from the merge query gives nearly the same answer for a fraction of the work.
Related Jump to heading
- Conflict Prevention by Design β the parent topic and what to do with the findings.
- Structuring a Codebase to Reduce Merge Conflicts β the remedy for a structural collision point.
- Reducing Lockfile Churn in a Busy Repository β the file that tops almost every one of these reports.