Windows大小写不敏感导致的git冲突

来个好玩的,遇到过 git reset --hard 来回翻烧饼的事儿么?每reset一次,文件内容就更改一次,像鬼打墙一样。不信可以在Windows机器上clone下这个repo:

D:\$ git clone https://github.com/finisky/git-case-demo.git
Cloning into 'git-case-demo'...
remote: Enumerating objects: 11, done.
remote: Counting objects: 100% (11/11), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 11 (delta 0), reused 8 (delta 0), pack-reused 0
Unpacking objects: 100% (11/11), 1.85 KiB | 126.00 KiB/s, done.
warning: the following paths have collided (e.g. case-sensitive paths
on a case-insensitive filesystem) and only one from the same
colliding group is in the working tree:

  'File.txt'
  'file.txt'

然后就会发现刚拉的main分支都不干净(橙色),而且git reset --hard也失效了,仔细看才发现,每reset一次,会在大写 File.txt 和小写 file.txt 之间切换,神奇不?

在github上浏览 repo,会发现文件目录与本地的不同:

github上根目录有两个txt File.txtfile.txt ,两个文件夹 Folderfolder,而本地只有一个file.txt 和一个 Folder 。而这也是 git reset --hard 失效的根本原因:

git是大小写敏感的,而Windows系统是大小写不敏感的

所以这个问题只会出现在Windows上使用git的情况,Windows将 File.txtfile.txt 认为是同一文件,所以reset失效了。Linux/Mac与git一致,大小写敏感,所以不存在这样的问题。Folderfolder 也是同理,在Windows上这两个文件夹会被合并成同一个。

这个问题常常会在配置CICD时发现,本地是Windows环境,而部署编译的机器是Linux,会提示找不到文件,但在Windows环境中看文件明明是存在的……

解决方案

在Linux/Mac上把文件统一当然可以解决问题,但如果手边没有Linux怎么办?

git是大小写敏感的,因此可以通过 git mv 解决问题。

同名文件冲突

假设保留小写文件,将大写文件覆盖之:

(fixconflicts -> origin)D:\git-case-demo$ git mv File.txt file.txt

(fixconflicts -> origin)D:\git-case-demo$ git status
On branch fixconflicts
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    File.txt
        modified:   file.txt

注意,直接在Windows中删除 File.txt 是不行的,两个文件都会被删除:

(fixconflicts -> origin)D:\git-case-demo$ git status
On branch fixconflicts
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    File.txt
        deleted:    file.txt

no changes added to commit (use "git add" and/or "git commit -a")

最后,commit change,分支上就干净了。

合并同名文件夹

可以通过两次 git mv 完成:

$ git mv Folder temp
$ git mv temp folder

先将其重命名成完全不同的文件夹,再合并文件夹内容即可:

(fixconflicts -> origin)D:\git-case-demo$ git mv Folder temp

(fixconflicts -> origin)D:\git-case-demo$ git status
On branch fixconflicts
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        renamed:    Folder/file2.txt -> temp/file2.txt

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    folder/file1.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        temp/file1.txt


(fixconflicts -> origin)D:\git-case-demo$ git mv temp folder

(fixconflicts -> origin)D:\git-case-demo$ git status
On branch fixconflicts
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        renamed:    Folder/file2.txt -> folder/file2.txt

Demo

Demo Repo: https://github.com/finisky/git-case-demo

其中fixconflicts分支已按本文解决了问题,供参考。

参考

# Git is case-sensitive and your filesystem may not be - Weird folder merging on Windows

# Git on Windows: "merging" 2 directories with the same name but different case

# Git case sensitivity