WindowsとmacOSで'file.txt'を'File.txt'にリネームしてもgit statusに変更が表示されない

beginner📦 Git2026-04-21| Windows(NTFS)およびmacOS(HFS+/APFSデフォルト大文字小文字非区別)上のGit、Git 2.x

Error Message

git status shows no changes after renaming 'file.txt' to 'File.txt' on Windows/macOS
#git#大文字小文字区別#リネーム#設定

問題の概要

utils.jsUtils.js にリネームした — 大文字1文字の違い。git status を実行しても何も表示されない。まるで何も起きていなかったかのようにGitは振る舞う。

ローカルでは問題なく動作する。しかしLinuxのCIサーバーにプッシュするとビルドが壊れる — サーバーは Utils.js を探しているのに、リポジトリにはまだ utils.js が残っているためインポートが失敗する。ローカルでは潜伏し、本番環境で牙をむく典型的なバグだ。

なぜこうなるのか

Windows(NTFS)とmacOS(デフォルト設定のHFS+/APFS)は大文字・小文字を区別しないファイルシステムを使用している。OSにとって file.txtFile.txt は同じファイルだ。Gitはファイルシステムに依存して変更を検出するため、違いを認識できない。

さらに関係するGitの設定オプションがある: core.ignoreCase。WindowsとmacOSでは、インストール時にGitがこれを自動的に true に設定する。これは意図的な動作で、大文字・小文字を区別しないシステムでの誤検知を防ぐためだ。しかしその副作用として、Gitは実際の大文字・小文字のリネームを無視してしまう。

Linuxは異なる。ファイルシステムが大文字・小文字を区別するため、file.txtFile.txt は別々のファイルとして扱われる。コードがLinux上で動作する場合 — サーバー、Dockerコンテナ、GitHub Actions — リポジトリに記録されたファイル名を正確に参照する。Gitがリネームを追跡していなければ古いケーシングのままになり、参照が壊れる。

修正方法1: 一時的な名前を使った git mv(推奨)

一時的な中間名を経由してリネームする。これによりGitは1つの見えないリネームではなく、2つの別々のリネームとして記録するよう誘導される。

# ステップ1: 一時的な名前にリネーム
git mv utils.js utils_temp.js

# ステップ2: 一時名から最終的な名前にリネーム
git mv utils_temp.js Utils.js

# ステップ3: Gitが追跡していることを確認
git status

ステージングエリアには以下のように表示されるはずだ:

Changes to be committed:
  renamed:    utils.js -> Utils.js
# ステップ4: コミット
git commit -m "Rename utils.js to Utils.js"

修正方法2: core.ignoreCase = false に設定して強制リネーム

別の方法として、この操作だけのためにGitを大文字・小文字を区別するモードに切り替える:

# このリポジトリでGitを大文字・小文字を区別するように設定
git config core.ignoreCase false

# 直接リネームを実行
git mv utils.js Utils.js

git status

コミット後、core.ignoreCase false のままにするか元に戻すかは任意だ。注意点として: 大文字・小文字を区別しないファイルシステムで false のままにすると、変更していないファイルに対してGitが幽霊のような差分を報告することがある。修正方法1ではその問題が完全に回避できる。

# オプション: コミット後に設定を元に戻す
git config core.ignoreCase true

修正方法3: すでに間違ったケーシングでコミットしてしまった場合

ファイルがすでに間違ったケーシングでコミット・プッシュされている場合、インデックスを直接更新する必要がある:

# Gitのインデックスから古いファイルを削除(ディスクからは削除しない)
git rm --cached utils.js

# 正しい新しい名前でファイルを追加
git add Utils.js

# 修正をコミット
git commit -m "Fix: rename utils.js to Utils.js (case correction)"

ファイルが存在しないとGitが文句を言う場合は、最終手段として全てを再インデックスする:

# macOS/Windowsでファイルに変更が表示されない場合:
git rm -r --cached .
git add .
git commit -m "Fix file casing in Git index"

警告: git rm -r --cached . は全てのステージングを解除して再追加する。実際のファイルは変更されない — Gitのインデックスにのみ影響する。それでも、含めたくない無関係なファイルを見落とさないよう、コミット前に git status を実行して確認すること。

確認: 修正が正しく適用されたか検証する

最後のコミットにリネームが正しく記録されているか確認する:

# 最新のコミットにリネームが表示されているか確認
git log --oneline --name-status -1

期待される出力:

a3f9c12 Rename utils.js to Utils.js
R100    utils.js    Utils.js

R100 はGitが100%の類似度でリネームを記録したことを意味する。D(削除)と A(追加)が表示される場合は理想的ではないが、それでも機能する — 新規クローン後にLinux上で正しい名前のファイルが存在することになる。

プッシュしてリモートで確認する:

git push origin main

# その後GitHub/GitLabで確認 — ファイルが新しいケーシングで表示されているはずだ

予防策: 大文字・小文字リネーム用のGitエイリアスを追加する

定期的に大文字・小文字のリネームを行う場合は、このエイリアスをグローバル設定に一度追加すれば、二段階の手順を気にする必要がなくなる:

git config --global alias.mvcased '!f() { git mv "$1" "${1}_tmp" && git mv "${1}_tmp" "$2"; }; f'

使用方法:

git mvcased utils.js Utils.js

クイックリファレンス

  • シナリオ1 — まだコミットしていないファイル: git mv file.txt temp.txt && git mv temp.txt File.txt
  • シナリオ2 — コミット済み、未プッシュ: core.ignoreCase false に設定し、git mv を使用後、設定を元に戻す
  • シナリオ3 — すでにプッシュ済み: git rm --cached oldname + git add newname + コミット + プッシュ
  • シナリオ4 — 複数ファイルが影響を受けている: git rm -r --cached . && git add . で全てを再インデックス

Related Error Notes