Git 与文件名中的 ä 混淆

Git gets confused with ä in file name

由于文件名带有 ä,我的处境很糟糕 git。这是一个可能已经存在多年的旧文件:

因此它被标记为 34 未跟踪,但如果我删除它,它会被标记为已删除,但标记为 40。非常混乱。我不太关心这个文件,但想知道未来......

~/d/p/uniply ❯❯❯ git status                                                                           master ◼
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
  (use "git add <file>..." to include in what will be committed)

        "deployment/ec2/Prods34ttning"

nothing added to commit but untracked files present (use "git add" to track)
~/d/p/uniply ❯❯❯ rm deployment/ec2/Prodsättning                                                       master ◼
~/d/p/uniply ❯❯❯ git status                                                                           master ✖
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    "deployment/ec2/Prodsa40ttning"

no changes added to commit (use "git add" and/or "git commit -a")
~/d/p/uniply ❯❯❯ git checkout -- deployment/ec2                                                       master ✖
~/d/p/uniply ❯❯❯ git status                                                                           master ◼
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
  (use "git add <file>..." to include in what will be committed)

        "deployment/ec2/Prods34ttning"

nothing added to commit but untracked files present (use "git add" to track)

简短版本:您显然正在使用 Mac,它将所有文件名转换为 NFD,并且 git 用于盲目地将文件名视为字节但现在将文件名转换为 NFC Mac 为了更好地与其他系统兼容。因此,提交中的旧路径会表现得很奇怪。

$ python3
>>> import unicodedata
>>> unicodedata.normalize('NFC', b'a40'.decode()).encode()
b'\xc3\xa4'
>>> unicodedata.normalize('NFD', b'34'.decode()).encode()
b'a\xcc\x88'

这些格式的全称是规范化形式 D(规范分解)和规范化形式 C(规范化分解,然后是规范化Composition),它们在 UAX #15.

中定义

类似的事情可能发生在 case-insensitive 文件系统上 — 尝试检查 Windows 或 Mac 上的 Linux 内核树! — 除了您可能希望找到一些包含 Makefilemakefile 的回购协议之外,但头脑正常的人不会签入同时命名为 a4034 的文件,至少不是故意的。

核心问题是操作系统使同一个文件以不同的名称出现,因此 git 会看到不同的内容,具体取决于它正在寻找的内容,如果它正在寻找的不是默认名称操作系统正在呈现。

从新开始,这条路径今天的表现如下:

$ git init 
Initialized empty Git repository
$ git config --get core.precomposeUnicode
true  # this is the default in git 1.8.5 and higher
$ touch Prodsättning 
$ env -u LANG /bin/ls -b
Prodsa40ttning
$ git status -s 
?? "Prods34ttning"

通过在 C 语言环境中使用 ls,我可以看到文件名中的字节,其中包含分解值。但是 git 是将字符组合成一个单一的代码点,这样不同平台的用户不会产生不同的结果。 patch that introduced precomposed unicode explains in detail 各种 git 命令会发生什么。

如果提交中的两个文件在 Unicode 规范化(或大小写折叠)之前具有相同的名称,那么当 git 签出文件时,它们将 "fight" 显示:

$ git clone https://gist.github.com/jleedev/228395a4378a75f9e630b989c346f153 
$ git reset --hard && git status -s 
HEAD is now at fe1abe4 
 M "Prods34ttning"
$ git reset --hard && git status -s 
HEAD is now at fe1abe4 
 M "Prodsa40ttning"

因此,如果您只是想删除文件,您可以按照自己的意愿进行操作。如果你想可靠地操作这些文件,请考虑将 core.precomposeUnicode 选项设置为 false,这样 git 将准确存储你告诉它的文件名字节,但是这可能比它的价值更麻烦。我可能建议创建一个将所有文件名转换为 NFC 的提交,这样 git 就不会认为文件丢失了。

这个问题在 Git and the Umlaut problem on Mac OS X 上有一些较早的答案,但其中许多早于 git 规范化 Unicode 的能力,设置 core.quotepath=false 只会引起混淆案例.