如何撤销 git rm --cached <filename>?

6

在暂存文件(已跟踪的文件)后,我意识到它需要做一些更改。为了取消暂存,我使用了git rm --cached <filename>。结果,我失去了所有更改。是否有一种方法可以撤消这个操作?


1
"gir rm --cached" 不是用来取消 Git 中文件的暂存状态,而是用来取消将该文件添加到仓库中的操作。你失去了哪些更改?你确定你真的失去了什么吗? - Tim Biegeleisen
4个回答

10

git rm --cached只会将新文件从索引中移除,而不会从工作目录中移除。以这种方式进行操作时要小心。如果这是一个已存在的文件,则刚刚将该文件删除进行了暂存。

您可以使用git add <filename>再次添加它。

取消暂存更改的一种更常见的方法是使用git reset HEAD <filename>

但是...不需要取消暂存文件以添加进一步的更改,您只需对其进行更改并再次使用git add <filename>进行暂存即可。


2
Git中使用“staged changes”或“to stage some changes”的短语可能会让人感到非常困惑。
为了避免混淆,这里是您需要知道的内容:
Git有一个被称为“索引”、“暂存区”或偶尔(现在很少)称为“缓存”的东西。这三个名称都指的是同一个东西。在这里,我将简称其为索引。
索引最初包含你检出的提交中的每个文件的副本。 (当前提交也称为HEAD。)这些文件以Git专有格式存在。你无法看到这些文件。(这是略微夸张了一下——有一些特殊的Git技巧可以看到它们——但通常它们是不可见的。)你可以看到的文件位于你的工作树中,其中包含普通的日常文件。
当你进行下一个提交时,Git将使用此时索引中的文件。也就是说,当你运行git commit -m "some message"(或者在没有-m选项的情况下运行git commit并从你那里收集消息后),Git将在那时打包所有在此时在索引中的文件,并用这些文件来创建新的提交。
因此,如果你已经更改或替换了工作树中的文件,则必须先将其复制回索引中。否则,Git将只是重复使用原始文件。
当你运行git add 文件时,Git会将工作树副本压缩成特殊的Git格式,并将压缩后的副本放入索引中。现在索引副本不再与HEAD副本匹配,但它与工作树副本匹配。
如果您再次更改工作树副本,则现在所有三个副本都不同!此时,git status将告诉您有“已准备提交的更改”和“未准备提交的更改”。
您可以随时更改工作树副本。这不会影响索引副本。
您可以使用计算机上的任何工具删除工作树副本而不删除索引副本,使用git rm --cached可以删除索引副本而不删除工作树副本,使用git rm可以同时删除两个副本。这些操作都不会影响任何现有提交 - 没有任何现有提交可以被更改!但是删除索引副本会影响下一次提交,因为当您运行git commit时,Git将打包所有在索引中的文件,所以如果文件从索引中消失了,它就不会出现在下一个提交中。
由于索引中的内容是下一个提交中的内容,这是索引的一个相当好的描述:索引大部分情况下是下一个提交中的内容。索引确实还有一些其他的角色要扮演,特别是在合并过程中,但我们不会在这里涉及到这些。
从技术上讲,索引实际上保存了对副本的引用。但是效果基本上与索引保存实际副本的效果相同,至少在您开始使用 `git update-index` 直接操作索引之前是这样的。

如何“取消暂存更改”

如果您想要将文件从当前(HEAD)提交返回到索引中,以其在当前提交中的形式,可以使用 git reset -- 文件名 命令。这里的 -- 只有在文件名类似于 git reset 选项或分支名称时才需要。例如,如果您有一个名为 master 的文件和一个名为 master 的分支,那么 git reset master 看起来像是您想使用 分支 名称,因此您必须写成 git reset -- master 来告诉 git 您指的是 文件 master

所以:git reset -- file将文件从HEAD提交复制到索引中。即使您已经删除了索引副本,这也是正确的:重置会将其放回。如果文件不在HEAD提交中,这也是正确的。如果您已将文件F添加到索引中,然后决定它不应再在索引中,您可以使用git reset -- F将其从索引中删除
(如果您喜欢,也可以使用git rm --cached F:此时具有相同效果。但是,如果您只想使索引副本与HEAD副本匹配,请使用git reset,因为它适用于两种情况:有一个HEAD副本或没有。)

如何知道哪些内容被暂存?

