它几乎但不完全是对称的。
确实git add file
将文件复制到舞台(又名“索引”)。但是,它这样做的方式有点奇怪。
在 git repo 中,所有内容都存储为 git“对象”。每个对象都有一个唯一的名称,即它的 SHA-1(那些 40 个字符的字符串,例如— 来自我这里753be37fca1ed9b0f9267273b82881f8765d6b23
的实际情况)。.gitignore
该名称是通过计算文件内容的哈希来构造的(或多或少——有一些噱头可以确保您不会从目录树或提交中创建文件,并导致哈希冲突,例如)。Git 假设无论内容如何,SHA-1 都是唯一的:没有两个不同的文件、树、提交或带注释的标签会哈希到相同的值。
文件(和符号链接)是“blob”类型的对象。因此,git repo 中的文件被散列,并且在某个地方,git 有一个映射:“文件名为.gitignore
”到“散列值753be37fca1ed9b0f9267273b82881f8765d6b23
”)。
在 repo 中,目录树存储为“树”类型的对象。树对象包含名称(如.gitignore
)、模式、对象类型(另一棵树或 blob)和 SHA-1 的列表:
$ git cat-file -p HEAD:
100644 blob 753be37fca1ed9b0f9267273b82881f8765d6b23 .gitignore
[snip]
提交对象为您(或 git)提供一个树对象,最终为您提供 blob ID。
另一方面,暂存区(“索引”)只是一个文件,.git/index
. 该文件包含1名称(以一种有趣的略微压缩的形式,使目录树变平)、合并冲突情况下的“阶段编号”和 SHA-1。同样,实际文件内容是 git repo 中的一个 blob。(Git 不在索引中存储目录:索引只有实际文件,使用扁平格式。)
所以,当你这样做时:
git add file_name
git 这样做(或多或少,我故意掩盖过滤器):
- 计算文件
file_name
( git hash-object -t blob
) 内容的哈希值。
- 如果该对象尚未在 repo 中,请将其写入 repo(使用
-w
to 选项hash-object
)。
- 更新
.git/index
(或$GIT_INDEX_FILE
),使其在名称下具有映射file_name
到来自 的名称git hash-object
。这始终是“阶段 0”条目(这是正常的、无合并冲突的版本)。
因此,该文件并不是真正“在”暂存区域,而是真正“在”存储库本身!暂存区域中的内容是名称到 SHA-1 映射。
相比之下,git checkout [<tree-ish>] -- file_name
这样做:
如果给定一个<tree-ish>
(提交名称、树对象 ID 等 - 基本上 git 可以解析为树的任何内容),则通过将参数转换为树对象来从找到的树中进行名称查找。使用如此定位的对象 ID,将索引中的哈希更新为阶段 0。(如果file_name
命名树对象,git 会递归处理树所代表的目录中的所有文件。)通过创建阶段 0 条目,任何合并冲突file_name
都是现在解决了。
否则,在索引中进行名称查找(不确定如果file_name
是目录会发生什么,可能是 git 读取工作目录)。将 转换file_name
为对象 ID(此时将是一个 blob)。如果没有第 0 阶段条目,则错误并显示“未合并”消息,除非给出-m
, --ours
,--theirs
选项。使用-m
将“取消合并”文件(删除阶段 0 条目并重新创建冲突的合并2),同时--ours
保留--theirs
任何阶段 0 条目(已解决的冲突保持已解决)。
在任何情况下,如果这还没有出错,请使用因此定位的 blob SHA-1(s)将 repo 副本(或副本,如果file_name
命名一个目录)提取到工作目录中。
所以,简短的版本是“是与否”:git checkout
有时修改索引,有时只使用它。但是,文件本身永远不会存储在索引中,只会存储在 repo 中。如果您git add
是一个文件,请对其进行更多更改,然后再更改git add
一次,这会留下 git fsck 将发现的“悬空 blob”:一个没有引用的对象。
1我故意省略了索引中的许多其他内容,以使 git 表现良好,并允许--assume-unchanged
等。(这些与此处的添加/签出操作无关。)
2此重新merge.conflictstyle
创建diff3
尊重diff3
对git checkout -m
.