相信大家对于 git 版本控制系统都不陌生,大部分同学在工作中都会使用到 git,他帮助我们管理我们的代码,让我们能够随心所欲的提交代码,进行不同的实验而不用担心将项目毁掉,同学们可以在工作中熟练的使用 git 进行代码提交,但是部分同学对于 rebase,merge 等等针对分支,合并的操作不十分自信,并且对目前腾讯视频客户端采用的主干开发模式背后的原理并不熟悉,在本文章中,我想通过介绍 git 的存储机制,来帮助大家更好的理解分支以及分支操作。
希望有相关困惑的同学能够通过本文章,从原理上对 git 的分支操作有一定的理解,在日后的工作中使用相对应的 git 命令时能更加得心应手,游刃有余,本文主要分为以下几个部分:
git 是一个分布式版本控制系统。Git 记录的是整个项目的快照,而非不同版本之间的差异,这一点上,git 和许多其他的版本控制系统不太一样,当 git 存储更新或者保存文件状态时,它基本上就会对当时项目中由 git 管理的全部文件创建一个快照并保存这个快照的索引。 为了效率,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个快照流。
Git 的版本控制是分布式的,这意味着当多人工作在同一个项目或者同一个分支上时,每个人的系统之都包含了这个项目或者分支的全部信息,即使此时中央 git 服务器出现不可修复的故障,仍可以基于任何一个已经检出该项目的电脑来使用本地的 git 记录来进行覆盖还原。同时 git 对于所有的数据在存储前都会计算校验合(使用一种叫做 SHA-1 的哈希计算方式),用来校验和引用,这意味着 git 可以察觉到所管理项目中任何一个文件内容或者目录内容的变动。
Git 一般只会添加数据,我们所做的任何操作(即使是回退代码),都在向 git 数据库中添加数据,这意味着 git 几乎不会执行任何可能导致文件不可恢复的操作。
Git 引入了分支的概念,并且允许我们频繁的添加,删除,合并以及修改分支,这极大地便利了我们在不同开发工作之间切换,同时也让项目管理者可以更好的管理有很多开发者参与以及提交贡献的项目。 Git 的这些特性迅速让他成为了主流,git 让我们可以愉快的进行多人协作开发,也允许开发人员针对项目的规模和特点,选择各种各样的开发模式。(对 git 开发模式感兴趣的同学可以阅读:https://git-scm.com/book/en/v2/Distributed-Git-Distributed-Workflows)
要想搞明白分支的概念,以及各种分支操作到底做了什么,我们首先需要对 git 存储代码版本和信息的原理有个简单的认识。
Git 的本质是一个键值存储系统,我们可以向 git 仓库中插入任意类型的内容,他会对内容进行存储,同时基于内容计算并返回一个唯一的值(前文说到的 sha-1 哈希值)作为键,通过该键我们可以再次访问到该内容。 Git 就是基于这个存储系统存储我们的项目提交的,为了记录我们项目的提交快照,提交信息等数据,git 定义并存储了不同类型的存储对象,主要包括:
数据对象(blob):包含存储文件的内容信息的对象,一个数据对象通常包含一个文件的内容信息,但是不包含文件名。
树对象(tree):存储文件名,同时可以将多个文件组织到一起,(有点类似 unix 文件系统中的目录项。一个树对象可以包含一条或多条提交记录)通常,我们在 git commit 时会将暂存区的所有状态写为一个树对象,一个树对象包含多个树对象记录(tree entry),每条记录都代表了当前目录下的一个条目,他们有可能是一个文件,也有可能是一个子目录(指向另一个树对象的指针)他们帮助我们管理项目的目录和层级关系。
提交对象(commit):有了树对象和数据对象,我们记录下了每次提交的内容,为了记录提交之间的顺序关系,以及提交的作者信息,git 引入了提交对象的概念,一个提交对象通常会保存一个树对象,作为当前的项目快照。可能存在的父提交对象,作者信息,时间戳,以及提交的注释。
靠这三种对象,git 基本上保存了我们项目需要的所有信息。假设我们创建了一个简易的项目,我们首先在项目中创建了一个 test.txt 文件并提交给 git 来管理,之后我们修改了 test.txt 文件的内容,并创建了另一个叫做 new.txt 的文件,最后我们又新建了一个叫 bak 的目录,并将 test.txt 文件的第一个版本放在了该目录中。在以上过程中,我们创建了三次提交,git 的提交历史大概长这样:
可以看到,数据对象对象保存了文件内容信息,树对象存储了文件名和目录相关的信息,而提交对象为我们存储了提交之间的顺序,以及和每个提交相关的信息。
现在我们回到我们的主题:分支以及分支操作上。这涉及到了另外一种对象:引用对象