各种 git 状态,对 git 状态的低级理解(头、索引、工作状态)

various git states, low-level understanding of git status (head, index, working states)

假设您有一棵树和一个文件。假设我们对该文件只有两种可能的状态,ab。如果它丢失或不存在,ø。我正在尝试构建一个 table 来理解所有可能的 git-statuses。我相信我所说的是有道理的,但是,我已经用 ** 标记了问题区域:

head    index   working status
a       a       a       no changes**
a       a       b       unstaged:modified**
a       a       ø       unstaged:deleted**
a       b       a       staged:modified, unstaged:modified
a       b       b       staged:modified
a       b       ø       staged:modified, unstaged:deleted
a       ø       a       no changes**
a       ø       b       unstaged:modified**
a       ø       ø       staged:deleted**
ø       a       a       staged:new file
ø       a       b       staged:new file, unstaged: modified
ø       a       ø       staged:new file, unstaged: deleted
ø       ø       a       untracked

对于任何 *, ø, * 我几乎觉得它取决于父树,以及它是否在索引中......例如,a, ø, ø 就好像你'我们从工作树中删除了 blob,还有索引。但是,从索引中删除是什么样子的呢?是否只是将父树添加到临时区域并删除了树条目?如果是这种情况,那么 blob 本身的索引中没有条目是有道理的。

对于 index = head, (a,a,a, a,a,b, a,a,ø) 的任何记录,我假设这种状态实际上不会发生,除非你在玩管道命令。

如果您在我的 table 中看到错误,and/or 任何对此的启示都会很棒!提前致谢。

将索引视为 "the proposed next commit"

基于提交的版本控制系统,例如 Mercurial 和 Git 需要一种方法来区分 当前提交 中的内容——与任何提交一样,永远无法区分被改变——以及我们下一次提交的内容,在我们进行提交之前,这当然是可以改变的。 Mercurial 本质上为此使用了工作树,但是 Git 添加了一个额外的层,它称为 index。 Git 然后可以将一些额外的属性分配到索引中:例如,当且仅当文件在索引中时,它才会被 跟踪 。在合并期间,索引具有额外的属性(我们将在这里忽略 :-) )。最后一个并发症我会留到最后。

But, what does a removal from the index look like?

从索引中删除文件相当于(从字面上看)从索引中删除文件。尝试 运行ning git ls-files --stage 看看我的意思:对于第一行 (a, a, a = no changes),您会发现索引中有一个名为 a 的文件。对于您的行 a, ø, a,文件 a 根本不再在索引中(因此 不会 在您现在进行的新提交中)。

因此,调用文件 "staged" 可能有点误导。如果 a 根本不在索引中(但在 HEAD 中),则文件为 "staged for removal",但直接说 "not in the index" 更简单。一旦一个文件不在索引中,它也不会被跟踪,所以工作树版本变成一个未跟踪的文件!

这意味着您的 a, ø, b 条目也是错误的:此处文件已准备好删除,带有 b 的工作树变体是未跟踪的文件。

a, a, ø 条目可能是最难命名的条目。该文件仍在索引中,因此它将出现在您从此处开始的每次提交中,直到您将其从索引中删除。但是,该文件根本不在工作树中,因此您无法看到 它正在提交。如果您 运行 git add file 在此状态下,Git 通过删除索引条目将不存在的工作树文件复制到索引中。

(Mercurial 有类似的状态,因为有一个隐藏的内部数据结构称为 manifest,它与 Git 的索引起着相同的作用。如果该文件从工作树中丢失,但在清单中,Mercurial 调用文件 缺失 。Mercurial 尝试将工作树视为 进入下一次提交,所以你会认为如果文件像这样简单地消失了,它也应该从下一次提交中消失。根据文档,Mercurial 最初是这样的,但发现这太过分了容易出错。)

用于探索的低级工具

  • 使用git ls-tree -r HEAD查看当前提交的整个树(如果只有一棵树则不需要-r)。
  • 使用git ls-files --stage查看当前索引的整棵树:索引就像一棵扁平化的树,如果你有一个名为dir的子目录(子树)和文件d1d2,你得到名为 dir/d1dir/d2 的索引条目(相对于提交,其中顶层树将有一个名为 dir 的子树,子树将有两个 blob命名为 d1d2).
  • 使用OS的普通工具(例如ls)查看您的工作树。您的工作树本身对 git commit 意义不大,它只是将现有索引(无论其中包含什么)转换为一个或多个树对象以存储到新提交中。 (这一切都会改变你 运行 git commit 文件路径名参数或 -a 或类似的。这里 Git 可能会将文件添加到索引,甚至切换到临时备用索引它仅在提交完成之前使用。这取决于在提供其他路径时,您是使用 --include 还是 --only。)

多一条皱纹

因为 Git 拥有并公开了索引,所以它可以而且确实以两种不同的方式公开了一项功能。索引的每个条目都有两个标志位,称为 assume-unchangedskip-worktree。要使用 git ls-files 查看这些标志位,您必须添加 --debug 参数,但可以相对简单地描述它们的作用——事实证明有点太简单了——如:

  • 如果在索引条目上设置了 assume-unchanged 或 skip-worktree 标志,Git 应该 "close its eyes" git status.

  • 这可以大大加快 Git 的速度,但有一定的副作用。副作用可能就是我们使用这些位的目的。

当你运行 git status, Git 运行s 两个 git diffs。一个比较 HEAD 与索引,第二个比较索引与工作树。第一个差异决定 git status --short 输出的第一列,第二个差异决定第二列。

assume-unchanged 和 skip-worktree 位告诉 Git 在第二次差异期间不要费心比较文件。1 请注意,要设置这些位, 索引必须有一个文件条目,即文件 必须 被跟踪才能像这样被跳过。我们大概可以假设索引条目与 HEAD 条目匹配(如果不匹配,它将在下一次提交之后!),因此这些标志位的 effect 是我们永远不会看到文件被修改,并且 git add 通常也会跳过该文件:它不会将工作树版本复制回索引中。

我们的假设——索引条目与提交匹配——在某些极端情况下使我们误入歧途,这就是有 两个 位的原因。有关详细信息,请参阅 Git - Difference Between 'assume-unchanged' and 'skip-worktree'


1第一个差异非常快,因为特殊形式的文件(blob)在存储在提交或索引中时具有。具体来说,Git 可以通过比较它们的哈希 ID 来判断任何一个文件的 内容 是否与任何其他文件的内容匹配。如果哈希 ID 匹配,则文件相同;如果不是,则文件不同。 Git 此时不是在寻找完整的差异,而只是寻找 --name-status 风格的差异:"are the files the same, or not?"

第二个差异要慢得多,因为 Git 在最坏的情况下必须打开并读取每个文件的全部内容。即使只是询问文件系统 关于 文件(调用 lstat 系统调用)也比 Git 的内部比较哈希 ID 技巧慢得多。