【问题标题】:git case-insensitive directory renamedgit不区分大小写目录重命名
【发布时间】:2021-01-07 19:45:42
【问题描述】:

希望解决一个问题,即我现在有 2 个同名的目录路径,只是大小写不同。因为,曾几何时,我将目录重命名为小写。

我在文件夹视图中只看到一个文件夹/目录,根目录中没有其他文件和文件夹。 "someproject"/"src"

但是,在进行更改时,我看到要提交 2 组文件。

Src/somefile.txt
src/somefile.txt

如何从 repo 中删除 Src 文件夹和内容?

  • 我要备份“src”,删除“src”文件夹,提交,然后添加重新加入、提交、推送?

【问题讨论】:

  • git rm -r --cached folderNameToRemove
  • 您使用的是 GNU+Linux 发行版还是 Windows(或 MacOS)?
  • @nobalG 给出答案,谢谢。
  • @knittl 我在 Mac 上,但也适用于 Windows。

标签: git


【解决方案1】:

从 git 中删除目录,而不是文件系统

git rm -r --cached folderNameToRemove

将目录添加到.gitignore,之后记得做一个git push。

类似情况,checkout this brilliant thread

【讨论】:

  • 实际上,这两个目录都不存在于磁盘上,只有正确的一个。对单个文件进行更改时,更改显示在 git status 的两个位置,显示错误和正确的位置。然而,当克隆时,将在磁盘上创建两个目录,这是我试图避免的。再次感谢您。
【解决方案2】:

nobalG's answer不错

如果你运行:

git rm -r --cached src
git add src

您应该一切顺利(通过git status 确认)。您无需向.gitignore 添加任何内容。你也可能实际上不需要git add 任何东西。

这个答案的问题在于它只是一个食谱;它不会告诉您下次遇到各种问题时该怎么做。

首先,让我们正确地定义问题。我们需要注意以下几点:

  • Git 存储提交,而不是文件。也就是说,当您运行 git checkoutgit commit 时,您处理的存储单元是 提交。确实提交 contain 文件,但这是一揽子交易。 (有一些有点笨拙的零碎工作方法,我们稍后会介绍;它们对特殊情况很有用。)

  • 任何提交的所有部分都是只读的。这包括所有存储的文件。它们以永久冻结的压缩格式存储。每个文件的内容也会针对所有其他存储的文件进行重复数据删除。这一点很重要,因为每个提交都存储每个文件的完整副本,但是通过重复数据删除,这意味着大多数提交大多与其他每个提交共享几乎所有文件。文件是只读的这一事实使这一点成为可能:更改一个存储的文件实际上是不可能的,所以如果提交 X 的文件 F1 需要与提交 Y 的文件 F2 相同的内容,这是完全合理的他们实际使用相同的存储内容。

  • 您看到和处理/使用的文件不在 Git 中在 Git 中的文件不是您看到并使用/使用的那些。这是前一项的直接结果。在任何给定的提交中,存储的文件都采用只有 Git 可以读取的形式,并且实际上没有任何东西——甚至 Git 本身——可以部分或部分覆盖它们完全地。这意味着 in Git 中的文件对于完成任何实际工作完全没有用处。所以 Git 不会尝试这样做。当您选择要处理/使用的提交时,Git 将提交的文件out复制到工作区,我们称之为您的工作树 em> 或 工作树

现在我们了解到您可以看到和使用的文件在 Git 中并不,而在 Git 中的文件在一个特殊的 Git-唯一的形式,现在我们可以理解这里发生了什么。当 Git 存储文件时,这些文件不在文件夹中并且可以有几乎任意的名称。在 Git 提交中,我们没有名为 srcSrc文件夹 存储名为 somefile.txt文件。相反,我们只有一个名为src/somefile.txt文件,其中包含一个斜线。1 而且——这是我们这里问题的关键——这些名称始终区分大小写,因此提交可以轻松地同时包含src/somefile.txt Src/somefile.txt,作为两个单独的文件。一个提交也可以同时包含readme.mdREADME.md,等等。

同样,这些不是普通文件。它们以一种特殊的、只读的、仅限 Git 的、压缩的和去重复的格式存储,只有 Git 可以读取(除了一个初始创建来进行新的提交之外,没有任何东西可以写入)。而且,它们区分大小写,并且可以拼出您在 Windows 机器上根本无法使用的文件名。2

