前言
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
,它并不整齐,却能够完好的记录下一切的作业痕迹。