Windowsでファイル名を大文字から小文字に変更したらGitが正しく認識しなくなった

ファイル名の表記を整理するため、大文字を含む名前をすべて小文字のkebab-caseへ変更しました。

例えば、次のようなファイル名です。

Bash
sample-Worker.ps1
demo-Parser.ps1
example-Runner.ps1

これらを、

Bash
sample-worker.ps1
demo-parser.ps1
example-runner.ps1

のように小文字に変更しました。

ところが、その後Gitの状態を確認すると不可解な現象が発生しました。VS Codeでは対象ファイルを正常にModifiedとして認識しています。

一方、Git CLIから新しいファイル名を指定すると、追跡されていないような挙動になります。

調査したところ、原因はWindows環境で大文字・小文字だけを変更したリネームがGitインデックスへ正しく反映されていなかったことでした。

目次

発生した問題

最初に気付いたのは、例としてsample-worker.ps1のようなファイルでした。VS Codeのエクスプローラーでは、ファイルがModifiedとして表示されています。

ところが、次のコマンドを実行しても何も表示されません。

Bash
git ls-files -v -- sample-worker.ps1

履歴を確認しても同様でした。

Bash
git log --all --oneline -- sample-worker.ps1

同じGitリポジトリにある他のファイルは正常に認識されています。さらに、同じように大文字を小文字へ変更した別のファイルでも同じ現象が発生しました。

そのため最初は、

Markdown
.gitignore
skip-worktree
assume-unchanged
VS CodeとGit CLIが別のリポジトリを見ている

といった原因も疑いました。

しかし、どれも今回の状況とは一致しませんでした。

原因を調査する

調査1. git statusではModifiedと認識されていた

ファイル名を指定せずに、

Bash
git status --short

を実行すると、Git自身は変更を認識していました。例えば次のような状態です。

Bash
 M sample-worker.ps1
 M logs/example.jsonl
 M templates/example-config.md

ここでさらに調査すると、原因が見えてきました。

Gitインデックスに登録されているファイル名を、大文字・小文字を無視して検索します。

Bash
git ls-files |
    Select-String -Pattern "sample-worker|demo-parser" -CaseSensitive:$false

すると、次のように変更前の名前が表示されました。

Bash
sample-Worker.ps1
demo-Parser.ps1

実際のファイル名は、

Bash
sample-worker.ps1
demo-parser.ps1

です。

つまり、状態は次のようになっていました。

Markdown
実際のファイル名
sample-worker.ps1

Gitインデックス上の名前
sample-Worker.ps1

Gitはファイル自体の変更は認識していますが、インデックスには変更前の大文字・小文字が残っていました。

調査2. core.ignoreCaseはtrueだった

Gitの設定も確認しました。

Bash
git config --get core.ignorecase

結果は、

Bash
true

でした。

Windowsでは通常、ファイル名の大文字・小文字を区別しないファイルシステムとして扱われます。

そのため、

Bash
sample-Worker.ps1
sample-worker.ps1

をWindows側では同じファイルとして扱えます。Gitもcore.ignoreCase=trueの環境では、この特性を考慮してファイルを扱います。

今回、

Markdown
sample-Worker.ps1

sample-worker.ps1

のように大文字・小文字だけを変更したため、Windows上ではファイル名が変わっていても、Git側では同じファイルとして処理され、インデックスに古い表記が残っていました。

調査3. VS CodeがModifiedと表示していたのは正常だった

途中では、

Markdown
VS CodeではModified
Git CLIでは新しい名前が見つからない

という状態だったため、VS Code側のGit連携も疑いました。しかし、VS Codeの表示は正常でした。

Git自身も、

Bash
git status --short

では、

Bash
 M sample-worker.ps1

と変更を検出しています。問題だったのはファイルの変更検出ではなく、Gitインデックスに保存されているパスの大文字・小文字でした。

そのため、

Bash
git log --all --oneline -- sample-worker.ps1

のように新しい名前を指定しても、期待した履歴を取得できませんでした。

Git側には、

Bash
sample-Worker.ps1

という旧表記が残っていたためです。

調査4. kebab-case自体が原因ではなかった

今回、問題が発生した直後は、

Markdown
大文字を混ぜたファイル名を使うと危険なのではないか

と考えました。

そこで、ファイル名をすべて小文字のkebab-caseへ変更しました。

Bash
sample-worker.ps1
demo-parser.ps1
example-runner.ps1

