如何确保在推送到 repo 之前应用 Mac 上的重命名目录区分大小写?

How to ensure the case sensitivity of a renamed directory on Mac is applied before pushing to repo?

我的 React 项目文件夹结构类似于,

├── folderA
├── folderB
│   └── SubFolderA // <--notice here
└── folderC

我注意到不一致,并将 SubFolderA 重命名为 subFolderA

然后我推送到我的存储库并触发了 Jenkins 构建。但是,这项工作失败了,因为经过一些调查,我的分支机构将新 subFolderA 列为 -- 仍然 -- SubFolderA.

我可以在这里做什么?

在阅读以下内容之前(但一定要阅读),请注意,如果您处于 Mac 并想要处理 case-sensitivity 问题,那么 真的通过在虚拟磁盘上创建一个 case-sensitive 文件系统,然后安装该磁盘(如果您愿意,只需 double-click 桌面上的 .dmg 文件即可实现此操作的简单方法 方法,例如),然后将您的存储库克隆到已安装的卷中。您现在有了 case-sensitive 设置,可以轻松处理所有事情。

有关创建 case-sensitive 卷的详细信息,请参阅 my answer here to How do I change case of the names of multiple files, already committed?

这是 class 一整套 Git 问题的症状,我喜欢这样描述:您看到和使用的文件,在 Git 存储库,不是 Git 存储库中的文件。

这听起来可能很有趣(奇怪,不可能,and/or 幽默)——它的本意是为了让它容易记住——但它字面意思是真实的。这里的问题是 Git 不存储 文件 。 Git 存储 提交 。每个提交然后存储文件,作为一种 read-only 存档,但是存储在 内部 提交的文件不是普通的日常文件。它们采用特殊的 Git-only 格式,不受您的计算机对文件施加的相同限制。这意味着 Git 可以存储您的计算机可能不喜欢的文件(文件名 and/or 内容),在某些情况下,甚至可能无法存储。1

除了行尾(如脚注 1 所示),最常见的表现形式与 mixed-case 文件 and/or 目录(“文件夹”,如果您愿意)名称有关。 macOS 和 Windows 通常提供和呈现 case-preserving 但 case-insensitive 名称。一旦创建了 README.md 文件,就不能在同一个地方创建第二个 ReadMe.md 文件:任何这样做的尝试都只会使用现有的 README.md 文件。然而,Git 提交可以存储 两个具有不同内容的文件 ,并且您可以在 Linux 系统上进行设置,两个文件的内容不同.

例如,在 Linux 系统上,我们创建 README.md 文件并在其中放入行 this is README.md。然后我们创建 ReadMe.md 并将行 this is ReadMe.md 放入其中。我们提交这两个文件并确保提交现在在某个地方,以便我们可以在我们的 Mac 或 Windows 框中获取它(例如,我们 git push 它到 GitHub,如果我们不能只使用 ssh 到 Linux 框)。

现在我们将存储库克隆到 Mac 或 Windows 框中,并要求 Git 检查我们在 Linux 系统上所做的提交。此提交无法正确签出,因为这样做需要创建两个文件,但您的系统不允许您这样做。

那么:当您检查这个提交时会发生什么?答案是 Git 实际上确实检查了这两个文件,但是您的 OS 只允许您处理/使用其中的 一个 。如果你比较你所拥有的,在你的work-area中,Git已经将它的文件提取到普通的日常文件中供你处理,Git在它的区域——它是Git的格式并存储两个文件——你实际上有删除两个文件之一(如果我们将名称不存在的文件视为已删除),或替换两个文件之一的内容 (如果我们在执行 case-folding 之后通过读取名称为 的文件 来将名称不存在的文件视为“存在”)。

也就是说,应该有一个ReadMe.md和一个README.md,但是没有;所以其中一个被删除,或者一个文件有它所拥有的那一行,这意味着我们更改了 other 文件——或者甚至可能同时更改了这两个文件。假设 ReadMe.md 第二次签出并覆盖了内容,因此 README.md 具有行 this is ReadMe.md.

