首页 > 解决方案 > 使用短标志了解 git status 的输出

问题描述

$ git status -s
 M README
MM Rakefile
A  lib/git.rb
M  lib/simplegit.rb
?? LICENSE.txt

输出有两列 - 左列表示暂存区的状态,右列表示工作树的状态。因此,例如在该输出中,README 文件在工作目录中被修改但尚未暂存,而 lib/simplegit.rb 文件已被修改和暂存。Rakefile 被修改、暂存,然后再次修改,因此对它进行了暂存和未暂存的更改。

以上来自 Scott Chacon 和 Ben Straub 的 Pro Git,由 Apress 出版。

我对暂存区和工作树之间的区别感到困惑。我会解释我认为是真的。

“README 文件已在工作目录中修改但尚未暂存”:我们不跟踪此文件。尽管如此,Git 知道它已被修改。从上一张快照开始。

“lib/simplegit.rb 文件已修改并暂存”:修改后我们已暂存文件。剩下的就是提交。

“Rakefile 被修改、暂存,然后再次修改,因此对它进行了暂存和未暂存的更改。”:就像之前的文件一样,我们已经暂存了修改后的文件。下一步是什么?

标签: gitterminology

解决方案


“README 文件已在工作目录中修改但尚未暂存”:我们不跟踪此文件。尽管如此,Git 知道它已被修改。从上一张快照开始。

不,这是错误的。暂存区的棘手部分是它通常是不可见的。这导致人们在试图理解它是如何工作的时候走上了错误的道路。

首先,让我们处理一些 Git 术语。此时有三个感兴趣的实体:当前提交暂存区域(实际上有三个名称)和工作树。staging-area 的三个名称是indexstaging-areacache,这三个名称反映了 Linus Torvald 最初选择(“索引”)的低质量或不可见的 staging-area 的巨大重要性,或两者兼而有之。(我认为两者都有。)让我们更深入地研究每一个:

  • 当前提交,我们也可以通过名称HEAD(全部大写1)命名,当然是一个提交——它是您(或任何人)运行时在暂存区域中的所有文件的快照git commit。此快照是永久的(大部分)和只读的(完全)。它的真实名称不是HEAD——这只是我们现在可以找到它的一个象征性名称——而是一些丑陋的大哈希 ID。哈希 ID 看起来是随机的,但实际上是提交的完整内容的加密校验和。这就是为什么不能更改提交的原因——更改任何内容都会更改校验和,从而导致不同的提交。

    2 次提交中存储的文件也是只读的。它们以一种特殊的、仅限 Git 的压缩形式存储。这种特殊的压缩具有很好的特性,如果一个文件的内容从一个提交到另一个提交是相同的,那么这些提交都共享底层的压缩文件映像。这意味着您可以提交一个大文件数百万次,如果您愿意,并且不会使用比提交该文件一次更多的空间。

  • 索引/暂存区/缓存是这种疯狂的几乎看不见的数据结构。它始终包含所有文件,就像提交包含所有文件一样。索引中的文件也是这种特殊的压缩 Git 格式。索引/暂存区域中的文件副本与提交中的副本之间的主要区别在于可以覆盖索引。

    (索引还缓存——因此得名“缓存”——关于工作树的信息,以使 Git 运行得更快。这两个事实,索引保存所有准备进入下一次提交的文件,并且它缓存与其他类似的版本控制系统相比,有关工作树的内容变得git commit如此之快。)

  • 工作树是三者中最简单的,但从某种意义上说,它也是 Git 最不关心的。这是您处理文件的地方。这些文件采用其他计算机程序可以理解的普通格式。它们对最重要,但对 Git 最不重要:--bare存储库没有工作树,但 Git 仍然可以运行(当然以更有限的方式)。

工作树是您可以轻松直接地看到的这三件事中唯一的一件。只需使用列出文件或查看文件的任何命令:它们就在那里,一目了然。幸运的是,通过检查提交也很容易看到。

当你最初检查某个特定的提交时——例如,通过git checkout mastergit checkout develop——Git 会填充你索引/暂存区域来自该提交的工作树。它设置HEAD为正确哈希 ID 的符号名称。这样,索引中已经包含HEAD提交中的所有相同文件,并且工作树中包含索引中的所有相同文件。

