无法从git--cached中rm目录

  • 本文关键字:rm 目录 git--cached git
  • 更新时间 :
  • 英文 :


我不小心在我的repo中添加了一个目录。没什么大不了的,我只是运行

git rm --cached <dir-name>

但是我收到这个错误

error: the following file has staged content different from both the
file and the HEAD:
<dir-name>
(use -f to force removal)

可能会使这一点复杂化的是,添加到缓存中的目录本身就是git repo,而根repo中唯一缓存的更改与git Subproject有关。

git diff --cached

输出:

--- /dev/null
+++ b/<dir-name>
@@ -0,0 +1 @@
+Subproject commit <commit-id>

引用的提交id是当前嵌套的repo HEAD,但我可能在意外添加后添加了它。

我的第一直觉只是使用-f来强制,因为这不是在实际的回购中,而是在阶段性的回购中。但这个答案让我三思而后行,因为我不想永远把任何事情搞砸。

幸运的是,这些都是本地的,所以我不需要处理任何遥控器,我相信有一个简单的解决方案,但我只想正确地做。我应该只运行git rm --cached -f <dir-name>吗?还是我需要采取另一种方法?

TL;DR

您的第一直觉几乎肯定是正确的:您可能想要git rm --cached -fpath,其中path命名子存储库的路径。这将从索引/暂存区/缓存中删除Git称为gitlink的东西。

Long

首先,请记住Git根本不存储目录。所以这个目录一开始就不在Git中。原因与Git所称的索引暂存区有关,或者——现在相对较少,但在git rm --cached中仍然可见——缓存

其次,要知道Git从不在存储库中存储另一个Git存储库。或者,换句话说,存储库从未真正嵌套。这里的实际实现是禁止任何由.git组成的路径名组件(包括不区分大小写的变体,如.GIT.Git或其他)1

这里有Git称之为子模块的东西(或者半个子模块:Git内部称之为gitlink的那一半)。


1在非常旧的Git版本中,作者忘记考虑Windows和MacOS上不区分大小写的文件系统,并允许使用名为foo/.GIT/HEAD等的文件创建存储库。这使得";外部";Git将foo/.GIT目录视为另一个Git存储库。这使得将特洛伊木马存储库设置为使用这些系统的人的陷阱变得太容易了。


委员会

Git最终由两个键值数据库构建而成,其中一个是通过克隆复制的。(另一个包含分支和标记等名称,在克隆过程中被部分复制但被修改。)主数据库由提交和其他内部Git对象组成。这些对象中的每一个都是只读的,因为Git查找这些对象的方式是通过其密钥,而其密钥本身就是对象的加密校验和。如果你从数据库中取出一个对象,处理它的一些位,然后尝试将其放回,你得到的不是一个修改过的对象,而是一个新的对象,带有一个新的不同键2

我们在这里讨论的最有趣的对象是提交。提交包含Git所知道的所有文件的快照。


2这使得假设除非值本身是重复的,否则任何键都不会重复。(这个重复值=Git如何消除文件内容的重复是相同的关键技巧。)Git目前使用的是SHA-1,它在实践中足够好,但容易受到蓄意攻击。幸运的是,这种袭击的后果大多只是滋扰。有关这方面的更多信息,请参阅新发现的SHA-1冲突如何影响Git?


索引

Git构建新的提交,首先在它称之为索引的东西中存储3一系列记录,这些记录给出了Git对象的路径名和哈希ID,主要是存储这些文件内容的blob对象。没有可以保存目录的记录类型,这就是Git不能存储目录的原因。

git commit命令只是简单地打包索引的记录4,并用提交对象封装该包,以便进行新的提交。因此,索引的函数将成为暂存区:它包含建议的下一次提交。由于索引本身不是Git对象,因此可以根据需要对其进行修改。

