前言
Git是一个版本控制系统,它在咱们的作业中发挥着重要的效果。
git merge 和 git rebase 两个指令是咱们整合 Git 作业,兼并不同分支内容的两大利器,可是它们两者的作业方法和对历史记录的影响却是截然不同的。
Git 原理
在了解 git merge 和 git rebase 之前,咱们先来简略的介绍下 Git 的内部原理,这将有助于对后续内容的了解。
Git 的存储
Git 实际上是一个内容寻址文件系统,它的中心部分是一个简略的键值对数据库(key-value data store)。Git 在实现内容存储时,都会返回一个根据 SHA-1 算法计算出来的 key,咱们能够经过这个 key 在任意时刻将内容读取出来。
SHA-1 hash 是一个由十六进制字符(0-9和a – f)组成的40个字符字符串,如:b0850823c8e5797e01e071eb93b6194e4543a4b4
Git 将内容存储在 Git 目标中:
-
blob目标:文件由blob目标存储,存储文件的全部内容(即文件快照) -
tree目标: 文件的目录结构由tree目标存储,目标中的每条记录含有一个指向blob目标或者子树目标的 SHA-1 指针,以及相应的形式、类型、文件名信息。 -
commit目标:Git的每一个提交由commit目标存储,目标中的内容包括提交者、提交的时间,以及指向顶层tree目标的指针(存储整个项目目录结构的tree目标,即项目快照),如果存在前一个commit, 还会包含指向前一个commit的指针。
classDiagram
TreeA <|-- Commit
TreeB <|-- TreeA
FileA <|-- TreeA
FileB <|-- TreeA
Commit : author
Commit : time
Commit: tree TreeA
Commit: commit Parent
class TreeA{
tree TreeB
blob FileA
blob FileB
}
class FileA{
file content
}
class FileB{
file content
}
class TreeB{
blob FileC
}
咱们能够在项目中的 .git 文件夹中看到每一个由 Git 创立的 目标。
.git 存储项目一切的 metadata 和 object database,履行 git clone 克隆一个项目时,其中最重要的便是将 .git 文件夹拷贝。
Git 将每一个目标对应的 SHA-1 值的前两个字符作为目录名,余下的 38 个字符则用作文件名,所以每一个Git 目标对应的存储方位相似以下结构:.git/objects/4a/c34d0644fc69cab26a829f0da5497eda562940
commit 与 branch
每一个 commit 目标对应着咱们在 Git 的每一次提交(即版本),每一个 commit 目标存储时返回的 SHA-1 值便是咱们常说的 commit id。
每一个 commit 目标(除了第一个),都会包含一个指向上一个 commit 的指针,所以 Git 中的 commit 能够笼统成以下形式:
commit 实际上是一个目标,那么 branch 是什么呢?
在 Git 中除了目标外,还有一种存储结构,称为引证(references),该引证类型的文件存储的是某一个commit 目标的 SHA-1 值,这样的文件一般有一些简略的姓名,如 master,dev 等。
Git 的 branch 实际上,便是引证,是一个指向 commit 目标的可变指针。
一般 Git 项目中会有一个默认的分支 master,当咱们从 master 切出一个名为 dev 的分支时,实际上便是对当时的 commit 目标创立了一个新的引证,而且它的内容会跟着新的 commit 的创立而改动。
那么 Git 在众多分支中,怎么知道咱们作业在哪个分支呢?
这归功于一个名为 HEAD 的特别的引证,这个引证特别在它的内容不是指向 commit 目标的 SHA-1 值,而是其他引证文件。它当时的内容是哪个引证文件,便意味着 Git 当时作业在该引证文件所代表的 branch 上,而且它的内容会跟着分支的切换而改动。
merge
咱们一般用 git merge 来兼并两个不同 branch 的内容,经过前面的内容咱们了解到 branch 实际上是指向 commit 的指针,兼并不同的 branch 便是兼并不同的 commit。
merge 能够分红两种状况:
- 快速兼并
- 三方兼并
满足快速兼并的条件是其中一个 commit 是另一个 commit 的先人。
当咱们在 dev 上持续提交多个 commit 后,履行git merge dev 将 master 和 dev 分支兼并。此刻 Git 会寻觅 master 和 dev 一起的先人 commit,于是发现 master 指向的 commit 62940 便是 commit 142e3 的先人,这时两者就能直接进行一个快速兼并,而且不会存在任何抵触。
当 merge 完结后,master 指针会指向最新的 commit 142e3。
如果咱们在 dev 分支提交 commit 的一起,也在 master 分支提交了 commit。当两者进行 merge 时,由于 commit 33888 和 commit 142e3 存在一起的先人为 commit 62940,无法进行快速兼并,此刻便需求选用三方兼并的方法进行处理。
三方兼并是指将commit 33888 和 commit 142e3 以及它们的一起先人 commit 62940,这三个 commit 的内容进行兼并,一起会主动生成一个全新的 commit 目标记录兼并之后的成果。
这个新的 commit 6d5d1 会一起存在两个父 commit。既包含了指向 commit 33888 的指针,又包含了指向 commit 142e3 的指针。 当 merge 完结后, master 指针会指向新的 commit 6d5d1。
可是,三方兼并并非总是一帆风顺的。
三方兼并需求将三个 commit 的内容兼并,如果存在两个 commit 对同一文件同一部分做了不同的修正,此刻兼并就会呈现抵触,由于Git不知道怎么处理这种问题,所以需求咱们手动的处理抵触。当呈现抵触时,Git 会完结兼并可是不会主动创立新的 commit,需求咱们手动处理抵触后,自己经过 git add 和 git commit 创立新的 commit。
rebase
rebase 在任何状况下,兼并不同分支的 commit ,都是选用相同的处理方法。
下面以一起在 master 和 dev 创立 commit 为例,在 dev 分支上履行 git rebase master 兼并master 分支的 commit。
首先 Git 会寻觅到两个分支的一起先人 commit 62940 ,然后将 dev 分支根据该 commit 之后的每一次提交对应的修正都提取并保存为临时文件。之后就将 dev 的指针指向与 master 相同的 commit 33888。

然后再将临时文件的内容从头运用到该 dev 分支上,顺次创立新的 commit,由于 dev 现已指向commit 33888,所以新的 commit 就顺次创立在 commit 33888 之后。如果在这个进程中存在抵触,则 rebase 会间断,需求等候处理抵触后,让 rebase 持续进行。
留意:此刻虽然从头运用到 dev 上的修正是一样的,可是由于顺次从头创立了 commit,因而 commit 对应的 SHA-1 值是不同的,即 commit id 不同了。
由于 rebase 的进程存在从头运用修正,从头创立commit 的进程,因而运用rebase时可能会遇到需求不断地从头处理抵触的问题。
总结
了解了这么多有关 merge 和 rebase 的内容,有些同学可能会想:在 Git 兼并分支时,运用哪一种方法更好呢?其实这个问题没有标准答案,只能是在不同的情境下挑选适宜的方法。
咱们不难发现,运用 rebase 的方法能够使咱们保持整齐的 commit 记录,这是一种除掉枝叶维护骨干的作业方法。而 merge 的方法,会留下支干,一起还会增加一些由 merge 创立的 commit,它并不整齐,却能够完好的记录下一切的作业痕迹。