如果您使用的是典型的 Mac 或 Windows 系统,您的常规文件会保留大小写但不区分大小写。这实际上是特定于文件系统的,并且很容易设置一个 macOS 可挂载磁盘,其中文件区分大小写,以便您可以存储readme.mdREADME.md。有关在 macOS 上执行此操作的方法,请参阅 my answer here。在 Windows 上,您可以设置 VM 并运行 Linux;我不使用 Windows,所以我没有固定程序。


1从技术上讲,已提交版本的文件在内部使用类似文件夹的结构;将文件夹展平的地方是 Git 的 index。但是无论如何你都不能直接处理提交:你将现有的提交读入 Git 的索引,并在 Git 的索引中建立新的提交,这里的文件名实际上包括斜杠。所以我们不妨直接说文件名中有斜线。

2Mac 用户可以嘲笑无法创建名为 aux.txt 的文件的 Windows 用户,因为在 Mac 上创建 aux.txt 很容易。但是 Mac 用户还有其他问题:例如,文件名agréable 的拼写有两种方法,并且只有一种方法可以在 Mac 上使用。虽然这有时是件好事——我们可能希望有两个不同的文件似乎具有相同的名称——但它带来了完全相同的互操作性问题。


在 macOS 上设置问题案例

由于我手边有一台 Mac,我可以说明我们如何设置问题案例。我们从一个新的、完全空的存储库开始:

sh-3.2$ cd ~/tmp && mkdir case && cd case && git init
Initialized empty Git repository in /Users/torek/tmp/case/.git/
sh-3.2$ mkdir Dir && touch Dir/file && giit add Dir/file && git commit -m initial
[master (root-commit) 07ed87d] initial
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 Dir/file
sh-3.2$ mv Dir dir
sh-3.2$ echo some contents > dir/file

现在,到目前为止,我们在 Git 本身 中所做的一切都是创建一个初始的空提交,其中包含一个名为 Dir/file 的文件。然而,最后两个命令告诉 macOS 修改我们的工作区——我们的工作树——将Dir(初始大写字母)重命名为dir(小写字母),并将一些内容放入给定的文件。运行ls 表明操作系统确实重命名了工作树目录,但Git 知道在这个文件系统上dir/fileDir/file 都可以读取或写入相同的普通文件。所以:

sh-3.2$ ls
dir
sh-3.2$ cat dir/file
some contents
sh-3.2$ cat Dir/file
some contents

您可以看到我们所做的更新。但是:

sh-3.2$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   Dir/file

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

Git 知道dir/fileDir/file 都可以访问同一个文件。此外,Git 知道我们的 last 提交中有一个空的 Dir/file。 Git 假设,因此,我们希望保留文件名的先前拼写:Dir/file。 (请注意,名称中包含斜线:没有文件夹,只有文件。)

深入了解:Git 的索引和core.ignorecase

这里涉及多个相互交织的部分:

  • 首先,当我们签出某个提交时,Git 会构建一个 Git 调用的数据结构,例如 index暂存区 em>,或者——现在很少见——缓存。这三个名字反映了这件事的重要性,或者说“索引”不是一个很好的名字,或者两者兼而有之。

    我喜欢将 Git 的索引总结为 您提议的下一次提交。这就是它作为暂存区的作用。但它并不完全完整,我们稍后会看到。尽管如此,这里的关键思想是,当您第一次检查某个特定的提交时,Git 复制所有提交的文件它的索引。2 index - 建议的 next 提交 - 现在与 current 提交匹配。如果并且当您进行更改时,您必须让 Git 将更新的文件复制到索引中,这就是 git add 的用武之地。

    请记住,索引包含文件的全名,并带有正斜杠。这些都保存在数据文件中——目前是.git/index,可能还有.git 目录中的一些附加文件——这些文件不是由您的操作系统管理,而是由Git 管理。所以它们取决于 Git,Git 说这些文件名区分大小写的,不管发生了什么。因此,索引可以同时存储readme.md README.md,或同时存储dir/file Dir/file

  • 同时,您的 work-treeworking tree 包含您的所有文件,扩展为可用形式。但是,如果您的操作系统将它们视为区分大小写,您将无法在此处同时存储readme.md README.md。您将无法同时存储dir/file Dir/file。您的操作系统将dirDir 视为单个文件夹名称,将readme.mdREADME.md 视为单个文件名。因此,Git 的提交可以在此处包含两个名称,但 您的操作系统的文件系统不能这一事实导致了我们的问题。

  • 最后,当 Git 设置 Git 存储库时(例如,在我们上面的示例中为 git init 时间),Git 会探测您的文件系统以查看它是否忽略大小写。如果您的操作系统确实忽略大小写,从而可能发生此问题,Git 将 core.ignorecase 设置为 true

    sh-3.2$ git config --get core.ignorecase
    true
    

    Git 使用它来“知道”Dir/filedir/file,虽然与 Git 不同,但与 您的主机操作系统的文件系统 相同。


