如何确保在推送到 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 调用的文件,不同的 index 或 staging area,或(现在很少)缓存。 这些文件采用Git自己的内部格式,因此可以使用Git支持的任何名称,并且任何内容(在 Windows 系统上包括 LF-only 行而不是 CRLF-ended 行)。
当然,最大的障碍是您无法查看或编辑 Git 索引中的文件副本。它们不是普通文件!要查看 Git-ized 文件的内容,因为它出现在 Git 的索引中,您必须告诉 Git 复制文件 [= Git 的索引中的 145=]out。通常的方法是 git checkout
,但是这种通常的方法失败了,因为使用文件名在某种程度上不符合您系统的 file-name 要求。但是还有其他查看内容的方法:例如,git show :README.md
和 git 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 add
、git mv
等时,如果您检查 Git 的索引,您将看到 Git 中的内容提议的下一次提交。
或者,您可以在不检查的情况下查看某些现有提交中的内容:将 git ls-tree -r
用于定位提交本身的任何内容,例如原始哈希 ID 或分支名称。有关拼写哈希 ID 的多种方法,请参阅 the gitrevisions documentation。
现在,如果您需要使用这样的文件,但无法将其检出——例如,如果在某个提交中有一个名为 aux.h
或 con.jpg
的文件,而您在 refuses to allow you to have a file with that name 的 Windows 盒子上——这是你如何手动和痛苦地做到这一点的方法:
- 检查提交。该文件不会是 check-out-able,但现在已填充 Git 的索引。
- 在 bash 中,使用
git show :aux.h > fakeaux.h
(将 fakeaux.h
替换为您喜欢的任何文件名)。使用您正在使用的任何命令行解释器所需的任何方法,以便Git提取内容(使用git show
)并将它们放入您可以处理其名称的文件。 (或者,您可以使用 git mv
临时重命名索引中的文件,尽管这可能会失败,因为它可能会注意到该文件不存在于您的工作树中。)
- 使用此文件的内容做任何您需要的工作。
- 要将文件放回 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 系统:some
,path
, to
, file\with\weird\name
。这里 Git 在 Windows 上做了什么,我不知道,但测试可能很有趣。我的意思是“总是正斜杠”是指当您的 Windows work-tree 有 some\path\to\file.ext
时,Git 在其索引中有 some/path/to/file.ext
。)
我的 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 调用的文件,不同的 index 或 staging area,或(现在很少)缓存。 这些文件采用Git自己的内部格式,因此可以使用Git支持的任何名称,并且任何内容(在 Windows 系统上包括 LF-only 行而不是 CRLF-ended 行)。
当然,最大的障碍是您无法查看或编辑 Git 索引中的文件副本。它们不是普通文件!要查看 Git-ized 文件的内容,因为它出现在 Git 的索引中,您必须告诉 Git 复制文件 [= Git 的索引中的 145=]out。通常的方法是 git checkout
,但是这种通常的方法失败了,因为使用文件名在某种程度上不符合您系统的 file-name 要求。但是还有其他查看内容的方法:例如,git show :README.md
和 git 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 add
、git mv
等时,如果您检查 Git 的索引,您将看到 Git 中的内容提议的下一次提交。
或者,您可以在不检查的情况下查看某些现有提交中的内容:将 git ls-tree -r
用于定位提交本身的任何内容,例如原始哈希 ID 或分支名称。有关拼写哈希 ID 的多种方法,请参阅 the gitrevisions documentation。
现在,如果您需要使用这样的文件,但无法将其检出——例如,如果在某个提交中有一个名为 aux.h
或 con.jpg
的文件,而您在 refuses to allow you to have a file with that name 的 Windows 盒子上——这是你如何手动和痛苦地做到这一点的方法:
- 检查提交。该文件不会是 check-out-able,但现在已填充 Git 的索引。
- 在 bash 中,使用
git show :aux.h > fakeaux.h
(将fakeaux.h
替换为您喜欢的任何文件名)。使用您正在使用的任何命令行解释器所需的任何方法,以便Git提取内容(使用git show
)并将它们放入您可以处理其名称的文件。 (或者,您可以使用git mv
临时重命名索引中的文件,尽管这可能会失败,因为它可能会注意到该文件不存在于您的工作树中。) - 使用此文件的内容做任何您需要的工作。
- 要将文件放回 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 系统:some
,path
, to
, file\with\weird\name
。这里 Git 在 Windows 上做了什么,我不知道,但测试可能很有趣。我的意思是“总是正斜杠”是指当您的 Windows work-tree 有 some\path\to\file.ext
时,Git 在其索引中有 some/path/to/file.ext
。)