三分钟上手! 一文看懂 Git 的底层工作原理

1. 三分钟上手! 一文看懂 Git 的底层工作原理

1.1. Git 目录结构

Git 的本质是一个文件系统(很重要, 记住这句话, 理解这句话), 工作目录中的所有文件的历史版本以及提交记录(commit)都是以文件对象的方式保存在 .git 目录中的。

我们先来创建一个名为 git-demo 空目录, 并采用 git init 命令初始化 Git 仓库。该命令会在工作目录下生成一个 .git 目录, 该目录将用于保存工作区中所有的文件历史的历史版本, commit, branch, tag 等所有信息。

$ mkdir git-demo
$ cd git-demo
$ git init

其目录结构如下:

1.webp

待会我们重点关注下这几个目录:

  • HEAD: 工作目录当前状态对应的 commit, 一般来说是当前 branch 的 head, HEAD 也可以通过 git checkout 命令被直接设置到一个特定的 commit 上, 这种情况被称之为 detached HEAD
  • objects: 这里是真正保存 Git 对象的目录, 包括三类对象 commit, tree 和 blob(具体这三类对象是什么, 慢慢往下看就知道了)
  • refs: 用来保存 branch 和 tag 对应的 commit

1.2. Git 三大对象

目前 Objects 目录中还没有任何内容, 我们创建一个文件并提交:

$ git:(master) echo "my project" > README
$ git:(master) mkdir src
$ git:(master) echo "hello world" > src/file1.txt

添加并提交:

$ git:(master) git add .
$ git:(master) git commit -m "init commit"
2.webp

从打印输出可以看到, 上面的命令创建了一个 commit 对象, 该 commit 包含两个文件。查看 .git/objects 目录, 可以看到该目录下增加了 5 个子目录 06, 3b, 82, c5, ca, 每个子目录下有一个以一长串字母数字命令的文件:

3.webp

这一大串是什么?

Git Object 目录中存储了三种对象: Commit, Tree 和 Blob, Git 会为对象生成一个文件, 并根据文件信息生成一个 SHA-1 哈希值作为文件内容的校验和, 创建以该校验和前两个字符为名称的子目录, 并以 (校验和) 剩下 38 个字符为文件命名 , 将该文件保存至子目录下。

可以通过 git cat-file -t 哈希值 命令查看对象类型, 通过 git cat-file -p 哈希值 命令查看对象中的内容, 哈希值就是目录名+文件名, 在没有歧义的情况下, 命令可以不用输入整个哈希值, 输入前几位即可。

我们挨个看下:

065bca(blob):

4.webp

3b18e(blob):

5.webp

824244(tree):

6.webp

c5bc98(commit):

7.webp

ca96(tree):

8.webp

认真看图, 大家看完也就差不多清楚了 commit、blob、tree 这几大对象是什么东西了

从 commit 对象(c5bc98)入手, commit 对象中保存了 commit 的作者, commit 的描述信息, 签名信息以及该 commit 中包含哪些 tree 对象和 blob 对象。从上图可知包含了 tree 对象(ca96)。

可以把 tree 对象看成这次提交相关的所有文件的根目录, 可以看到 ca96 这个 tree 对象中包含了一个 blob 对象(065bca), 即 README 文件, 以及一个 tree 对象(824244), 即 src 目录。而 blob 对象存储的就是真正的内容。

这几个对象的对应关系如下图所示:

9.webp

1.3. Git Brach 和 Tag

现在来看下 HEAD 中的内容, 前面说过, HEAD 中存储的是工作目录当前状态对应的 commit:

$ git:(master) cat .git/HEAD
ref: refs/heads/master
$ git:(master) cat .git/refs/heads/master
c5bc98b8990bedd7444da537320559e601eba87b

c5bc98 正是我们最近的这次 commit!

master 是一个分支名, 所以分支(branch)的本质是一个指向 commit 的指针

我们切一个新分支 feat/work:

10.webp

查看下 refs/heads/master 和 refs/heads/feat/work 中的 commit 值:

11.webp

从其内容可以看到, feat/work 这个 branch 并没有创建任何新的版本文件, 和 master 一样指向了 c5bc98 这个 commit。

从上面的实验可以看出, 一个 branch 其实只是一个 commit 对象的应用, Git 并不会为每个 branch 存储一份拷贝, 因此在 git 中创建 branch 几乎没有任何代价。

接下来我们在 feat/work 这个 branch 上进行一些修改, 然后提交:

$ git:(feat/work) echo "new line" >> src/file1.txt
$ git:(feat/work) echo "do nothing" >> License
$ git:(feat/work) git add .
$ git:(feat/work) git commit -m "some change"
12.webp

查看当前的 HEAD:

13.webp

可以看到 HEAD 指向了 feat/work 这个 branch, 而 feat/work branch 则指向了 8a442 这个 commit, master branch 指向的 commit 未变化, 仍然是 c5bc98。

查看 8a442 这个 commit 对象的内容:

14.webp

可以看到 commit 有一个 parent 字段, 指向了前一个 commit c5bc98。还包含了一个 tree 对象(2a9dd):

15.webp

可以观察到, 由于 README 没有变化, 还是指向的 065bca 这个 blob 对象。License 是一个新建的 blob 对象, src 和 file1.txt 则指向了新版本的对象。

增加了这次 commit 后, Git 中各个对象的关系如下图所示:

16.webp

Tag 和 branch 类似, 也是指向某个 commit 的指针。不同的是 tag 创建后其指向的 commit 不能变化, 而 branch 创建后, 其指针会在提交新的 commit 后向前移动。

猜你喜欢

转载自blog.csdn.net/wan212000/article/details/132413933