2索引中的实际内容是:

  • 文件名,包含正斜杠;
  • 文件的模式,通常是100644(读/写)或100755(读/写但也可执行);
  • 一个 blob 哈希 ID,其作用类似于文件内容的链接,已去重并采用内部 Git 格式;和
  • 标志和其他内部缓存数据可帮助 Git 跟踪您的工作树中的内容。

由于内容实际上位于其他位置,因此索引不包含文件的真正副本。但是,副本管理是全自动的。这意味着索引副本就像文件的副本一样,尽可能不占用任何磁盘空间(如果内容与某些现有的已提交文件重复)。

所有这一切的最终结果是每个文件的索引“副本”已准备好进入 next 提交。换句话说,索引中的内容就是上演的内容。但是,如果您有一万个文件,当其中 9997 个文件与当前提交中已经提交的文件相同时,将它们全部列出会很痛苦和无聊。所以git status 不会告诉你 9997 identical 文件。它只是说 匹配的三个是“为提交而准备的”。其实一万个都是上演的;我们只是保持git status 可用,不任何关于匹配的内容。


Git 如何使用core.ignorecase

所以,在我们上面的设置中,我们:

  1. 在 Mac 或 Windows 不区分大小写的系统上创建了一个新的空存储库,其中我们不能同时拥有 README.md readme.md 文件,我们不能同时拥有 @ 987654384@ dir/file。如果我们有一个名为Dir(大写)的目录并且我们尝试创建dir/another系统将创建Dir/another

    这不是 Git 的问题,但 Git 确实必须处理它。

  2. 创建并提交了一个空的Dir/file。这现在在一个提交中。它永远无法改变!该提交,只要它存在,就在其中包含该路径名,并带有特定的大小写。

  3. 使用了一些操作系统端工具(在 macOS 上是纯 mv,我不确定你会在 Windows 上使用什么)将 Dir 重命名为 dir

由于主机文件系统不区分大小写,Git 在我们创建存储库时将core.ignorecase 设置为true。我们现在可以对 Git 撒谎,并说我们的操作系统将这些情况视为不同。这不是真的!但我们可以实际上是故意暂时对 Git 撒谎。如果出现问题,我们将负责:除非您愿意承担责任,否则请勿这样做。

sh-3.2$ echo some contents > dir/file
sh-3.2$ git status --short
 M Dir/file
sh-3.2$ git config core.ignorecase false
sh-3.2$ git status --short -uall
 M Dir/file
?? dir/file

(第一行是重复的,但因为我使用了&gt;,所以我可以随意重复它——它会擦除文件并在其中添加一行读取some contents)。

在这里,我使用git status --short 来缩短输出。 M 表示工作树副本已被修改。在对 Git 撒谎之前,Git 检查并发现 dir/file 存在,找出这与索引 Dir/file 匹配,并将它们匹配起来。但是,一旦我对 Git 撒谎,就会发生一些有趣的事情:

  • Git 相信有一个 Dir/file。它会尝试打开该文件,假设它不会得到dir/file。但它确实得到dir/file。它将dir/file 与它认为得到的Dir/file 进行比较,发现它有所不同。所以现在 Git 说该文件已修改(在工作树中状态为 M)。

  • Git 发现名为dir 的目录/文件夹,读取它并找到一个名为file 的文件。 dir/file 的路径在 Git 的索引中 not,所以 Git 说这个文件是 untracked(这是双问号)。

我现在可以git add dir/file:

sh-3.2$ git add dir/file
sh-3.2$ git status --short
 M Dir/file
A  dir/file

Git 使用小写名称将dir/file 复制到其索引中。 文件副本的内容为some contentsgit status 输出现在将其显示为 new 文件。那是因为当前提交没有dir/file;它只有一个Dir/file(内容不同)。

我现在可以进行新的提交了。这个新的提交包含Dir/file(仍然是空的)和dir/file,内容为some contents

sh-3.2$ git commit -m 'add lowercase version with content'
[master 793d32c] add lowercase version with content
 1 file changed, 1 insertion(+)
 create mode 100644 dir/file
