DAG如何在Git内部存储? 例如,考虑DAG A-> B-> C-> d - > E-> F-&GT克
您以某种方式保存以下信息。 A-> B,B-> C,C-> D,A-> E,E-> F,F-> G. 那它是如何存储的?给定一个特定节点,你怎么知道它在哪个分支上?
答案 0 :(得分:4)
这样我们就不会迷失在术语中:DAG通常是一个图形 G =(V,E),由顶点集 V 和边集 E ,其中边集中的每个边都强加一个方向(也称为 arc ),并且没有循环(没有来自任何顶点的路径)通过各种弧回到自己)。边的典型表示是顶点对,例如,如果节点表示为单个大写字母(如在您的示例中),我们可能在边集中&lt; A,B&gt; < em> E 表示顶点 A 在该方向上连接到顶点 B 。也就是说,&lt; A,B&gt; 表示从 A 到 B 有一个弧。
Git不使用这种典型的表示形式。相反,每个&#34;顶点&#34;是一个提交,其唯一标识符是其哈希ID(至少,我倾向于在图中调用这些&#34;节点&#34;而不是&#34;顶点&#34;)。每个提交按其哈希ID列出其父提交。因此,如果提交A
(真正a234567890123456789012345678901234567890
或某些此类)是提交B
的父(真b876543210...
),则A
中没有任何内容命名B
,但B
命名为A
时有一个父ID。
换句话说,Git图中的边缘都是向后。
同时,分支名称指向单个提交节点,该节点被指定为该分支的 tip commit 。例如,master
可以解析为08bb3500a2a718c3c78b0547c68601cafa7a8fd9
。
名称HEAD
或者包含当前分支名称,或者包含当前提交的原始哈希ID。使用git rev-parse
,我们可以将任何名称(包括HEAD
)转换为相应的ID:
$ git rev-parse HEAD
08bb3500a2a718c3c78b0547c68601cafa7a8fd9
我们现在可以回答这些问题:
那么它是如何存储的?
存储库中的提交节点作为commit
类型的对象存在,其内容(在通常的压缩扩展之后)只是git cat-file -p
所示格式的纯文本:
$ git cat-file -p HEAD | sed 's/@/ /'
tree a775288b86ae652ea163357939d852cdd927eed6
parent 36cafe44443fcca9eb35399ef0e9bfe289ec5dde
author Junio C Hamano <gitster pobox.com> 1468959976 -0700
committer Junio C Hamano <gitster pobox.com> 1468959976 -0700
Sixth batch of topics for 2.10
Signed-off-by: Junio C Hamano <gitster pobox.com>
这告诉我们08bb3500a2a718c3c78b0547c68601cafa7a8fd9
到36cafe44443fcca9eb35399ef0e9bfe289ec5dde
有一个弧。
要找到完整的图形 - 所有边和顶点/节点的集合 - 我们从 所有合适的起点 开始(见下文)并从中读取这些对象库。对于提交对象,我们读取了他们的parent
行,它们提供了额外的节点ID,并提供了一个弧:从我们刚读取的节点到每个parent
行中命名的节点。 (合并提交有多个parent
行,而不是列出多个ID的parent
行,但当然这非常简单。另请注意每个带注释的标记对象指向另一个对象,通常是提交,所以当我们找到类型为tag
的对象时,我们应该读取它的object
行,并重复这个过程,直到我们找到一个非标记对象。但是我们如果我们仅从分支名称开始,则不会找到任何此类对象;请参阅下文。)
(在普通的非Git DAG中,没有特别区分弧,但在Git中,为每个节点列出的第一个父与任何其他父项区别开来,使用后缀 - ^
语法时,顺序很重要。特别是,当您进行合并提交时,以前为HEAD
的ID将成为新合并提交的第一个父级。)< / p>
给定一个特定节点,你怎么知道它在哪个分支上?
这个问题有一个缺陷:它假设一个节点在分支上。
事实上,节点可能位于 no 分支上,或许多分支上。
现在让我们回到 所有合适起点的概念 。有什么起点?如果我们有一个典型的图形表示,我们将在某处列出全套顶点(或节点)。在Git中,我们没有这个。 1 相反,我们有引用,它们大多是以refs/
开头的名称。分支和标签是引用形式,分别以refs/heads/
和refs/tags/
开头。 Git命令git for-each-ref
可以让你找到所有这些引用。
有一些特殊用途参考不以refs
开头:HEAD
,MERGE_HEAD
,CHERRY_PICK_HEAD
,ORIG_HEAD
等。一些Git命令也需要在这里查看。但是,对于您的特定情况,我们只关心分支名称,所有这些都以refs/
开头 - 事实上,refs/heads/
- 所以我们可以运行git for-each-ref refs/heads
列出所有分支名称。 (for-each-ref
命令在这里为我们添加额外的/
,理论上它类似于目录列表操作。)
因此,要找出节点 X (对于某些 X )是否在一个或多个分支上,如果是,那么我们从节点ID开始存储在每个分支名称下。这标识了该分支的 tip commit 。然后,我们遵循该提交的父母和那些节点&#39;父母,等等,直到我们的父母用完(使用我们喜欢的任何搜索算法)。如果我们遇到节点 X ,节点 X 就在该分支上。
因此,节点 X 包含在每个分支中,我们可以从该分支的提示开始找到 X 。这是git branch --contains
显示的内容。
(标记名称通常直接指向提交节点(&#34;轻量级标记&#34;)或标记对象(&#34;带注释的标记&#34;)。因此,如果我们允许所有引用,我们必须准备好处理标签。分支名称被限制为仅指向提交节点。)
1 好吧,我们可以对整个存储库进行详尽的遍历,以查找所有对象。例如,这就是git fsck
或git gc
。
答案 1 :(得分:2)
每个提交都有N个父项,N是一个等于或大于0的整数。根提交有0个父项。非合并提交有1个父级。合并提交有2个或更多父母。父母是另一个提交。除root之外的每个提交都存储其父项的sha1(s)。因此,提交被组织为DAG。知道提交,我们可以告诉它的所有祖先。 git log -1 <commit> --pretty=%P
可以输出提交的父级。
在一个git repo中,可能有一个或多个DAG。分支是指向的引用,或者是附加到提交(或另一个分支)的锚。它有点像C中的指针,提交为结构或类变量,sha1为其地址。分支可以从一个提交移动到另一个提交。有时它会自动移动,通过引入新的提交,包括git commit
,git rebase
,git merge
,git cherry-pick
等。有时它会通过git reset
按照我们的意愿移动。
当我们说一个提交在一个分支上时,可以用另一种方式理解,这个提交等于,或者该分支引用的提交的祖先。 git branch --contains <commit> --all
可以告知提交所在的分支。