如果您修改工作树中的文件,然后git add在其上运行,Git 会将该文件的工作树版本复制到索引/暂存区域中。现在HEAD提交版本和索引版本不同,但索引版本和工作树版本彼此一致。

如果您修改工作树中的文件但不在其上运行git add,则HEAD与索引版本一致,但索引版本与工作树版本不一致。

如果您修改工作树中的文件,则 (1) 使用git add将其复制到索引/暂存区和 (2)再次修改它,现在该文件的所有三个版本都不同。这是您将看到MM状态的地方。

git status实际上,正在做的是运行两个差异。第一个HEAD与索引进行比较。这里的任何不同之处都是“为提交而准备的”。第二个差异将索引与工作树进行比较。这里有什么不同的是“不是为提交而准备的”。差不多了——我们快完成了!

最后,让我们看一下应用于文件的跟踪术语。在 Git 中,当且仅当文件在索引/暂存区域中时,它才会被跟踪。真的就是这么简单!棘手的部分是判断一个文件是否真的在索引中,因为它通常在那里是不可见的。

git status命令比较索引:首先,它比较HEADvs index。假设某个文件在两者中HEAD并且在两者index中具有相同的内容。然后你不会在这里看到它。同样,如果它在索引和工作树中相同,您将不会在此处看到它。因此,如果文件索引中,但同时HEAD与工作树版本匹配,则它是不可见的。

假设某个文件不在索引中。如果它在 中HEADgit status会告诉你在HEAD和 索引之间,文件被删除了——D在短输出的第一列中。因此,在这种情况下,您可以知道:该文件已从索引中消失,并且不再被跟踪。它不会出现在下一次提交中。

假设某个文件不在HEAD,但索引中。在这种情况下git status,将告诉您在HEAD和索引之间,文件被添加-A在简短输出的第一列中。因此,在这种情况下,您可以知道该文件现在已被跟踪,并且将在下一次提交中。

棘手的情况发生在文件既未跟踪又被忽略时,因为现在,如果文件不在提交中HEAD(根据定义,它不在索引中——我们只是说它是未跟踪的),第一列不能告诉你任何东西:它不在这两个实体中的任何一个中,所以 Git 在这里什么也没说。如果文件存在于工作树中,第二列可能会告诉您索引和工作树不匹配,但是由于您告诉 Git 应该忽略未跟踪的工作树文件,git status因此这里不再提及任何一个。

最后,有几点值得一提:

  • 您实际上可以查看索引。运行git ls-files --stage以快速查看暂存区中的大部分内容。这在大型项目中是不切实际的,正是因为 staging-area 保存了每个文件的副本——嗯,每个将被提交的文件。那可能是数以万计的文件。查看提交和索引/暂存区域之间的差异会更有用HEAD,所以这就是git status(在--short输出的第一列中)。

  • 您还可以直接查看提交的内容。运行git ls-tree -r HEAD以查看所有已提交的文件。输出类似于git ls-files --stage。(它添加了 Git 对象类型名称并去掉了暂存编号,并使用树结构而不是索引的扁平树。)git ls-files --stage这主要用于调试 Git 或编写花哨的新命令,而不是用于常规工作。

这里的关键是通过与索引进行比较,然后将索引与工作树进行git status比较来总结三个感兴趣实体的状态。HEAD这两列向您展示了它们之间的差异,只剩下一个字母代码和一个文件名。尽管下一次提交将是当时索引/暂存区域中每个文件的快照,但告诉您该快照与当前快照或潜在快照相比有何不同更有用通过将工作树文件复制到索引中来制作。


1在 Windows 和 MacOS 上,打开一个名为的文件会readme.txt打开一个名为的现有文件README.TXT(反之亦然),您可以使用小写字母,但 Git 有很多地方可以硬编码全大写HEAD字符串,因此最好坚持使用。如果您不太喜欢打字,则该字符@是 的同义词HEAD

2从技术上讲,提交存储对象的哈希 ID。树对象根据需要存储每个文件的名称、模式(100644 或 100755)和内容哈希 ID,以及子树的名称和哈希 ID。因此,文件内容实际上并不提交中,而是作为blob对象布置在提交和树对象旁边。这是提交(和索引!)共享 blob 对象的机制,因此无论您拥有多少个大文件的快照,您实际上在存储库数据库中只有一个副本。


推荐阅读