首页 > 解决方案 > git中的“重置后未暂存的更改”是什么意思?

问题描述

我不小心add将一堆文本文件编辑到我的 git 存储库中并试图取消暂存它们(在提交之前):

git reset dir/*.txt

运行命令时它说:

unstaged changes after reset:
dir2/file.h
dir4/file2.cc
...

这些文件与重置通配符无关。据我所知,这些文件仍处于已修改的提交阶段,并且看起来完好无损。git 想告诉我什么?

标签: git

解决方案


Git 试图在这方面有所帮助——在这种情况下,可能过于有帮助。短语unstaged changes是一种思考方式,旨在简化 Git 的使用。这并不总是有效,因为 Git 很复杂。

这是基本的现实:Git 在任何时候都拥有每个文件的1 三个副本。其中两个副本实际上是不到的,至少在您通常使用的文件导航工具中看不到。

当您考虑提交是什么以及做什么时,这三个副本中的两个是有意义的:

  • 每个提交都包含每个文件的完整快照,永久保存。该文件的快照版本来自您(或任何人)提交时该文件外观。(每个提交还包含一些其他内容——一些元数据,或者关于提交本身的信息——但我们将在这里忽略它。)

  • 因为每个提交都有每个文件的完整快照,所以存储在提交中的文件不会存储为普通的日常文件。如果是这样,您的存储库会以可笑的速度快速增长。

  • 因此,Git 提交中的文件以一种特殊的、只读的、仅限 Git 的、压缩的和去重复的格式存储。因为它们是只读的,所以提交共享这些文件副本是完全安全的。使用一万个文件进行新提交,但其中 9999 个与上次提交相同,实际上只是重新使用了 9999 个文件,并且仅对更改的文件进行快照。并且,如果文件已更改之前某个提交时的状态,则最后一个文件将与之前的提交共享,这样新的快照就不会占用任何空间。2

上述所有问题的问题在于,提交中的文件完全无法用于完成任何实际工作:它们只能被Git读取,而没有任何东西——甚至 Git 本身——可以写入它们。因此,要使用提交,Git 必须将其复制出来,将文件从其特殊的仅 Git 格式扩展为普通的日常文件。这些日常形式的文件进入 Git 所称的工作树工作树

所以这一切都是有道理的:某个文件的两个“活动”副本,例如README.md或其他文件,是当前提交版本- 这个是只读的,并且在您选择作为当前提交的任何提交中,并且只有Git 可以查看和读取它——以及你的工作树版本,它实际上不在Git 中。Git 已将其提取到工作区,您现在正在使用它,但它不在存储库中。它是从存储库中复制出来的;从那时起,它可能会也可能不会改变。

我们真正需要的只是两个副本,而其他版本控制系统——不是 Git——到此为止,只有两个“活动”副本。但无论出于何种原因,无论好坏,Git 都不会止步于此。Git 插入,介于冻结README.md和有用之间,第三个副本。第三个副本位于 Git 不同的地方,称为indexstaging area,或者(现在很少见)cache。这三个名字都是同一个东西的名字。3


1嗯,大多数时候。如果你把事情分解得足够好——特别是,如果你使用 Git 的一些非面向用户的工具——你可以做一些有趣的技巧或任何事情。还有所谓的存储库,它们根本没有工作树,除非您临时分配一个;这是另一个我们将在这里忽略的并发症。

2保存元数据所需的空间除外。这里的细节也很棘手。所有这一切的重点在于,通过重用旧文件,Git 可以保持其存储库较小。鉴于文件被压缩的方式,在某些情况下,具有许多提交的 Git 存储库有时任何签出版本都小!

3 Git 的一些内部部分对索引(通常是文件.git/index)和缓存(此时是内存中的数据结构)进行了区分。相当古老的git apply命令有两个独立的标志,--index--cached,做不同的事情。但git rm --cached实际上意味着从索引中删除;例如,这里的indexcache是真正的同义词。


索引中有什么

从技术上讲,索引中的内容不是文件本身:它是文件——Git 看到的文件的全名,加上正斜杠,例如path/to/file.ext——还有一大堆内部的东西,其中一些你可以见git ls-files --stage。(尝试一下,但请注意,它会在没有暂停的情况下溢出大量输出。)

不过,撇开技术细节不谈,索引实现的是它保存了您提议的下一次提交。索引中的文件与提交中的文件形式相同——它们是预压缩和预去重的——但与提交的副本不同,Git 可以通过删除去重副本并创建一个新副本覆盖它们去重副本。

最初,当您进行某些特定提交时(例如,分支上git checkout的最新或提示提交),Git 会使用feature该提交中的文件填充其索引,并使用这些文件填充您的工作树。 结果是所有三个活动副本都匹配。 提交的副本是只读的,与索引副本匹配。可以替换的索引副本与提交的副本和您的工作树副本相匹配。

当你做你的工作时,你会修改一些文件。这些自然是的文件副本,格式可用。Git 不使用这些文件!Git之前通过从提交中提取它们来创建它们,但除此之外,Git 只是将这些文件留给。如果你改变了一个,你需要告诉 Git 对它的索引/暂存区副本做一些事情。

你所做的就是运行git add。这让 Git读取您的工作树副本,对其进行压缩,对所有存储的文件进行重复数据删除,并更新其索引副本。现在 Git 的索引副本与您的工作树副本匹配。

请注意,由于存在三个副本,因此您可以使所有三个副本不同步:只需检查一些提交,修改一些文件,git add在该文件上运行,然后再次修改该文件。现在,永久冻结的提交副本与索引副本不同,索引副本是您add之前编辑的副本,并且您的工作副本仍然与那个不同,因为您在没有git add-ing 的情况下再次更改了它。

git status通过做两个差异来工作

当你运行时git status,它首先会打印出一些人们认为有帮助的整体信息,例如当前分支名称、4这个分支“领先”或“落后”某个其他分支或远程跟踪名称的距离等等。然后它进入文件

它列出的第一组文件(如果它在此处列出的话)是它为提交而调用的那些。然而,它真正在做的是将当前提交与 index 进行比较。对于每个相同的文件,它什么也没说。对于每个不同的文件,它表示已暂存以进行提交

它列出的第二组文件(如果有的话)是它调用的那些未暂存的文件。不过,它真正在做的是将其索引与您的工作树进行比较。对于每个相同的文件,它什么也没说。对于每个不同的文件,它表示not staged for commit


4 Git 将当前分支名称存储在它所调用的名称中HEAD。每个工作树都有一个HEAD索引,每个工作树都有一个索引;mainHEAD和 index通常是.git/HEADand .git/index,并且任何添加的工作树都会得到一对新的。您不需要知道这一点,但有时只需查看一下.git/HEAD(目前只是一个纯文本文件)就可以很好地了解这一点。不过,这可能会在未来发生变化:HEAD例如,曾经是一个符号链接。


git reset

git reset命令很复杂。5git reset我们将忽略大多数复杂情况,只关注你跑步时遇到 的那种情况:

git reset dir/*.txt

git reset现在,您可以使用新的(从 Git 2.23 开始)实现这种特殊的东西git restore。它将文件从当前提交复制到 Git 的索引,而不会触及您的工作树。6

当你这样做时,你会找到一个文件名列表。这有点复杂,因为可能是你的 shell,也可能是 Git,如果是 Git,Git 将找到的文件名集可能与你的 shell 将找到的文件名集不同。为简单起见,让我们假设以任何一种方式找到的文件集都是相同的:shell 将找到的所有文件都是 Git在当前提交中dir/*.txt找到匹配的文件集。dir/*.txt所以 Git 将所有这些文件从当前提交复制到 Git 的索引中。

如果它们已经在 Git 的索引中——作为该文件的那个版本——则没有效果。但是,无论 Git 的 index / staging-area 中的任何文件是不同的——可能是因为你在更改副本后使用git add它——这会覆盖更新的索引副本,而是将其设置回与提交的副本匹配。因此,对于匹配.git adddir/*.txt

完成之后,git reset现在做一个部分的git status. 也就是说,它将Git 索引中的每个文件与工作树中同一文件的副本进行比较。对于那些不同的,Git 将它们列为“未分级”。Git 没有触及dir2/file.hGit 的索引,但它在您的工作树中已经不同于在 Git 的索引中。所以git reset这里的输出包括它。其他列出的文件也是如此。


5我认为它复杂了,应该按照git checkout拆分为git switchand的方式拆分git restore。当然,出于兼容性的目的,即使在拆分之后,git reset仍然会有 ,就像 Git 2.23 和更高版本仍然有 一样。git checkout

6git restore实际上强大,因为您可以选择任何提交,而不仅仅是当前的提交,并且您可以选择是将其复制到 Git 的索引、您的工作树还是两者兼而有之。因此,如果按照我在脚注 5 中的想法git reset 进行git restore拆分,则两个命令之一将是.


结论

git reset成功修改文件的某些索引副本后,运行部分git status. 这会将文件的索引副本与您的工作树副本进行比较。它不仅比较单独重新设置的文件,还比较所有索引条目。由于索引列出了将在下一次提交中的每个文件,因此这可能是很多文件。

请注意,当您修改文件然后运行git add时,您所做的是在 Git 的“暂存区”(索引)中安排每个文件的中间副本,以便为下一次提交安排一切。这就是我们称之为暂存区的原因:我们将特定文件的特定副本“放在舞台上”,然后拍摄我们称之为提交的照片快照。该提交是根据舞台上的内容构建的,这不一定与您正在使用的内容相同。

其他版本控制系统不这样做:它们没有单独的暂存区域,并且当您进行新提交时,它们会快照您的工作树。这有其自身的优点和缺点,最终需要一个文件列表(通常称为清单),因为工作树中往往有很多应该提交的文件。Git 使用它的索引来达到这个目的:如果你不将文件复制索引中——即,不要将它放在“舞台”上以供以后的快照使用——它就不会在快照中。

但是,因为索引具有每个文件的完整副本,所以会生成三个副本,这导致了这些奇怪的情况。由于您看不到索引副本,因此您需要一些东西——<code>git status,通常是——将索引副本与工作树副本进行比较,并让您知道是否要更新建议的下一次提交。我们通过阴影“看到”索引:当它与当前提交和/或工作树匹配时,我们什么也看不到。阴影越少,那里的阴影就越突出,所以效果很好。但这很棘手!


推荐阅读