为了具体起见,实际记录——忽略标题和扩展名,只关注索引的正常日常文件条目——包括:

  • 基于Unix风格inode模式字段的模式
  • 路径名称
  • 哈希ID,其给出内部Git对象ID;以及
  • 我将在此处忽略其他缓存数据

对于普通文件,mode100644100755——您经常在git diff输出中看到这些——其他模式值保留给符号链接和gitlink。路径名包含所需的任何斜杠:这里的文件可以有长名称,如path/to/file.txt。这不是一个包含子目录to的目录path,它包含一个名为file.txt的文件:它实际上是一个名称为path/to/file.txt的文件。

请注意,签出一些现有的提交首先用存储在该提交中的这些记录填充Git的索引,然后在需要时用实际文件填充工作树。


3这是目前一个通常命名为.git/index的单个文件,但它本身可以引用其他文件。这有点问题,因为这些额外的文件在Git操作过程中无法得到适当的保护。非常大的索引文件(例如,数百万条记录)会导致性能问题,因此;"分割索引";,这个答案根本没有涵盖。

4Git将名称转换为一个或多个内部树对象,这些对象通常指更多的树对象,每个以斜线分隔的名称组件都分组到某个子树中。如果索引可以存储目录名,那么这些树对象将允许Git存储一个空目录,但它不能,所以Git不能。


子模块是对另一个Git存储库的引用

这最终使我们进入子模块。我们知道:

  • 存储库是提交的集合,并且
  • 提交由散列ID标识

如果我们可以让Git在工作时自动为我们克隆一些其他存储库,然后在其他存储库中git checkout提交正确的,该怎么办?这就是子模块的全部内容。

为了克隆Git存储库,Git需要:

  • URL,以及
  • 存放克隆存储库的位置:相对于此存储库的路径

要获得";外部";或者超级项目Git到git clone一些内部Git,我们需要存储这些信息。这些东西进入一个纯文本文件,格式类似于Git配置文件,名为.gitmodules

然而,一旦克隆完成,我们需要让超级项目Git进入子模块并运行git checkouthashgit switch --detachhash。这需要两件事:

  • 相对于此存储库的路径,以及
  • 提交哈希ID

超级项目Git从Git的索引中获取这些,正如我们已经看到的,该索引存储路径名和Git哈希ID。当提交包含gitlink(一个模式为160000的实体)时,签出操作只会将该gitlink读取到索引中。因此,现在Git在索引中有一个path/to/gitlink或任何名称,以及一个存储的提交哈希ID。

这意味着索引存储gitlink

无论何时:

  • 在您的超级项目工作树中(而不是在子模块工作树中),以及
  • 是子模块的路径的路径上运行CCD_ 30

您的超级项目Git将在其索引中添加或更新相应的gitlink条目请注意,Git此时不会检查是否存在合适的.gitmodules条目它只是在超级项目Git的索引中更新或添加gitlink。

超级项目Git通过cd进入子模块并运行git rev-parse HEAD来找到与该gitlink相关的哈希ID5因此,根据子模块中实际签出的提交,更新索引中的gitlink条目。

如果.gitmodules文件丢失或不完整,那么这个特定的子模块就有点半途而废了:你对这个存储库的任何其他克隆都不知道用什么URL来运行git clone获得子模块。既然您提到这完全是本地的,那么这对您的用例来说可能并不重要。


5当前版本的Git确实做到了这一点,而且这不是最有效的过程。正在酝酿中的Git的新版本具有避免启动新的子命令的功能,但也能获得相同的结果。


结论

如果你不想要一个子模块——或者一个只由保存的gitlink组成的半成品子模块,而没有必要的东西来git clone子模块——你应该从索引中删除gitlink。使用:

git rm --cached -f path/to/gitlink

会这么做的。请确保使用--cached选项!(幸运的是,如果你忘记了,我相信它应该只是出错。)

如果这是一个合适的子模块,您可能需要做更多的工作:请参阅删除git子模块的当前方法是什么?然而,如果它从未被正确添加,那就没有什么可做的了

最新更新