The Problem
You renamed utils.js to Utils.js โ one capital letter. Run git status: nothing. Git acts like it never happened.
Locally, everything works. Then you push to a Linux CI server and the build breaks โ imports fail because the server looks for Utils.js but the repo still has utils.js. Classic case of a bug that hides locally and bites you in production.
Why This Happens
Windows (NTFS) and macOS (HFS+/APFS in default configuration) use case-insensitive filesystems. To the OS, file.txt and File.txt are the same file. Since Git relies on the filesystem to detect changes, it never sees a difference.
There's also a Git config option in the mix: core.ignoreCase. On Windows and macOS, Git sets this to true automatically at install time. That's intentional โ it prevents false positives on case-insensitive systems. But it also means Git will silently skip real case renames.
Linux is different. Its filesystems are case-sensitive, so file.txt and File.txt are two distinct files. When your code runs on Linux โ servers, Docker containers, GitHub Actions โ it looks for the exact filename from the repo. If Git never tracked the rename, the old casing is still there, and references break.
Fix 1: Use git mv with a Temporary Name (Recommended)
Rename through a throwaway intermediate name. This tricks Git into recording two separate renames instead of one invisible one.
# Step 1: Rename to a temporary name
git mv utils.js utils_temp.js
# Step 2: Rename from temp to the final name
git mv utils_temp.js Utils.js
# Step 3: Verify Git tracked it
git status
The staging area should now show:
Changes to be committed:
renamed: utils.js -> Utils.js
# Step 4: Commit
git commit -m "Rename utils.js to Utils.js"
Fix 2: Set core.ignoreCase = false and Force-Rename
Another option โ flip Git into case-sensitive mode just for this operation:
# Make Git case-sensitive for this repo
git config core.ignoreCase false
# Now do the rename directly
git mv utils.js Utils.js
git status
After committing, you can leave core.ignoreCase false or flip it back. Fair warning: leaving it as false on a case-insensitive filesystem sometimes causes Git to report phantom changes on unmodified files. Fix 1 avoids that entirely.
# Optional: revert the config after committing
git config core.ignoreCase true
Fix 3: Already Committed with the Wrong Case?
If the file was already committed and pushed with the wrong casing, the index needs to be updated directly:
# Remove the old file from Git's index (not from disk)
git rm --cached utils.js
# Add the file under the correct new name
git add Utils.js
# Commit the fix
git commit -m "Fix: rename utils.js to Utils.js (case correction)"
If Git complains that the file doesn't exist, try the nuclear option โ re-index everything:
# On macOS/Windows when the file shows no changes:
git rm -r --cached .
git add .
git commit -m "Fix file casing in Git index"
Warning: git rm -r --cached . unstages everything and re-adds it. Your actual files are untouched โ this only affects the Git index. Still, run git status before committing to catch any unrelated files you don't want to include.
Verification: Confirm the Fix Worked
Check that the rename shows up correctly in the last commit:
# Check the latest commit shows the rename
git log --oneline --name-status -1
Expected output:
a3f9c12 Rename utils.js to Utils.js
R100 utils.js Utils.js
R100 means Git recorded a rename with 100% similarity. Seeing D (deleted) and A (added) instead isn't ideal, but it still works โ the file will exist with the correct name on Linux after a fresh clone.
Push and verify on the remote:
git push origin main
# Then check GitHub/GitLab โ the file should now show the new casing
Prevention: Add a Git Alias for Case Renames
Rename files by case regularly? Add this alias to your global config once and forget about the two-step dance:
git config --global alias.mvcased '!f() { git mv "$1" "${1}_tmp" && git mv "${1}_tmp" "$2"; }; f'
Usage:
git mvcased utils.js Utils.js
Quick Reference
- Scenario 1 โ File not yet committed:
git mv file.txt temp.txt && git mv temp.txt File.txt - Scenario 2 โ Committed, not pushed: Set
core.ignoreCase false, usegit mv, then reset the config - Scenario 3 โ Already pushed:
git rm --cached oldname+git add newname+ commit + push - Scenario 4 โ Multiple files affected:
git rm -r --cached . && git add .to re-index everything

