深入理解Git

原文地址:http://shellhue.github.io/2017/03/22/gitcommit/

深入理解 git 中 commit 和 branch ,有助于在日常开发中,更好的运用 git 。

commit的本质

初始化目录

为便于讲解,先创建一个如下目录结构的 BeiLiao (贝聊是我所在的公司)目录:

在命令行中输出文件的目录结构如下:

~ qingmo$: tree
.
└── BeiLiao
    ├── Component
    │   └── App.js
    ├── index.html
    └── index.js

如果以竖向文件层级树的样式看是这样的(深色表示文件目录,浅色表示文件),

其中 App.js 、 index.html 和 index.js 都填充了相应的内容。

初始化git并提交第一个commit

先初始化 git ,然后添加所有文件到 git 的暂存区,成功后提交一个 commit ,最后 log 一下版本历史记录:

~ qingmo$: cd ~/../BeiLiao(切换到BeiLiao这个文件目录)
~ qingmo$: git init
~ qingmo$: git add .
~ qingmo$: git commit -m “init project”
~ qingmo$: git log

commit 5bb33508dd4f00dd4d63c6e28546e044ebb51ac0
Author: qingmo <qingmo@163.com>
Date:   Sun Mar 26 15:18:01 2017 +0800

    init project

完成上述我们开发中的日常操作后, git 便开始成功追踪 BeiLiao 整个文件夹,很炫很酷,但问题是 git 到底是如何做到的,而我们日常开发熟悉不能再熟悉的 commit 的本质又是什么?

其实当我们运行 git commit -m “init project” 命令时, git 主要进行了三个操作。

  • 为每一个文件生成一个快照

    每一个文件其实是真的数据,所以 git 会把整个文件内容转成二进制,然后经过压缩直接存在键值对数据库中,对应的键值就是文件中的内容再附加一些头信息的40位校验和 sha-1 。既然是真数据,所以文件快照的类型为 blob 类型( binary large object )即大型二进制对象类型,

  • 为每一个文件夹生成一个快照

    文件夹并不是直接的文字数据,其主要记录的是文件夹的结构和每个文件或者文件夹所对应的快照键值,所以文件夹的快照内容主要是其包含的所有文件和文件夹的键值信息总和,附加一些头信息,如文件名,文件夹名。对应快照键值为快照内容的40位校验和 sha-1 。既然不是直接数据,数据类型与文件快照必然不同,文件夹快照对应的类型为 tree 类型。

  • 生成一个项目快照

    也即生成一个 commit ,项目快照的内容主要包含四部分信息,根项目目录的快照、提交人信息、项目快照说明(即 commit 信息)和父项目快照。其中项目文件快照,只要根目录即 BeiLiao 的目录快照即可。项目快照 commit 的键值为项目快照内容的40位校验和 sha-1 。项目快照类型为 commit 类型。

git commit -m “init project” 后,生成的 object 引用图表如下图(浅灰色的表示文件快照,为 blob 类型,深蓝色的表示文件夹快照,为 tree 类型,绿色的表示项目快照,为 commit类型):

git中生成的所有 object 都存在 .git/objects/ 文件夹内,每一个 object 保存时,取其40位校验和 sha-1 的前两位生成文件夹,后38位作为文件名,存储对应的数据。 git commit -m “init project” 后,git的 .git/objects/ 文件夹内容如下图:

下图把 .git/objects/ 中的每一个 object 对应的内容文件一一标注了出来,可以很清晰的看到每一个数据 object 到底是什么。

添加文件并提交第二个commit

在 Component 文件夹添加一个新文件 Footer.js ,形成新的文件目录结构:

~ qingmo$: tree
.
└── BeiLiao
    ├── Component
    │   ├── App.js
    │   └── Footer.js
    ├── index.html
    └── index.js

然后添加一个新的提交

~ qingmo$: git add .
~ qingmo$: git commit -m “add footer”

此时生成的 object 引用图表如下图:

可以看出,新添加的 Footer.js 生成了一个快照 efdfb8 ,其所在的文件夹 Component 也因新添加的文件而产生了内容的变化,因此也生成了新的快照 017bde ,同样的根文件夹 BeiLiao 也生成了新的快照 3d7c8c 。最终的形成新的commit d34903 指向最新的根文件 BeiLiao 的快照 3d7c8c 。

需要注意的是,只有变化的文件或文件夹才会形成新的快照,没有变化的文件不会形成新的快照。

一个branch是什么

branch 的信息记录在 .git/refs/heads/ 目录下,

用文本编辑器打开 master 文件,内容是:

d34903acaba24d2eccc8f5227ca61e6b0f7bd783

这是最新 commit 的键值,所以 branch 仅仅是指向一个 commit 的指针而已,指向一个 commit ,而一个 commit 同时指向其父 commit ,如此循环最终形成了一个 branch 。

HEAD指针

那 git 是怎么知道项目在 master 分支上呢? HEAD 指针。 git 有一个独立的 HEAD 指针,记录项目现在所在的位置,比如现在我们在 master 分支上,查看 .git/HEAD 文件,内容为:

ref: refs/heads/master

此时 HEAD 指针指向 master ,所以项目在 master 分支上。

当我们创建一个新的分支 test 时,git会在 .git/refs/heads/ 目录下生成一个文件 test ,并将其指向当前 HEAD 所指向的分支 master 所指向的提交 d34903 (有点绕),并把 HEAD 指向新的分支 test 。

当我们在新的分支生成新的 commit 时, git 会将 HEAD 所指向的分支 test 所指向的 commit 作为新 commit 的父 commit ,然后将 HEAD 所指向的分支 test 移动指向新的提交。

可以看出 HEAD 指针在整个 git 中的重要性,没有 HEAD ,就不知道当前项目的位置,就不会生成新的提交,也不会创建新的分支(除非用 git 的底层命令)。

总结

git 中每一个文件会生成一个 blob 类型的 object 记录这个文件的状态,每一个文件夹都会生成一个 tree 类型的 object 就录这个文件夹的状态,一个项目会生成一个指向根目录 tree object 的 commit 类型 object 作为项目的快照。

branch 其实只是指向一个 commit 的指针而已, HEAD 记录了当先项目的位置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值