Overview
Case collisions happen when a repository contains two paths that differ only by letter case (e.g., README.md vs readme.md). On case-insensitive filesystems (Windows NTFS by default, macOS APFS/HFS+ default), these map to the same on-disk file, causing checkout, merge, or rename failures.
This guide shows how to detect, fix, and prevent these collisions in Git across Linux, macOS, and Windows.
Platform defaults:
- Linux: case-sensitive (ext4, xfs)
- macOS (default): case-insensitive but case-preserving (APFS/HFS+)
- Windows (default): case-insensitive but case-preserving (NTFS)
Quickstart (most common fixes)
- Case-only rename on case-insensitive FS:
- git mv old tmp && git mv tmp Old && git commit -m "Case-only rename via hop"
- Resolve existing collision in repo (created on Linux, breaks Windows/macOS):
- On a case-sensitive machine, rename one file to a unique name, commit, push.
- Detect collisions quickly:
- git ls-files | awk '{k=tolower($0); a[k]=a[k]?a[k]"\n"$0:$0} END{for(k in a) if(index(a[k],"\n")) print a[k]}'
- Avoid breaking Windows/macOS devs:
- Enforce a no-collision rule with a pre-commit or CI check (script below).
Minimal Working Example
This demo simulates a collision on Linux and shows how to fix it so Windows/macOS can clone.
# 1) Create a repo with colliding paths (on Linux or a case-sensitive volume)
mkdir case-collide && cd case-collide
git init -q
printf 'hello\n' > readme.md
git add readme.md && git commit -q -m "add readme.md"
cp readme.md README.md
git add README.md && git commit -q -m "add README.md (collides on Windows/macOS)"
echo "-- Detect collisions (case-folded duplicates) --"
git ls-files | awk '{k=tolower($0); a[k]=a[k]?a[k]"\n"$0:$0} END{for(k in a) if(index(a[k],"\n")) print a[k]}'
# 2) Fix: disambiguate by renaming one of them
# Use a two-step rename if changing only case on a case-insensitive FS;
# here we choose a distinct name to be portable everywhere
git mv README.md README-UPPER.md
git commit -m "rename to avoid case collision"
# 3) Push; Windows/macOS can now clone/checkout safely
Expected result: The awk line prints both paths, confirming a collision. After the rename and commit, the collision disappears.
Common Scenarios and Fixes
1) Case-only rename fails locally
Symptoms on Windows/macOS: git mv foo.txt Foo.txt does nothing or results in odd status; or checkout creates/overwrites unexpectedly.
Fix (two-hop rename):
git mv foo.txt foo.tmp && git mv foo.tmp Foo.txt
git commit -m "Case-only rename via intermediate path"
Notes:
- Use an intermediate name that does not currently exist.
- git mv -f may still fail on some tools; the two-hop method is most reliable.
2) Checkout or merge fails due to collision from another platform
Symptoms:
- The following untracked working tree files would be overwritten by checkout
- unable to create file ...: File exists
- updates were rejected because the working tree is not clean (after collision side-effects)
Fix (make the repo portable at the source):
- On a case-sensitive system (Linux or a case-sensitive APFS volume), create a commit that disambiguates the paths:
# pick one to keep and rename the other
git mv README.md README-LEGACY.md
# or consolidate content, then remove the duplicate
git commit -m "Resolve case collision by renaming"
# push so others can fetch a portable tree
git push
- After that, Windows/macOS developers can fetch and checkout without error.
3) Merge introduces a case-only rename conflict
Scenario: Branch A has README.md; Branch B renames it to Readme.md only by case.
Fix on a case-insensitive FS:
# On branch B, before merging:
git mv Readme.md Readme.tmp && git mv Readme.tmp README.md
git commit -m "Normalize filename before merge"
# Now merge B into A normally
Alternatively, perform the two-hop rename on the branch where you resolve the conflict, then complete the merge.
Detection and Prevention
One-off detection in a working tree
# List groups of files that collide when lowercased
git ls-files | awk '{k=tolower($0); a[k]=a[k]?a[k]"\n"$0:$0} END{for(k in a) if(index(a[k],"\n")) print "COLLISION:\n" a[k] "\n"}'
Pre-commit hook to block collisions
Place in .git/hooks/pre-commit and make executable:
#!/usr/bin/env bash
set -euo pipefail
mapfile -t files < <(git diff --cached --name-only)
awk_script='{
k=tolower($0);
if (seen[k] && seen[k]!=$0) { print "Case collision:", seen[k], "and", $0; exit 1 };
seen[k]=$0
}'
printf '%s
' "${files[@]}" | awk "$awk_script"
This blocks any commit that introduces a case-duplicate among staged paths.
CI check (entire repo)
collisions=$(git ls-files | awk '{k=tolower($0); a[k]=a[k]?a[k]","$0:$0} END{for(k in a) if(index(a[k],",")) print a[k]}')
if [ -n "$collisions" ]; then
echo "Case-colliding paths detected:"; echo "$collisions"; exit 1;
fi
core.ignorecase explained
- On case-insensitive filesystems, Git auto-sets core.ignorecase=true. Leave it as-is.
- For Linux repos that must stay portable to Windows/macOS, temporarily setting core.ignorecase=true can help reveal potential issues during testing, but do not commit with that assumption on a case-sensitive FS.
# Temporary local check (Linux):
git config core.ignorecase true
# run your collision detection and tests
# then revert
git config --unset core.ignorecase
History Cleanup (if collisions exist across history)
If historical commits contain collisions, some checkouts will always fail on Windows/macOS. Consider rewriting history to rename paths:
Using git filter-repo (recommended):
# Install per your platform, then:
git filter-repo --path-rename README.md:README-LEGACY.md
This rewrites all commits, renaming the path consistently. Force-push and coordinate with the team.
Fallback with git filter-branch (slower, legacy):
git filter-branch --index-filter \
'git ls-files -s | sed "s#\tREADME.md#\tREADME-LEGACY.md#" | GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info && mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' \
-- --all
Platform Notes and Tips
- Windows per-directory case sensitivity can be enabled, but mixed tooling may not honor it. Prefer repo-level fixes.
- macOS case-sensitive volumes can be created, but teammates may not have them. Use them mainly to author fixes.
- core.protectNTFS=true (default on Git for Windows) guards against other NTFS edge cases; keep it enabled.
Pitfalls
- Do not toggle core.ignorecase globally on Windows/macOS; it won’t make the filesystem case-sensitive and can confuse Git.
- A case-only rename in a single commit on case-insensitive FS may be ignored. Always use the two-hop rename.
- Sparse checkout and .gitattributes do not solve collisions; they can only limit visibility or attributes, not path identity.
- Clean up untracked files if they shadow paths during checkout: git clean -fd (review with -n first).
Performance Notes
- Detecting collisions scales with the number of tracked files. Prefer streaming methods (git ls-files | awk) over invoking Git per file.
- For large histories, git filter-repo is significantly faster and safer than filter-branch. Run it locally and push once, rather than many small rewrites.
- Avoid repeated full-repo scans in pre-commit; only check staged paths for speed.
FAQ
- Q: Can .gitattributes prevent case collisions?
- A: No. Use hooks/CI to enforce and human-reviewed renames to resolve.
- Q: Should I force core.ignorecase=false on Windows?
- A: No. Leave the default true; use proper renames instead.
- Q: Can I clone a repo with collisions on Windows?
- A: Not safely. Ask a maintainer on a case-sensitive system to disambiguate and push a fix.
- Q: Is there a one-command fix for case-only rename?
- A: The reliable method is the two-hop rename: tmp name, then final name, commit.