sh-3.2$ git show HEAD:Dir/file
sh-3.2$ git show HEAD:dir/file
some contents
sh-3.2$ git ls-tree -r HEAD
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    Dir/file
100644 blob f70d6b139823ab30278db23bb547c61e0d4444fb    dir/file

这里的blob 行是Git 表示有两个文件的方式。这两个文件的模式是100644(读/写但不执行),大而丑陋的哈希ID是Git对内容进行重复数据删除的方式;文件的名称出现在右侧。 -rgit ls-tree 的选项需要让它显示每个“文件夹”中的内容(另请参见脚注 1:如果不是因为索引扁平化文件夹,Git 会 em> 能够存储一个空文件夹,但是因为索引做了它的事情,Git 不能)。

现在我已经提交了this,这个冻结状态——有两种不同的名称——永远存在,或者至少,只要第二次提交继续存在。即使在不区分大小写的 Mac 文件系统上,我也能够通过对 Git 撒谎的伎俩做到这一点。

我现在可以在恢复 core.ignorecase 之前做最后一招:

sh-3.2$ git rm --cached Dir/file
rm 'Dir/file'
sh-3.2$ git status --short
D  Dir/file
sh-3.2$ ls
dir
sh-3.2$ ls dir
file

实际上,我本来可以恢复 core.ignorecase,因为 git rm --cached 不需要花哨的大小写技巧:它总是直接从 Git 的索引中删除条目,而且这些条目总是区分大小写的。但我想我会像这样在这里展示它。让我们把core.ignorecase 放回原来的样子,然后提交结果:

sh-3.2$ git config core.ignorecase true
sh-3.2$ git commit -m 'remove uppercase Dir/file'
[master c5edc17] remove uppercase Dir/file
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 Dir/file
sh-3.2$ git status
On branch master
nothing to commit, working tree clean

我已经用我想要的内容进行了更正的提交,现在我有一个可以在这台 Mac 上正确使用的提交,即使在不区分大小写的文件系统中也是如此。中间提交,其中包含Dir/file dir/file无法在典型的 Windows 或 Mac 设置上正确使用——但因为 Git 确实有效在进行新提交时,使用 index,我们可以通过谨慎的技巧来使用这样的存储库。这不有趣,更好的工具可能会更好,但这表明你可以如何克服一些问题。

请注意,当您关闭 core.ignorecase 时, 有责任确保文件名大小写正确。 Git 会错误地认为创建dir/file 将创建dir/file,即使存在名为Dir/ 的文件夹。您必须手动 rm -r 整个 Dir 目录,或者将其重命名:

mv Dir save
git checkout -- dir/file

例如,当 Git 去创建 dir 时,操作系统不会只是愉快地使用现有的 Dir/ 目录。

一旦你完成了这种手动、小心、一个文件的操作,重新打开core.ignorecase 绝对是个好主意(假设它最初是打开的:使用git config --get 找出答案) -时间挖掘。

注意:

git checkout <commit> -- <path>

告诉 Git 从一些特定的提交中提取给定的&lt;path&gt;

  1. 将其复制到 Git 的索引中:关闭 core.ignorecase 后,Git 会认为可以使用不同大小写的相似名称;
  2. 将生成的索引文件复制到您的工作树中;这是有责任确保任何现有文件夹的大小写正确的地方。

这就是我上面所说的“零碎”的意思,在长部分的顶部。形式:

git checkout -- <path>

告诉 Git 从 Git 索引中的当前内容中提取给定路径

我们在一种情况下使用提交说明符而在另一种情况下省略它,这一事实令人困惑。如果您有 Git 2.23 或更高版本,则可以使用 git restore 而不是 git checkoutrestore 命令允许您将某些内容的来源指定为提交或索引。如果您选择提交,您可以指定是将文件复制到索引,还是您的工作树,或两者兼而有之。如果您选择索引作为来源,您可以将文件复制到的唯一位置就是您的工作树。

(最后一个事实是,您的工作树和 Git 的索引都可以写入,但提交只能读取。)

【讨论】:

  • 感谢您提供此信息。我只需要你提到的和@nobalG 提到的第一个命令。
  • @JasonFoglia:每当有人询问文件名大小写问题时,我主要只是想把它作为重复的问题和答案使用。
猜你喜欢
  • 2016-08-16
  • 2016-11-24
  • 1970-01-01
  • 2010-12-10
  • 1970-01-01
  • 2023-03-25
  • 2015-06-11
  • 2012-02-12
  • 2016-05-15
相关资源
最近更新 更多