【问题标题】:Is it a good idea to set git config core.ignorecase to false?将 git config core.ignorecase 设置为 false 是个好主意吗?
【发布时间】:2024-01-13 08:09:01
【问题描述】:

我的团队目前跨多个文件系统管理一个 git 存储库。我们大多数人使用 Windows,但我们中的一些人使用 Linux。另外,我们将生产代码部署在 Linux 服务器上。这导致了我们代码库中文件名大小写的多个问题(即,当文件实际上是foo.js 时导入Foo.js)。最近,我完成了对我们项目文件夹的清理工作,其中主要包括重新组织我们的目录并将我们的目录/文件重命名为通用命名标准。许多这些文件名更改正在改变文件的大小写(例如foo.js -> Foo.js)。

当我进行这些更改时,我并不完全了解 git 在区分大小写和不区分大小写的文件系统上处理文件名的方式有何不同,这导致了很多奇怪的错误。我在研究这个时遇到的一件事是.gitconfig 中的ignorecase 值。

来自 git-config 文档:

启用各种变通方法的内部变量,以使 Git 能够 在不区分大小写的文件系统上工作得更好,比如 APFS, HFS+、FAT、NTFS 等。例如,如果目录列表找到 “makefile” 当 Git 需要“Makefile”时,Git 会假设它确实是 同一个文件,并继续记住它为“Makefile”。

Git 依赖于该变量的正确配置 操作系统和文件系统。修改此值可能会导致 意外行为。

据我所知,设置ignorecase = true 将使得如果 git 检测到相同的文件名但大小写不同,它将更改 git 索引中的名称以匹配文件系统(即如果 Windows 有 foo.js 并且 git checkout/pull 有 Foo.js,它将假定 foo.js 是正确的并且 git 将使用该大小写)。我担心的是,如果我推送到远程并且我们的部署服务器接受了更改会发生什么。然后它会尝试导入不存在的Foo.js 并出错。

但是,通过设置ignorecase = false,那么 git 应该能够检测到文件名存在差异并正确替换文件。这是否正确,我是否应该让我的团队将此值设置为 false,因为我们跨不同的文件系统工作?

我读过人们说你绝对应该这样做的帖子,还有一些帖子说的完全相反。

【问题讨论】:

    标签: git filesystems case-sensitive git-config ignore-case


    【解决方案1】:

    据我所知,设置 ignorecase = true 将使得如果 git 检测到相同的文件名但大小写不同,它将更改 git 索引中的名称以匹配文件系统(即,如果 Windows 有 foo.js 并且 git checkout/pull 有 Foo.js,它将假定 foo.js 是正确的并且 git 将使用该大小写)。

    不,这是倒退的:文档指出,如果索引当前包含 Foo.js,并且无论出于何种原因工作树具有 foo.js,Git 将假定 foo.js 确实是 Foo.js 并且将Foo.js 放入下一次提交中。但是,将core.ignorecase 设置为false,Git 完全相信工作树:git add . 将删除大写名称(和内容)并使用小写名称foo.js 在索引中安装一个新副本,基于工作树中的副本。

    (要查看索引中的内容,请使用git ls-files --stage,或省略--stage 以仅获取文件名。请注意,这会转储整个索引内容!添加路径参数以选择特定文件,例如,@ 987654335@,仅查找这些文件。在类 Unix 的 shell(如 bash)中需要引号,以防止 shell 解释此处的元字符 [Ff]。shell 将删除引号。在其他 Windows 中使用什么- 特定的命令行解释器,我不确定。)

    一般情况下,您不应更改core.ignorecase。如果要重命名文件,最直接的方法是使用git mv(同时调整索引和工作树)。如果只想改大小写,可以git mv两次:

    git mv foo.js temporary-name
    git mv temporary-name Foo.js
    

    现在你有了Foo.js,在索引和工作树中都有一个首字母大写。某些版本的 Git 可能能够在没有中间临时名称的情况下处理此问题,但当您使用中间名称时,所有版本都会做正确的事情。

    【讨论】:

    • "但是,将 core.ignorecase 设置为 false,Git 完全相信工作树: git add . 将删除大写名称(和内容)并使用小写名称 foo.js 来安装基于工作树中的副本的索引中的新副本。”但这不是我所期待的行为吗?通过这样做,它不会强制索引与工作树匹配,因此大小写将在每个人的本地存储库中匹配吗?另外,感谢有关使用 git ls-files 查看索引中内容的提示。在我进行所有更改后,我意识到使用 git mv 两次是正确的方法
    • 好吧,假设用户 A 昨天故意将 foo.js 重命名为 Foo.js(无论出于何种原因)。进一步假设用户 B 在 Windows 或 MacOS 上,并且今天在他们的工作树中签出了名称为foo.js 的所有内容。他们将core.ignorecase 设置为false。他们运行git fetch 来获取新的提交(将其重命名为Foo.js),然后运行git merge --ff-onlygit checkout 以移动到最新的提交,它调用文件Foo.js。 (到目前为止一切顺利,对吧?)但现在...... [继续]
    • 现在他们的 Git 尝试用新提交中的大写 Foo.js 替换工作树中的小写 foo.js他们的工作树中的名称没有任何变化;它仍然是foo.js(即使文件的任何其他更改现在出现在他们的工作树中)。如果他们现在运行 git add .,这将在下一次提交中将文件名 back 更改为 foo.js,从而撤消用户 A 所做的文件名更改。
    • 从根本上说,core.ignorecase 试图通过将丢失的信息(文件名的大写或小写)编码到索引中。操作系统不会在任何地方为 Git 保存丢失的信息,因此 Git 必须自己在索引中保存它。关闭core.ignorecase 代码告诉Git:没有必要走私丢失的信息;操作系统正确地处理了这个问题。 在这些折叠文件系统上,这是一个谎言。谎言有时可能对你有利,但它仍然是谎言。
    • 非常感谢您的详细解释。我想我现在明白了这一切是如何运作的。我已经确保现在在 Windows 上将 ignorecase 设置为 true,将来在更改文件名的大小写时我会更加小心。