这里有趣的是我们实际上可以在 macOS 或 Windows 系统 上使用这个存储库。这是因为 Git 不会根据我们工作树 中的内容进行提交。当您 运行 git commit、Git 不使用文件时 处理/使用的文件。相反,它使用 Git 存储在 Git 调用的文件,不同的 indexstaging area,或(现在很少)缓存这些文件采用Git自己的内部格式,因此可以使用Git支持的任何名称,并且任何内容(在 Windows 系统上包括 LF-only 行而不是 CRLF-ended 行)。

当然,最大的障碍是您无法查看或编辑 Git 索引中的文件副本。它们不是普通文件!要查看 Git-ized 文件的内容,因为它出现在 Git 的索引中,您必须告诉 Git 复制文件 [= Git 的索引中的 145=]out。通常的方法是 git checkout,但是这种通常的方法失败了,因为使用文件名在某种程度上不符合您系统的 file-name 要求。但是还有其他查看内容的方法:例如,git show :README.mdgit show :ReadMe.md 会显示两个文件的内容。这种特殊的冒号 (:) 语法是 Git 本身特有的; :README.md 表示 在索引 中找到名为 README.md 的文件。由于Git的文件名是case-sensitive,这与:ReadMe.md明显不同; Git 不会混淆两者。

当然,这个特定的问题表现与您所看到的不同。在你的情况下,问题是 two-fold:

  • 你怎么知道 case Git 用来 store 这些文件的?当您 运行 git checkout、Git 只是懒惰地 re-uses 文件 and/or 文件夹名称时,这意味着如果您有 SubFolderA在适当的位置,Git 将 使用 ,即使 stored-in-Git 名称使用 folderB/subFolderA/file.ext 作为它们的名称.2

  • 如何确保您的 next 提交将使用 folderB/subFolderA/file.ext 作为文件名,如果这是您想要的 Git 用作文件名?

幸运的是,在任何系统上,查看Git正在使用的东西很容易,因为有一个low-level 命令——一个你通常不会使用的命令——让你 转储 Git 的索引 中的内容。该命令是 git ls-files。当 运行 没有任何选项时,它只是转储出 Git 索引中的所有文件名。 (与 --stage 一起使用时,它会产生 more-detailed 输出,这有助于说明为什么索引也被称为 暂存区 。与 --debug 一起使用时,它会输出内部标志位和其他缓存信息是索引也称为 缓存 的原因。在所有情况下,这些信息并不是真正供人类使用的:它是 Git调用管道命令,这是一个用于构建higher-level、human-useful命令的命令。)

当你git checkout一些提交(或git switch它,这里是一样的),Git 填写它的索引来自 提交。所以索引中的文件名是 Git 真正 使用的文件名。如果您检查它,您将看到提交中的内容。当您处理/处理提交并使用 git addgit mv 等时,如果您检查 Git 的索引,您将看到 Git 中的内容提议的下一次提交

或者,您可以在不检查的情况下查看某些现有提交中的内容:将 git ls-tree -r 用于定位提交本身的任何内容,例如原始哈希 ID 或分支名称。有关拼写哈希 ID 的多种方法,请参阅 the gitrevisions documentation

现在,如果您需要使用这样的文件,但无法将其检出——例如,如果在某个提交中有一个名为 aux.hcon.jpg 的文件,而您在 refuses to allow you to have a file with that name 的 Windows 盒子上——这是你如何手动和痛苦地做到这一点的方法:

  1. 检查提交。该文件不会是 check-out-able,但现在已填充 Git 的索引。
  2. 在 bash 中,使用 git show :aux.h > fakeaux.h(将 fakeaux.h 替换为您喜欢的任何文件名)。使用您正在使用的任何命令行解释器所需的任何方法,以便Git提取内容(使用git show)并将它们放入您可以处理其名称的文件。 (或者,您可以使用 git mv 临时重命名索引中的文件,尽管这可能会失败,因为它可能会注意到该文件不存在于您的工作树中。)
  3. 使用此文件的内容做任何您需要的工作。
  4. 要将文件放回 Git 的索引中:
    • 运行git hash-object -w fakeaux.h。请注意,hash-object 不应用 CRLF 转换、清理过滤器等。如果您有 new-enough Git,您可以添加 --path=aux.h 以使其执行 git add aux.h 会发生的任何转换。如果 none 关于清洁过滤器的讨论对您有意义,请首先确保文件具有正确的行尾(LF-only,如果适用)。

    • hash-object 命令打印出一个丑陋的大哈希 ID。用鼠标抓住它 (cut-and-paste),或者如果您使用 bash,请考虑使用命令替换来捕获哈希 ID,例如:

      hash=$(git hash-object -w fakeaux.h)
      
    • 最后,用git update-index替换hash ID。这可能最好使用 --cacheinfo 选项来完成。请注意,当使用 --cacheinfo 时,您必须提供 模式 ,对于 non-executable 文件,它应该是 100644,对于 100755可执行文件。

