0%

Git的理解-分支

这是我理解Git的第二篇文章,在阅读这篇文章之前请先参考第一篇Git的理解-数据模型

Snipaste_2020-05-30_11-39-38.png

让我们从上次离开的地方开始–git的数据模型。只是这一次我们将简化一下,只显示提交对象,并给它们一些符号化的名字,而不是校验和(只是为了更容易遵循),所以我们得到的图是这样的。

Snipaste_2020-05-30_11-45-13.png

熟悉图形理论的人会注意到,这是一个有向无环图形(DAG)。这意味着,图中节点之间的连接边(在git的情况下,就是commits)是有方向的,如果你从一个节点开始,沿着图中的边的方向行驶,那么你永远不可能到达你开始时的同一个节点(没有 “往返”)。

在我们的示例图中,我们可以将三个分支分别标记为红色(包含提交A,B,C,D,E)和绿色(包含提交A,B,F,G)。我们将它们标记为红色(包含提交A,B,C,D,E),蓝色(包含提交A,B,F,G)和绿色(包含提交A,B,H,I,J)。

Snipaste_2020-05-30_11-51-46.png

所以这是定义分支的一种方式——将其与包含的提交列表关联起来。然而,git 并不是这样做的。Git 使用了一个更简单的方法。git 不需要拥有一个属于一个分支的所有提交的列表并保持更新,而是只记录一个分支上的最后一次提交。通过知道一个分支的最后一次提交,只需按照 git commit-graph 中的有向边,就可以重构该分支的整个提交列表。例如,要定义我们的蓝色分支,我们只需要知道蓝色分支上的最后一次提交是G,如果我们需要蓝色分支上的所有提交列表,我们只需要按照G开始的有向图边进行跟踪。

Snipaste_2020-05-30_11-58-45.png

这就是git通过保持指向提交的指针来管理分支的方式。让我们来看看它是如何做的。

首先我们初始化一个空库。

1
git init

看一下.git目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.
|-- HEAD
|-- config
|-- description
|-- hooks
| |-- applypatch-msg.sample
| |-- commit-msg.sample
| |-- fsmonitor-watchman.sample
| |-- post-update.sample
| |-- pre-applypatch.sample
| |-- pre-commit.sample
| |-- pre-merge-commit.sample
| |-- pre-push.sample
| |-- pre-rebase.sample
| |-- pre-receive.sample
| |-- prepare-commit-msg.sample
| `-- update.sample
|-- info
| `-- exclude
|-- objects
| |-- info
| `-- pack
|-- refs
| |-- heads
| `-- tags

8 directories, 16 files

这次,我们将重点放在refs子目录。它代表引用,这是git保留分支指针的地方。
由于我们尚未提交任何更改,因此refs目录为空,因此我们将创建并提交一些文件。

1
2
3
4
5
6
7
8
9
echo "Hello World" > helloEarth.txt
git add .
git commit -m "Hello World Commit"
echo "Hello Mars" > helloMars.txt
git add .
git commit -m "Hello Mars Commit"
echo "Hello Saturn" > helloSaturn.txt
git add .
git commit -m "Hello Saturn Commit"

如果我们现在执行git branch,我们将看到此输出:

1
* master

这意味着我们现在位于master分支上(在第一次提交时git自动创建)。

如果我们再来看看.git/refs目录

1
2
3
4
5
6
.
|-- heads
| `-- master
`-- tags

2 directories, 1 files

我们看到refs / heads子目录中有一个文件,就像我们的分支一样,它被称为master。这是一个文本文件,因此我们可以使用cat进行查看:

1
cat .git/refs/heads/master

我们看到它包含一个校验和。

1
43cdb7eab6a763cc9ae9233d91cf1d6fa598de67

如果我们这样做:

1
git log

我们看到它是最后一次提交的校验和:

1
2
3
4
5
commit 43cdb7eab6a763cc9ae9233d91cf1d6fa598de67 (HEAD -> master)
Author: hongzhijun <azjhong1997@gmail.com>
Date: Sat May 30 12:09:31 2020 +0800

hello Status commit

所以,我们有了它——git 中的分支只是一个文本文件,包含了该分支上最后一次提交的校验和。换句话说,就是一个指向提交的指针。

Snipaste_2020-05-30_12-27-00.png

如果我们现在创建并签出一个新的功能分支:

1
git checkout -b feature

我再来看下.git/refs目录。

1
tree > t.md

当然,我们可以看到另一个名为”feature”的文件。

1
2
3
4
5
6
7
.
|-- heads
| |-- feature
| `-- master
`-- tags

2 directories, 2 files

如果我们看一下它的校验和(指针)。

1
cat .git/refs/heads/feature

我们看到它与主文件(分支)中的相同。

1
43cdb7eab6a763cc9ae9233d91cf1d6fa598de67

因为我们没有对该分支进行任何新的提交。
Snipaste_2020-05-30_14-12-36.png

所以这就是在git中创建一个新的分支是多么的快速和便捷。Git只需要创建一个文本文件,然后用当前提交的校验和来填充它。

但是现在我们有两个分支,有一个问题。git如何知道我们当前检查的这两个分支中的哪一个?嗯,还有一个特殊的指针(名字可能听起来很熟悉),叫做 HEAD . 它很特别,因为它(通常)不是指向一个提交对象,而是指向一个 ref (分支),而 git 用它来跟踪当前被检查的分支。

如果我们看HEAD:

1
cat .git/HEAD

我们可以看到当前指向feature引用文件(branch)。

1
ref: refs/heads/feature

Snipaste_2020-05-30_14-18-44.png

如果我们这样做:

1
git checkout master

并查看一下HEAD:

1
cat .git/HEAD

我们会看到:

1
refs: resfs/heads/master

它会指向主分支:
Snipaste_2020-05-30_14-23-20.png

这就是git的分支模型。要了解许多对该图进行操作的git操作(合并,变基,检出,还原…),这非常简单但很重要。

^_^