しかし、VS Codeを再起動したあと、変更した複数のファイルで同じ問題が発生しました。

ここで、kebab-caseへの変更そのものではなく、多数のファイルについてケースだけを変更したことが共通点だと分かりました。

例えば、

Markdown
example-Runner.ps1

example-runner.ps1

は、文字列として変化しているのはRからrだけです。

一方、

Markdown
example_runner.ps1

example-runner.ps1

のようにアンダースコアをハイフンへ変更する場合は、今回とは性質が異なります。

問題になったのは、

Markdown
大文字・小文字だけ変更
+
Windows
+
core.ignoreCase=true

という組み合わせでした。

問題対策

1. 一時ファイル名を経由して1ファイルずつ修正する

今回の問題は、実際のファイル名だけが小文字になり、Gitインデックスには変更前の大文字を含む名前が残っていることが原因でした。

そのため、Gitが管理している変更前のファイル名から、一度まったく別の名前へ変更し、その後目的の小文字のファイル名へ変更しました。

例えば、Gitインデックスに、

Bash
example-Runner.ps1

という名前が残っていて、実際のファイル名を、

Bash
example-runner.ps1

へ変更したい場合は、次のようにします。

Bash
git mv example-Runner.ps1 temp.ps1
git mv temp.ps1 example-runner.ps1

つまり、

Markdown
example-Runner.ps1

temp.ps1

example-runner.ps1

という順番です。

一度temp.ps1のような大文字・小文字以外も異なる名前を経由することで、Gitにファイル名の変更を明確に認識させられます。

今回は同様の状態になっていたファイルについて、この方法を使って1ファイルずつ修正しました

なお、変更後の名前から、

Bash
git mv example-runner.ps1 temp.ps1

と実行すると、Gitインデックスにはまだexample-Runner.ps1という名前が登録されているため、次のようなエラーになる場合があります。

Bash
fatal: not under version control, source=example-runner.ps1, destination=temp.ps1

その場合は、Gitが管理している正確な名前を確認します。

Bash
git ls-files |
    Select-String -Pattern "example-runner" -CaseSensitive:$false

例えば、

Bash
example-Runner.ps1

と表示されたら、その名前を最初のgit mvに指定します。

2. core.ignoreCaseをfalseにする方法は採用しない

対策として、

Bash
git config core.ignoreCase false

に変更する方法も考えられます。しかし、今回は採用しませんでした。

core.ignoreCaseは、Gitが使用しているファイルシステムの大文字・小文字の扱いに対応するための設定です。

Windows環境で単純にfalseへ変更すると、別のファイル名判定で問題を引き起こす可能性があります。

今回必要なのはGit全体のケース判定を変更することではなく、

Markdown
Gitインデックスに残った旧ファイル名

現在使用する小文字のファイル名

へ正しく更新することです。

そのため今回はcore.ignoreCase=trueのまま、対象ファイルを、

Markdown
変更前の名前

一時的な名前

変更後の名前

の順番で1ファイルずつgit mvし、Gitインデックスのファイル名を修正しました。

まとめ

Windows環境で既存ファイルの名前を、

Markdown
sample-Worker.ps1

sample-worker.ps1

のように大文字から小文字へ変更したところ、VS CodeではModifiedと表示される一方、

Bash
git ls-files
git log
git mv

で新しいファイル名を正しく扱えない問題が発生しました。

原因を調べると、

Bash
git config --get core.ignorecase

は、

Bash
true

となっており、Gitインデックスには変更前の、

Bash
sample-Worker.ps1
demo-Parser.ps1

という名前が残っていました。今回の原因は、kebab-caseやVS Codeの不具合ではなく、

Markdown
Windows上で
大文字・小文字だけのリネームを行った

Gitは同じファイルとして認識

インデックスには古いケースが残った

というものでした。

今後はファイル名を小文字のkebab-caseへ統一しつつ、既存ファイルについてケースだけを変更するときは、Gitが認識している旧ファイル名からgit mvで明示的に変更するようにします。

今回のようにVS CodeとGit CLIの表示が食い違って見える場合は、

Bash
git status --short
git ls-files
git config --get core.ignorecase

を確認すると、Gitが実際にどのファイル名をインデックスへ記録しているか切り分けやすくなります。

この記事が参考になりましたら、
上記の「いいね」を押していただけると嬉しいです😄
今後の記事作りの励みになります!
目次