这是一个执行这种棘手操作的示例,by-hand 在一个根本不需要它的框上进行更新,这样您就可以看到方法了。我在这里使用的第一步 git ls-files --stage Makefile 只是为了找到当前的 mode 以备后用,尽管它也显示了文件实际上是如何存储在索引中的(按名称和blob-hash-ID):

$ git ls-files --stage Makefile
100644 7b64106930a615c2e867a061f94cd6d3ea834641 0       Makefile
$ git show :Makefile > xx
$ vim xx
[editor session, snipped]
$ git hash-object -w xx
cd099334db6c1136d79653872256c7091db7c1bd
$ git update-index --cacheinfo 100644,cd099334db6c1136d79653872256c7091db7c1bd,Makefile
$ git status -s
MM Makefile
?? xx

上面的MM是因为stagedMakefile里面有我的改动,但是working tree Makefile 没有; xx 未跟踪文件是我进行更改的地方。我现在可以 运行 git diff --cached 来表明我实际上已经在提议的下一次提交中更改了提议的 Makefile

$ git diff --cached
diff --git a/Makefile b/Makefile
index 7b64106930..cd099334db 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,4 @@
+sneaky
 # The default target of this Makefile is...
 all::
 

请注意,这个问题在 macOS 上不仅会出现在 case-folding 上,还会出现在某些 Unicode 文件名上。例如,Unicode 有两种拼写方式 schön。在 Linux 系统上,我们可以使用这两种方法来制作两个具有两个不同名称的不同文件,它们都 显示为 schön。然后我们可以 git add 这两个文件,然后提交。但是在 macOS 上,你不能在这里有两个单独的文件:我们回到了旧的 ReadMe.md vs README.md 的情况,这次是 schön vs schön。 case-sensitive 音量技巧在这里无济于事,但丑陋的 do-it-by-hand、manipulate-the-index-with-plumbing-commands 会。


1Modern Windows 和 macOS 可以存储任何 content,所以这通常不是问题,但是——尤其是在 Windows 上——你可能会看到其行有 CRLF 结尾的文件,但 Git 中存储的内容没有 有 CRLF 结尾。在这种情况下,您正在编辑的文件明显不同于要提交的文件。

从技术上讲,问题不在于 Windows 或 macOS 本身。相反,它是这些系统提供的文件系统。 Linux现在可以挂载case-insensitive文件系统,macOS当然支持case-sensitive文件系统。只是你在Linux遇到的default是case-sensitive,而你遇到的default在 Mac 上 OS 是 case-insensitive.

2请注意,如存储在 Git 中, 文件的名称中嵌入了斜线 。这些不是 folders-with-files,它们只是包含斜杠的长名称。斜线始终是正常的(正向)斜线,即使在 Windows 上也是如此。从技术上讲,这是 Git 索引的一个特性,而不是存储在提交中的文件的特性,但是由于提交是 索引构建的,你可能应该这样考虑文件名。

(请注意,您 也可以 有一个带反斜杠的文件名: some/path/to/file\with\weird\name 可以,就 Git 而言. 这只是一条很长的路径,但是当检出时,Git 将尝试通过将 Linux 系统分成三个目录/文件夹,加上一个最终名称来容纳 Linux 系统:somepath, to, file\with\weird\name。这里 Git 在 Windows 上做了什么,我不知道,但测试可能很有趣。我的意思是“总是正斜杠”是指当您的 Windows work-tree 有 some\path\to\file.ext 时,Git 在其索引中有 some/path/to/file.ext。)