将索引视为“建议的下一次提交”
基于提交的版本控制系统(例如 Mercurial 和 Git)需要一种方法来区分当前提交中的内容(与任何提交一样,永远无法更改)和我们进行的下一次提交中的内容,这当然必须在我们提交之前可以更改。Mercurial 基本上为此使用了工作树,但 Git 添加了一个称为index的额外层。然后,Git 能够为索引分配一些额外的属性:例如,当且仅当文件在索引中时,它才会被跟踪。在合并期间,索引具有额外的属性(我们将在这里忽略它们:-))。最后一个并发症我将留到最后。
但是,从索引中删除是什么样的?
从索引中删除文件相当于(从字面上看)从索引中删除文件。尝试运行git ls-files --stage
以了解我的意思:对于您的第一行 ( a, a, a = no changes
),您会发现在 index.html 中有一个名为的文件a
。对于您的 row a, ø, a
,该文件a
根本不再在索引中(因此不会在您现在所做的新提交中)。
因此,将文件称为“暂存”可能有点误导。如果a
根本不在索引中(但在 中HEAD
),则文件“暂存以待删除”,但更简单地说“不在索引中”。一旦文件不在索引中,它也不会被跟踪,因此工作树版本成为未跟踪文件!
这意味着您的a, ø, b
输入也是错误的:这里的文件被暂存以便删除,并且工作树变体b
是一个未跟踪的文件。
该a, a, ø
条目可能是最难命名的。该文件仍在索引中,因此它将在您从此处开始的每次提交中,直到您将其从索引中删除。但是,该文件根本不在工作树中,因此您看不到它正在提交。如果您git add file
在此状态下运行,Git 通过删除索引条目将不存在的工作树文件复制到索引中。
(Mercurial 也有类似的状态,因为有一个隐藏的内部数据结构称为清单,它的作用与 Git 的索引相同。如果文件从工作树中丢失,但在清单中,Mercurial 称文件丢失. Mercurial 尝试将工作树视为进入下一次提交的内容,因此您会认为如果文件像这样简单地消失了,它也应该从下一次提交中消失。根据文档,Mercurial 的行为方式是这样的原来,但发现这太容易出错了。)
用于探索的低级工具
- 用于
git ls-tree -r HEAD
查看当前提交的整个树(如果只有一棵树您不需要-r
)。
- 用于
git ls-files --stage
查看当前索引的整个树:索引就像一棵扁平的树,如果您有一个dir
以 filesd1
和命名的子目录(子树),您将获得名为and的d2
索引条目(与提交相比,顶部树将有一个名为的子树,并且该子树将有两个名为和) 的 blob。dir/d1
dir/d2
dir
d1
d2
- 使用操作系统的普通工具(
ls
例如)查看您的工作树。您的工作树本身对 没有什么意义git commit
,它只是将现有索引(无论其中的任何内容)转换为一个或多个树对象以存储到新的提交中。git commit
(您使用文件路径名参数或类似参数运行的所有更改-a
。这里 Git 可能会将文件添加到索引中,甚至切换到它使用的临时备用索引,直到提交完成。这取决于在提供其他路径时是否,你使用--include
或--only
。)
多了一道皱纹
因为 Git 拥有并公开了索引,它可以并且确实以两种不同的方式公开了另外一项功能。该索引的每个条目有两个标志位,称为假设不变和跳过工作树。要查看这些标志位,git ls-files
您必须添加--debug
参数,但它们的作用可以相对简单地描述 - 事实证明有点过于简单 - 如下:
当你运行时git status
,Git 运行两个 git diff
s。一个比较HEAD
与索引,第二个比较索引与工作树。第一个差异决定了git status --short
输出的第一列,第二个差异决定了第二列。
假设不变和跳过工作树位告诉 Git 在第二个差异期间不要费心比较文件。1 请注意,要设置这些位,索引必须具有文件的条目,即,必须跟踪文件才能像这样跳过。我们大概可以假设索引条目匹配HEAD
条目(如果不匹配,它将在下一次提交之后!),所以这些标志位的效果是我们永远不会看到文件被修改,并且git add
通常会跳过文件同样:它不会将工作树版本复制回索引中。
我们的假设——索引条目与提交匹配——在某些极端情况下使我们误入歧途,这就是有两个位的原因。有关此的更多信息,请参阅Git - 'assume-unchanged' 和 'skip-worktree' 之间的区别
1第一个差异非常快,因为存储在提交或索引中时具有特殊的表单文件(blob)。具体来说,Git 可以通过比较它们的哈希 ID 来判断任何一个文件的内容是否与任何其他文件的内容匹配。如果哈希 ID 匹配,则文件相同;如果不是,则文件不同。Git 在这一点上并不是在寻找一个完整的差异,而只是一个--name-status
风格差异:“文件是否相同?”
第二个差异要慢得多,因为在最坏的情况下,Git 必须打开并读取每个文件的全部内容。即使只是向文件系统询问文件(调用lstat
系统调用)也比 Git 的内部 compare-hash-IDs 技巧慢得多。