git - git中的“重置后未暂存的更改”是什么意思?
问题描述
我不小心add
将一堆文本文件编辑到我的 git 存储库中并试图取消暂存它们(在提交之前):
git reset dir/*.txt
运行命令时它说:
unstaged changes after reset:
dir2/file.h
dir4/file2.cc
...
这些文件与重置通配符无关。据我所知,这些文件仍处于已修改的提交阶段,并且看起来完好无损。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 不同的地方,称为index或staging area,或者(现在很少见)cache。这三个名字都是同一个东西的名字。3
1嗯,大多数时候。如果你把事情分解得足够好——特别是,如果你使用 Git 的一些非面向用户的工具——你可以做一些有趣的技巧或任何事情。还有所谓的裸存储库,它们根本没有工作树,除非您临时分配一个;这是另一个我们将在这里忽略的并发症。
2保存元数据所需的空间除外。这里的细节也很棘手。所有这一切的重点在于,通过重用旧文件,Git 可以保持其存储库较小。鉴于文件被压缩的方式,在某些情况下,具有许多提交的 Git 存储库有时比任何签出版本都小!
3 Git 的一些内部部分对索引(通常是文件.git/index
)和缓存(此时是内存中的数据结构)进行了区分。相当古老的git apply
命令有两个独立的标志,--index
和--cached
,做不同的事情。但git rm --cached
实际上意味着从索引中删除;例如,这里的index和cache是真正的同义词。
索引中有什么
从技术上讲,索引中的内容不是文件本身:它是文件名——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/HEAD
and .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 add
dir/*.txt
完成之后,git reset
现在做一个部分的git status
. 也就是说,它将Git 索引中的每个文件与工作树中同一文件的副本进行比较。对于那些不同的,Git 将它们列为“未分级”。Git 没有触及dir2/file.h
Git 的索引,但它在您的工作树中已经不同于在 Git 的索引中。所以git reset
这里的输出包括它。其他列出的文件也是如此。
5我认为它太复杂了,应该按照git checkout
拆分为git switch
and的方式拆分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,通常是——将索引副本与工作树副本进行比较,并让您知道是否要更新建议的下一次提交。我们通过阴影“看到”索引:当它与当前提交和/或工作树匹配时,我们什么也看不到。阴影越少,那里的阴影就越突出,所以效果很好。但这很棘手!
推荐阅读
- javascript - 跟踪点击广告不断刷新我的网页
- java - Java多线程等待任务结束
- selenium - 在 Selenium 中按顺序运行测试 - 如何设置测试设置和拆卸
- c++ - 我怎样才能让 gcc 警告我“int i = i;”
- docker - 通过 Vagrantfile 安装 Docker
- r - 使用 stringr 将字符对象转换为数字对象
- php - 如何从多个图像源中获取单个图像源
- api - 我可以使用 Google 表格连接到 API(使用 OAuth2)还是可以将 Postman 的每小时预定回复发送到 Google 表格?
- c# - Postgres 拒绝了 Dapper 列表参数
- flutter - 如何在 Flutter 中获取当前连接的 wifi 的 wifi 名称(SSID)