前言

Git是一个版本控制系统,它在咱们的作业中发挥着重要的效果。 git mergegit rebase 两个指令是咱们整合 Git 作业,兼并不同分支内容的两大利器,可是它们两者的作业方法和对历史记录的影响却是截然不同的。

Git 原理

在了解 git mergegit rebase 之前,咱们先来简略的介绍下 Git 的内部原理,这将有助于对后续内容的了解。

Git 的存储

Git 实际上是一个内容寻址文件系统,它的中心部分是一个简略的键值对数据库(key-value data store)。Git 在实现内容存储时,都会返回一个根据 SHA-1 算法计算出来的 key,咱们能够经过这个 key 在任意时刻将内容读取出来。

SHA-1 hash 是一个由十六进制字符(0-9和a – f)组成的40个字符字符串,如:b0850823c8e5797e01e071eb93b6194e4543a4b4

Git 将内容存储在 Git 目标中:

  1. blob 目标:文件由 blob 目标存储,存储文件的全部内容(即文件快照)
  2. tree 目标: 文件的目录结构由 tree 目标存储,目标中的每条记录含有一个指向 blob 目标或者子树目标的 SHA-1 指针,以及相应的形式、类型、文件名信息。
  3. 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

一篇文章,深入理解 git 的 merge 和 rebase

每一个 commit 目标(除了第一个),都会包含一个指向上一个 commit 的指针,所以 Git 中的 commit 能够笼统成以下形式:

一篇文章,深入理解 git 的 merge 和 rebase

commit 实际上是一个目标,那么 branch 是什么呢?

Git 中除了目标外,还有一种存储结构,称为引证(references),该引证类型的文件存储的是某一个commit 目标的 SHA-1 值,这样的文件一般有一些简略的姓名,如 masterdev 等。

Gitbranch 实际上,便是引证,是一个指向 commit 目标的可变指针。

一般 Git 项目中会有一个默认的分支 master,当咱们从 master 切出一个名为 dev 的分支时,实际上便是对当时的 commit 目标创立了一个新的引证,而且它的内容会跟着新的 commit 的创立而改动。

那么 Git 在众多分支中,怎么知道咱们作业在哪个分支呢?

这归功于一个名为 HEAD 的特别的引证,这个引证特别在它的内容不是指向 commit 目标的 SHA-1 值,而是其他引证文件。它当时的内容是哪个引证文件,便意味着 Git 当时作业在该引证文件所代表的 branch 上,而且它的内容会跟着分支的切换而改动。

一篇文章,深入理解 git 的 merge 和 rebase

一篇文章,深入理解 git 的 merge 和 rebase

merge

咱们一般用 git merge 来兼并两个不同 branch 的内容,经过前面的内容咱们了解到 branch 实际上是指向 commit 的指针,兼并不同的 branch 便是兼并不同的 commit

merge 能够分红两种状况:

  • 快速兼并
  • 三方兼并

满足快速兼并的条件是其中一个 commit 是另一个 commit 的先人。

当咱们在 dev 上持续提交多个 commit 后,履行git merge devmasterdev 分支兼并。此刻 Git 会寻觅 masterdev 一起的先人 commit,于是发现 master 指向的 commit 62940 便是 commit 142e3 的先人,这时两者就能直接进行一个快速兼并,而且不会存在任何抵触。

一篇文章,深入理解 git 的 merge 和 rebase

merge 完结后,master 指针会指向最新的 commit 142e3

如果咱们在 dev 分支提交 commit 的一起,也在 master 分支提交了 commit。当两者进行 merge 时,由于 commit 33888commit 142e3 存在一起的先人为 commit 62940,无法进行快速兼并,此刻便需求选用三方兼并的方法进行处理。

一篇文章,深入理解 git 的 merge 和 rebase

三方兼并是指将commit 33888commit 142e3 以及它们的一起先人 commit 62940,这三个 commit 的内容进行兼并,一起会主动生成一个全新的 commit 目标记录兼并之后的成果。

一篇文章,深入理解 git 的 merge 和 rebase

这个新的 commit 6d5d1 会一起存在两个父 commit。既包含了指向 commit 33888 的指针,又包含了指向 commit 142e3 的指针。 当 merge 完结后, master 指针会指向新的 commit 6d5d1

可是,三方兼并并非总是一帆风顺的。

三方兼并需求将三个 commit 的内容兼并,如果存在两个 commit 对同一文件同一部分做了不同的修正,此刻兼并就会呈现抵触,由于Git不知道怎么处理这种问题,所以需求咱们手动的处理抵触。当呈现抵触时,Git 会完结兼并可是不会主动创立新的 commit,需求咱们手动处理抵触后,自己经过 git addgit commit 创立新的 commit

rebase

rebase 在任何状况下,兼并不同分支的 commit ,都是选用相同的处理方法。

下面以一起在 masterdev 创立 commit 为例,在 dev 分支上履行 git rebase master 兼并master 分支的 commit

一篇文章,深入理解 git 的 merge 和 rebase

首先 Git 会寻觅到两个分支的一起先人 commit 62940 ,然后将 dev 分支根据该 commit 之后的每一次提交对应的修正都提取并保存为临时文件。之后就将 dev 的指针指向与 master 相同的 commit 33888

一篇文章,深入理解 git 的 merge 和 rebase

然后再将临时文件的内容从头运用到该 dev 分支上,顺次创立新的 commit,由于 dev 现已指向commit 33888,所以新的 commit 就顺次创立在 commit 33888 之后。如果在这个进程中存在抵触,则 rebase 会间断,需求等候处理抵触后,让 rebase 持续进行。

留意:此刻虽然从头运用到 dev 上的修正是一样的,可是由于顺次从头创立了 commit,因而 commit 对应的 SHA-1 值是不同的,即 commit id 不同了。

一篇文章,深入理解 git 的 merge 和 rebase

由于 rebase 的进程存在从头运用修正,从头创立commit 的进程,因而运用rebase时可能会遇到需求不断地从头处理抵触的问题。

总结

了解了这么多有关 mergerebase 的内容,有些同学可能会想:在 Git 兼并分支时,运用哪一种方法更好呢?其实这个问题没有标准答案,只能是在不同的情境下挑选适宜的方法。

咱们不难发现,运用 rebase 的方法能够使咱们保持整齐的 commit 记录,这是一种除掉枝叶维护骨干的作业方法。而 merge 的方法,会留下支干,一起还会增加一些由 merge 创立的 commit,它并不整齐,却能够完好的记录下一切的作业痕迹。