[英]Git - Made a commit with a delete to an untracked file
在我们真正开始之前,让我们先提两件事:
package-lock.json
通常应该被提交(并因此被跟踪)。 请参阅是否提交由 npm 5 创建的 package-lock.json 文件?现在,让我们开始了解正在发生的事情以及您所有的实际选择。 首先,我们必须以精确的方式定义跟踪文件的确切含义。 幸运的是,与 Git 中的许多东西不同,它有一个非常简单的定义:当且仅当它在 Git 的索引中时才跟踪Git 中的文件。 不幸的是,这个简单的定义使用了“Git 的索引”,这......好吧,并不是那么简单。 幸运的是,它也没有那么复杂,您必须知道 Git 的索引是什么以及使用 Git 做了什么。 有时你可以忽略它而侥幸逃脱,但最终它会咬你——就像它在这里所做的那样。
Git 的索引是Git 如何构建新的提交。 请记住,Git 本身就是关于提交的——不是文件,不是分支,而是提交。 每个提交都有每个文件的完整快照,例如 tar、WinRAR 或 zip 存档,除了它是花哨的 Gitty 并且对其文件进行了重复数据删除,因此重复提交不会使存储库变得非常庞大。 (每个提交也有一些元数据,但我们将在这里完全忽略它。)
这些档案的问题与任何档案的问题相同:它采用某种档案格式,您的常规程序无法读取。 (而且 Git 的档案是严格只读的,至少在原则上是这样:甚至 Git 都不允许覆盖它们。)因此,我们没有尝试直接使用档案,而是有一些程序——也许是一个 unarchiver,或者在这种情况下是 Git本身——从存档中提取文件:
tar -xf foo.tar
或者:
unzip foo.zip
或者:
git checkout main
所有这三个操作基本上都是通过解压缩档案来工作的,给你一堆文件来处理。 git checkout
变体只是首先删除之前的 checkout ,然后提取您要求的提交。 1这填充了一个工作区,我们称之为工作树或工作树。 该区域现在充满了普通的日常文件。 这些文件实际上并不在Git 中,即使它们只是从 Git 中出来的。 它们只是普通的日常文件,你可以对它们做任何你想做的事情。
作为对Git非常方便的副作用(尽管有时对您来说可能不是那么方便),当 Git 提取提交并填充您的工作树时,Git 也会填充它的index 。 因此,在git checkout main
之后,Git 的索引包含它从最新的main
-branch 提交中获得的所有相同文件,这些文件放在你的工作树中。
在您工作时,您可以修改工作树文件。 您还可以创建 Git 最初没有提取的新文件。 您还可以删除文件,无论它们是否是 Git 提取的文件。 因此,您的工作树“偏离”了 Git 提取的提交。
然而,Git 的index仍然包含所有这些文件。 它们已经处于 Git 用来进行提交的特殊内部重复数据删除形式,这使得 Git 可以快速进行下一次提交。
如果并且当您在某个文件上运行git add
时,Git 将:
如果你对某个文件运行git rm
,Git 会从你的工作树和Git 的索引中删除该文件。 如果您对某个文件运行git rm --cached
,Git 会从 Git 的索引中删除该文件,但不会从您的工作树中删除。 2
在你运行git add
之前,索引保存了一个提议的提交,准备好提交。 在你运行git add
或git rm
之后,索引仍然包含一个建议的 commit ,准备好提交。 结果是这样的: Git 的索引始终保存您提议的下一次提交。 您的git add
和/或git rm
命令所做的是更新建议的 commit 。
1实际上,这不是简单的擦除和提取。 相反,这是一个非常复杂的问题,Git 避免擦除和提取它可能避免接触的所有文件,这使得事情进展得很快。 但是 Git 这样做的方式部分是通过使用 Git 的index中的内容。 我们试图解释索引,所以我们必须避免在这里过多地考虑索引,并从更简单的模型开始:删除/擦除,然后安装。
2在一个奇怪的怪癖中, git add somefile
会注意到您是否删除了某个文件,并且表现得像git rm --cached somefile
。 所以你可以使用git add
来删除文件的索引。 我个人永远无法完全习惯这一点,总是使用git rm
代替,但请注意这个怪癖, git add
有时意味着“删除”。 记住它的一种方法是,它实际上意味着使索引副本与工作树副本匹配,如果您删除了工作树副本,它会这样做以匹配。
所以让我们回顾一下:
提交包含快照和元数据。 提交中的快照永远无法更改,只要提交本身还存在,它就存在。
Git 的索引包含一个提议的下一次提交:一组准备提交的文件。
git checkout
和git switch
命令通过填写你的工作树和来自某个提交的 Git 索引来工作。 所以现在有一些文件——那些从提交中提取的——并且这些文件被跟踪,因为它们在 Git 的索引中,以及在你的工作树中。 您的工作树中可能还有其他文件在此之前未被跟踪并且仍然未被跟踪。 您无法仅通过查看工作树文件来判断!
使用git add
和/或git rm
,您可以调整 Git 索引中文件的内容,甚至可以调整 Git 索引中的文件。 因此,此时您可以使文件成为跟踪或不跟踪。 这样做的目的是为git commit
做好准备。
最终,在完成所有这些调整之后,您将运行git commit
。 这将创建一个新快照,并且此快照中的文件正是 Git 索引中的那些文件:不多,不少,也没有不同。 因为索引包含预先删除重复并准备好提交的文件,与历史版本控制系统相比, git commit
本身非常快。 3
您可以使用git commit -a
假装您不需要了解索引,但这在某些情况下会失败。 特别是它永远不会添加任何全新的文件:它或多或少等同于运行git add -u && git commit
。 您也可以使用git commit --only <file>
和git commit --include <file>
commit --include <file> ,要了解它们是如何工作的,您需要了解 Git 的索引。 当然,首先要了解未跟踪的文件,您需要了解 Git 的索引。
当您运行git status
时,Git会将当前提交中的内容与索引中的内容进行比较,然后分别将索引中的内容与工作树中的内容进行比较。 要了解git status
的输出,您需要了解索引。 Git 在这里称它为暂存区,指的是你如何使用它,并且在许多方面比“索引”更好,但一堆旧的 Git 命令仍然使用“索引”。 这个东西甚至还有第三个名字:Git 有时使用这个词cache 。 您可以在git rm --cached
中看到这一点。 4
3在传统的 Git 之前的版本控制系统中,他们的“提交”动词意味着去弄清楚要提交的内容,这可能需要几秒钟或几分钟,或者在某些极端情况下,需要几小时。 Git 使得将数百万个文件放入存储库中而没有那种痛苦成为可能,尽管当你达到这个级别时,你会遇到其他类型的痛苦。
4我不知道为什么没有git rm --staged
。 在我看来,当git diff
获得--staged
作为--cached
的同义词时, git rm
也应该获得--staged
作为--cached
的同义词。 但它没有。 Git 的命名很糟糕。 幸运的是,研究表明,如果你经常使用任意名称,它们并没有那么糟糕。 有关更多想法,请参阅例如https://smallstep.com/blog/the-poetics-of-cli-command-names/以及为什么 UNIX/POSIX 系统调用命名如此难以辨认?
现在,一旦我们有一个充满提交的存储库,我们通常希望将提交视为自上次提交以来的更改。 即使每个提交都有一个快照(不是更改),Git 也可以轻松地向我们展示更改,因为每个提交都会记住(通过我不会提到的元数据)哪个提交在该特定提交之前。
因此, git show commit
只是将两个提交提取到临时内存区域:我们询问的一个和之前的一个。 然后它比较两个快照。 对于两个绝对相同的文件(自动去重),它什么也没说——事实上,由于它存储提交的方式,它可以避免一开始就提取这些文件。 对于新旧内容不同的两个文件,Git 玩了一个Spot the Difference游戏,并告诉我们它发现了什么。 瞧,我们有一个diff ,即使提交包含一个快照。
如果差异包括完全删除某些文件,我们会得到您在问题中显示的内容:
D frontend/package-lock.json
这意味着之前的提交有文件,而我们要求 Git 总结的提交缺少文件。 实际上,该文件已被删除。
如果你签出之前的提交,它有文件,所以文件将进入你的工作树并进入 Git 的索引,现在它是一个被跟踪的文件。 如果您进行了后续提交(即被删除的提交),您必须告诉 Git删除索引副本,可能使用git rm --cached
:
我不小心提交并在该文件上删除了推送(它仍然在本地)
这正是你使用git rm --cached
后跟git commit
得到的结果:你首先让 Git 从 Git 的索引中删除文件,然后让 Git 从 Git 的索引中进行新的提交,新的提交会忽略文件。 但是git rm --cached
从未接触过工作树副本,并且由于它现在没有被跟踪——自从git rm --cached
步骤以来——它就在你的工作树中,占用空间并且看起来很可爱。
但:
...任何拉动该提交的人都将删除他的
package-lock.json
文件
这是正确的。 那是因为他们正在使用文件所在的提交,并且他们在 Git 的索引和他们的工作树中都有该文件,他们现在要求 Git 切换到或合并文件所在的提交删除。 因此,Git 尽职尽责地从索引和工作树中删除跟踪的文件。
(他们可以通过首先使用git rm --cached
从 Git 的索引中删除跟踪的文件,使其成为未跟踪的文件,然后检查目标提交来防止这种情况。但是,使用真正的合并更难做到这一点,即使它对他们有用,他们也必须知道首先执行git rm --cached
。)
尽管据说它是未跟踪的,但当然仍然希望它未跟踪。
你不能有这个。 文件要么在提交中,然后将在结帐时进行跟踪,要么不在提交中,然后您就会遇到现在的情况。
这种情况是站不住脚的。 你最好的方法是:一开始就不要卷入其中。
如果应该提交package-jock.json
,请保持提交; 不要将其作为未跟踪的文件。 将文件不存在的现有提交视为“毒药”并避免它(或尝试完全摆脱它)。
如果确实不应该提交,但您需要它的数据,请让其他文件名保存数据。 也就是把 package- package-jock.json
重命名为package-lock.json.template
什么的。
(在 Git 中,“重命名”文件只是一个已删除的文件加上一个新添加的文件,其中新文件的内容与旧的、现在已删除的文件内容重复删除。所以无论你如何到达那里,你只需创建一个包含旧内容的新文件。如果方便的话,您可以使用git mv
到达那里,或者您可以使用任何其他方式到达那里。)
通过软重置到最后一次提交( git reset --soft HEAD~1
)然后隐藏更改( git stash save "commit fix"
)然后强制推送( git push -f
)来修复此问题
之后,我从存储中挑选文件(老实说,我不知道 cmd,为此我使用了 Sublime Merge)并再次提交并推送。
总而言之,这会删除最后一次提交,然后您可以在新的提交中更正它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.