再次强调,实际上你无法“看到”索引中的文件副本。那么你怎么知道它是否与其他副本匹配呢?答案是:`git status`以简短而有用的方式告诉你。
假设你的工作树中有三个文件,分别为`README.md`、`main.py`和`util.py`。前两个文件来自于`HEAD`提交,而你刚刚创建了`util.py`,因此它既不在`HEAD`提交中,也不在索引中。
`git status`命令运行了两个比较过程:
首先,它将HEAD中的每个文件与索引中的每个文件进行比较。这样就会将HEAD中的README.md(HEAD副本)与:README.md(索引副本)进行比较。然后将HEAD:main.py与:main.py进行比较。对于每个相同的文件,git status不会显示任何内容。对于每个不同的文件,git status会显示需要提交的更改。如果索引中完全没有该文件,则已经将其删除。如果索引中的文件是全新的,则已经添加了一个新文件。因此,如果您知道HEAD提交中的内容,现在就可以确定索引中哪些文件是相同的:即git status未提及的任何内容。
接下来,告诉你HEAD与索引之间的差异或相同之后,git status继续将索引中的每个文件与工作树中的每个文件进行比较。所以,在这里,它将比较:README.md和README.md,:main.py和main.py,并发现有一个util.py不在索引中。对于每个相同的文件,git status不会显示任何内容。对于每个不同的文件,git status报告未暂存的更改。这是正确的,因为git commit不会使用工作树,它只会使用索引。对于像util.py这样的文件,在工作树中但不在索引中的文件,git status将其报告为未跟踪的文件。这就是未跟踪文件的含义:它是一个在工作树中但不在索引中的文件。
请注意,如果您从索引中移除一个文件,则该文件立即不再被跟踪!如果您使用git add将该文件添加到索引中,则现在它是被跟踪的。
这个有趣的HEAD:file:file语法是特定于Git本身的,仅适用于一些Git命令。其中之一是git show:例如,您实际上可以使用git show :README.md查看README.md的索引副本。由于git show是一个面向用户的命令,因此您可以相对安全地执行此操作:git show HEAD:filegit show :filegit show <commit:>file都是查看特定文件的特定副本的相当不错的方法,该文件已被转换为其内部Git形式,无论是保存在提交中还是准备进入提交中。
您还可以使用git ls-files查看所有文件的索引副本的名称。在大型存储库中,这将打印出很多名称!它不适用于日常使用,也不是非常用户友好的命令。

提交会读取索引;git checkout写入索引

要创建一个新提交,您只需将正确的文件复制到索引中并运行git commit。索引已经包含了当前提交的所有文件,因此您只需要更新任何要更改的文件,添加任何要添加的新文件,或删除任何要删除的文件。然后您git commit并创建一个新的提交。这个新提交现在是HEAD提交,现在HEAD提交和索引匹配。

如果HEAD提交和索引匹配,并且这两者与工作树匹配,则一切都很“干净”,您可以轻松地切换到另一个提交,例如:

git checkout otherbranch

(或者 Git 2.23 中的新命令 git switch,它类似于友好版本的 git checkout branch,不会把所有其他 Git 工具都塞进一个命令中——根据您的计算方式,git checkout 可以完成三到七个不同的工作)。

假设这个 checkout 成功,它必须:

  1. 用来自 otherbranch 尖端的所有文件填充您的暂存区(并删除任何不相关的文件);
  2. 用来自 otherbranch 尖端的所有文件填充您的工作目录(并删除任何不相关的文件);以及
  3. 使名称 HEAD 引用 otherbranch 的尖端提交。

完成所有这些操作后,您的 HEAD 提交、暂存区和工作目录再次匹配。


3有时候,即使索引和工作树不是“干净”的,git checkout命令也可以成功。这里“干净”一词定义不清,但如果想了解更多关于何时git checkout允许您在携带修改的情况下切换分支,请参见当当前分支存在未提交的更改时,检出另一个分支


结论

您无法直接查看文件的索引副本!您可以并且应该使用git status,它会将HEAD文件与索引副本进行比较。在一个大型项目中,有成百上千甚至数百万个文件,Git几乎不会对它们做出任何反应。它只会提到那些不同的文件。这非常有用。

每当HEAD、索引和工作树完全匹配时,git status不会输出任何内容。当git status发现一些不匹配的情况时,它会在已暂存提交更改和/或未暂存提交更改下打印文件名,具体取决于是HEAD和索引副本不一致,还是索引和工作树副本不一致,或两者都不一致。

git add将文件复制到索引中。如果它们之前已经在索引中了,那么现在它们被来自工作树的副本覆盖掉了。如果它们之前不在索引中,那么现在它们就在了。

git rm 命令从索引中删除文件。如果不使用 --cached 选项,它还会从工作树中删除相同的文件。如果使用了 --cached 选项,则保留工作树副本,这在现在是可以的,但是以后可能需要通过 git checkout 命令来删除工作树副本!

一般情况下,Git 会尽力避免破坏任何具有重要数据的工作树文件。那么什么是“重要的”呢?如果工作树文件 util.py 已添加并提交,并与 HEAD:util.py:util.py 匹配,此时 util.py 就不再是重要的了,因为如果您需要它,它已经在提交中了。因此,您可以 git checkout 一个旧的提交,该提交中没有 util.py,Git 将完全安全地从您的工作树中删除 util.py。只需执行最新的 git checkout 命令即可将其恢复。

一些 Git 命令,包括 git reset --hard销毁工作树文件。 git reset --hard 的理论是你想要重置索引和工作树。默认模式是 git reset --mixed,只重置索引副本。4 这就是为什么 git reset -- file “取消暂存”文件 - 将 HEAD:file 复制到 :file,但不会触及文件的工作树副本。
奇怪的是,git reset --softgit reset --mixedgit reset --hard 只允许你做所有文件。当你只想做一个文件时,使用git reset -- file,它总是使用--mixed。对于其他情况,你必须使用git checkout的许多备选模式之一。Git 2.23引入了一个新命令git restore,旨在使这个过程更少混乱。时间会告诉我们它是否做到了: git restore在技术上仍然是实验性的。

1

你需要再次添加 </head>。以下是一组示例命令。

echo testmodify>> FOO //Appends "testmodify" to FOO
git add FOO // Stage FOO to commit
git rm --cached FOO // Unstage FOO
git add FOO // Stage FOO

FOO仍然包含“testmodify”


0
根据我的经验,我运行了这个命令“git rm --cached .”,然后我的整个项目文件都被删除了,所以在推送任何提交之前,我会隐藏代码。这是解决此问题的简单易行的方法。
git stash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接