🙆

同名だが大小文字が異なるファイルがGitリポジに

に公開2

背景

仕事中に起きたことです。

git pullでローカのリポジトリを更新した後、なぜかローカルのREADME.mdに変更が発生したとGitから報告されました。変更内容を確認したところ、ファイル全体が別の内容に上書きされたようで、自分が加えた変更ではないです。

なぜだろう?と疑問を持ちつつ、その変更をGitで取り消しました。

そして直ぐに、今度はreadme.mdに変更発生の報告が出ました。さっきと同じく、ファイル全体が上書きされました。変更方向はさっきと真逆です。

えっ?と続けて疑問を持ちつつ、また変更を取り消しました。

そしてまた直ぐに、つい取り消したばっかりのREADME.mdの変更が戻ってきました。

起因

何回も「なんで?」を繰り返した後、ようやく気付きました。

リモートのGitリポジトリに、README.mdreadme.md二つのファイルが同時に入っています。

一方、私のローカルはWindowsで、ファイル名に大小文字を区別しないシステムです。

リモートのリポジトリと同期したら、同名だが大小文字が異なるファイルが私のローカルで喧嘩してしまいます。

昔のエンジニア仕事でもこんなことがありましたね。

解決策

1)まずはリモートで片方のファイルをメンバーに削除してもらいます。

それはGitリポジトリのWeb画面(私たちの場合はGitHub)かLinuxでできます。

2)ローカルのリポジトリを、両方のファイルが同時に入ってないバージョン(コミット)に戻します。

やり方はいくつかあります。

git reset --hard <コミット>が一番手っ取り早いです。<コミット>HEAD^かコミットIDです。自分はよくHEAD^^^^で、複数の^を使って、確実だろうと思う数個前のコミットに戻します。

もう一つのやり方は、両方のファイルが同時に入ってない、別のローカルブランチ(あるなら)に切り替え(チェックアウト)ます。その後、

  • git reset --hard
  • git restore
  • git checkout -- "*"

でローカルのリポジトリの状態をリセットします。

もちろん、ローカルのリポジトリを丸ごと削除も一つの手です(痛いですが)。

3)git pullでリモートのリポジトリともう一度同期します。

別のブランチにチェックアウトしているなら、元のローカルブランチを削除した後、git fetchしてgit checkoutします。

これでローカルの問題は解消します。

再発防止策?

まだ実践していないが、いくつかの再発防止策を考えました。

ローカルフォルダにおける策

調べたところ、Gitリポジトリではプロジェクト全員に強制的に大小文字区別させる設定はありません。

しかし、ローカルのWindowsでは一つ再発防止策ができそうです。(全然関係ありませんが、Godotのドキュメントに見つかりました。)

Windows 10以降では、大文字と小文字の区別に関するミスをさらに回避するために、プロジェクトフォルダでも大文字と小文字を区別するように設定できます。Windows Subsystem for Linux機能を有効にした後、PowerShellで次のコマンドを実行します。

# To enable case-sensitivity:
fsutil file setcasesensitiveinfo <path to project folder> enable

# To disable case-sensitivity:
fsutil file setcasesensitiveinfo <path to project folder> disable

Windows Subsystem for Linux を有効にしていない場合は、管理者として実行している PowerShell に次の行を入力し、要求されたら再起動します。

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

リモートリポジトリにおける策

Gitリポジトリではプロジェクト全員に強制的に大小文字区別させる設定はない、と書きました。

しかし、pre-commitのcheck-case-conflictフックを使えば、コミットする時に大小文字が異なる同名ファイルの存在をチェックして、存在する場合はコミットを拒否できます。(rakiさんのコメントで知りました。ありがとうございます!)

GitHubを使う場合は更にpre-commit.comを導入し、CI/CDのタイミングにチェックを行うこともできそうです。

Discussion

rakiraki

readme.md が要らないファイルだったのなら、Mac ユーザ(たぶん後から readme.md を作った人?)に、git mv readme.md README_readme.md みたいにリネームしてもらってコミットして貰えばいいような?(歴史を改変するより作業記録を残すほうが意味がある)
こんな感じ
https://www.geeksforgeeks.org/git/how-to-commit-case-sensitive-only-filename-changes-in-git/

また、 pre-commit には check-case-conflict ってフックがあるので、pre-commit を導入したなら1行で大丈夫
https://github.com/pre-commit/pre-commit-hooks?tab=readme-ov-file#check-case-conflict

豚&紙箱豚&紙箱

コメントありがとうございます。

解決策の

3)リモートのリポジトリともう一度同期します。

git push --forceで歴史を改変すると認識されたかと思いますが、当時は歴史を改変しないで、git pullでリモートの歴史をもう一回ローカルに同期しました。誤解させやすい気がしました。文章を修正します。

check-case-conflictの補足、ありがとうございます!こちらも追記します!