侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 297 篇文章
  • 累计创建 134 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

Git合并分支命令merge和rebase的使用方式和区别

孔子说JAVA
2022-09-02 / 0 评论 / 0 点赞 / 106 阅读 / 6,059 字 / 正在检测是否收录...

分支是便于多人在同一项目中的协作开发方式。比如每个人开发不同的功能,在各自的分支开发过程中互不影响,完成后都提交到某一个分支(如develop),极大的提高了开发的效率。git rebase 与 git merge 提供了合并分支的方式,都是将一个分支的提交合并到另一分支上。从最终效果看这两种方式没有任何区别,都是将不同分支的代码融合在一起,只是生成的代码树稍微有些不同。

1、分支和冲突

初始化git时会有一个master主分支生成,在实际开发过程中,一般会创建其他分支进行开发,其他分支开发不会影响master主分支的开发。Git分支优点如下:

  • 可以同时并行开发多个功能,提高开发效率;
  • 多个分支同时开发时,一个分支开发失败,不会影响其他分支的开发,将失败分支删除重新开始即可。

当多分支协作时,最终需要合并我们当前的开发分支到指定分支(汇聚需要的开发结果),这时候有可能会产生冲突。冲突的产生是因为在合并的时候,不同分支修改了相同的位置。所以在合并的时候git不知道哪个到底是你想保留的,所以就提出疑问(冲突提醒)让你自己手动选择想要保留的内容,从而解决冲突。

相关操作命令:

git branch -v :查看所有分支;
git checkout 指定分支名 :切换到指定分支;
git merge 指定分支名 :把指定分支名合并到当前分支上。
git rebase 指定分支名 :把当前分支合并到指定分支上。
  • 分支本质:一个commit链,一条工作记录线
  • 分支名:分支名指向当前的提交(commit)
  • HEAD:head是一个指针,指向当前分支(HEAD->分支名)

2、合并分支操作

git rebase 与 git merge 是两种合并分支的方式,假设有2个分支,分别为 master 分支和 bugfix 分支,分别使用 git rebase 和 git merge 进行操作。合并前分支结构如下:

image

2.1 git rebase

rebase 命令是一个经常听到,但是大多数人掌握又不太好的一个命令。rebase 合并往往又被称为变基,意即改变分支的根基,他的作用和 merge 很相似,用于把一个分支的修改合并到当前分支上。它是将把所有的提交压缩成一个 patch,然后把 patch 添加到目标分支里。rebase 与 merge 不同的是,rebase 通过为原始分支中的每个提交创建全新的 commits 来重写项目历史记录。下面以 master 分支为基,对 bugfix 分支进行变基(基于 master 这个基点进行 rebase 的操作):

# 切换到 bugfix 分支
git checout bugfix
# 基于 bugfix 分支将 master 合并进来
git rebase master

使用 rebase 操作后分支结构如下图:

image-1661995916415

rebase 不会保留commit记录,直接将分支中的内容排到 master 的记录之后。

rebase可以给你提供一套清晰的代码历史,可以看到我们的提交记录非常清晰,没有分叉,上面演示的是比较顺利的情况,但是大部分情况下,rebase 的过程中会产生冲突的,此时,就需要手动解决冲突,然后使用 git addgit rebase --continue 的方式来处理冲突,完成 rebase,如果不想要某次 rebase 的结果,那么需要使用 git rebase --skip 来跳过这次 rebase。

rebase命令会始终把你最新的修改放到最前头。比如你对主branch进行rebase以后, 你的所有修改就会在主branch当前所有的修改之前。你会更有信心保证你的代码运行畅通无阻。通过你自己的测试以后, 你就可以放心的把代码合并到主的branch里面了。

注意点:

  • 一般来说,执行rebase的分支都是自己的本地分支,没有推送到远程版本库。
  • 使用rebase一定要遵守rebase黄金法则,共享的 public 分支不能 rebase,通俗的说,当一个分支是一个人开发处理的,才可以rebase,假如一个分支被多个人协同共享开发,这时候使用 rebase 就乱套了,不仅处理起来复杂,也不方便后期的问题排查;

2.2 git merge

git merge 命令将在子分支的所有提交记录成一次commit,保留在记录中(下图的E即为该记录)。不同于 git rebase 的是,git merge 在不是 fast-forward(快速合并)的情况下,会产生一条额外的合并记录,类似 Merge branch ‘xxx’ into ‘xxx’ 的一条提交信息。下面将 bugfix 分支合入到 master 分支:

git checkout master
git merge bugfix

// 或者
git merge bugfix master

使用merge操作后分支结构如下图:

image-1661995288573

git merge 的优势是它保留了分支的结构与历史提交目录,但同时这也导致了提交历史会被大量的 merge 污染。另外,在解决冲突的时候,用 merge 只需要解决一次冲突即可,简单粗暴,而用 rebase 的时候 ,需要一次又一次的解决冲突。

Merge命令会保留所有commit的历史时间。每个人对代码的提交是各式各样的。尽管这些时间对于程序本身并没有任何意义。但是merge的命令初衷就是为了保留这些时间不被修改。这样也就形成了以merge时间为基准的网状历史结构。每个分支上都会继续保留各自的代码记录, 主分支上只保留merge的历史记录。子分支随时都有可能被删除。子分子删除以后,你能够看到的记录也就是,merge某branch到某branch上了。这个历史记录描述基本上是没有意义的。

fast forward

# Fast-Forward模式
git merge dev

# 使用no-ff可以强制禁用Fast Forward模式
git merge -ff dev

# 使用no-ff可以强制禁用Fast Forward模式
git merge -ff -m "describe message about this commit" dev

在Fast-Forward模式下,如果合并时没有冲突(conflict)时,会直接简单的移动指针,即快进(Fast-Forward)。在该模式下的快速合并不会显示在合并图中。fast forward本质就是分支指针的移动。注意:跳过的中间commit,仍然会保存。

  1. 两个分支 fast forward 归于一点commit
  2. 没有分支信息(丢失分支信息)

git在 merge 时,默认使用fast forward,也可以禁止使用,命令为:git merge --no-ff。no-ff模式下会强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。

  1. 两个分支no fast forward ,不会归于一点commit (主动合并的分支会前进一步)
  2. 分支信息完整(不丢失分支信息)

合并分支时,加上–no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。

2.3 交互式rebase

git rebase -i 命令比 git rebase 更为强大,它将会在 commits 移动到新分支时更改这些 commits ,通常,这用于在合并 feature 分支到 master 之前清理其杂乱的历史记录。

  • -i 参数表示交互(interactive)

例如在开发中,经常会遇到在一个分支上产生了很多的无效的提交,这种情况下使用 rebase 的交互式模式可以把已经发生的多次提交压缩成一次提交,得到了一个干净的提交历史,例如某个分支的提交历史情况如下:

image-1661999199127

交互式rebase命令如下:

# 检出feature分支
git checkout feature

# 进入交互式模式
git rebase -i <base-commit>

参数base-commit就是指明操作的基点提交对象,基于这个基点进行 rebase 的操作,对于上述提交历史的例子,我们要把最后的一个提交对象(ac18084)之前的提交压缩成一次提交,我们需要执行的命令格式是:

git rebase -i ac18084

上述命令会进入一个 vim 的交互式页面,列出即将移动的所有提交(每个 commit 前面有一个操作命令,默认是 pick),它清晰地展示了分支在 rebase 后的样子。通过重新调整,提交历史可以变成任何你想要的样子。如更正1,更正2,更正3,更正4是一些无意义的提交,那么消除这种无意义的提交使你的功能历史更容易理解。这是 git merge 根本无法做到的事情。

  • 我们可以选择不同的 commit,并修改 commit 前面的命令,来对该 commit 执行不同的变更操作。

image-1661999482206

想要合并这一堆更改,squash 和 fixup 都可以用来合并 commit。如我们使用 squash 策略进行合并,只需要把要合并的 commit 前面的动词,改成 squash(或者 s)即可。即把当前的 commit 和它的上一个 commit 内容进行合并,大概可以表示为下面这样:

pick  ... ...
s     ... ... 
s     ... ... 
s     ... ... 

修改文件后,按 ESC 键后输入 :wq 保存退出,此时又会弹出一个编辑页面,这个页面是用来编辑提交的信息,修改为 feat: 更正。最后保存一下,接着使用 git branch 查看提交的 commit 信息,rebase 后的提交记录如下图所示,是不是清爽了很多?rebase 操作可以让我们的提交历史变得更加清晰。

  • rebase 后,第 2、3、4 行的 commit 都会合并到第 1 行的 commit。

image-1661999688467

至于 commits 条目前的 pick、fixup、squash 等命令,在 git 目录执行 git rebase -i 即可查看到,大家按需重排或合并提交即可,注释说明非常清晰,git rebase 支持的变更操作如下:

image-1662012104754

特别注意,只能在自己使用的 feature 分支上进行 rebase 操作,不允许在集成分支上进行 rebase,因为这种操作会修改集成分支的历史记录。

2.4 merge与rebase的区别

rebase把当前的commit放到公共分支的最后面,merge把当前的commit和公共分支合并在一起;用merge命令解决完冲突后会产生一个commit,而用rebase命令解决完冲突后不会产生额外的commit。

git merge:

git merge 的优势是它保留了分支的结构与历史提交目录,但同时这也导致了提交历史会被大量的 merge 污染。

  • 记录下合并动作,很多时候这种合并动作是垃圾信息。
  • 不会修改原 commit ID。
  • 冲突只解决一次。
  • 分支看着不大整洁,但是能看出合并的先后顺序。
  • 记录了真实的 commit 情况,包括每个分支的详情。

git rebase:

git rebase 的优势是可以获得更清晰的项目历史。首先,它消除了 git merge 所需的不必要的合并提交;其次 rebase 会产生完美线性的项目历史记录,你可以在主分支上没有任何分叉的情况下一直追寻到项目的初始提交。但是, rebase 会丢失合并提交的上下文,使我们无法看到真实的更改是何时合并到目标分支上的。

  • 改变当前分支 branch out 的位置。
  • 得到更简洁的项目历史提交记录。
  • 每个 commit 都需要解决冲突。
  • 修改所有 commit ID。

3、使用场景

每个程序员在创建自己的代码之前,要首先创建自己的个人分支,然后代码修改开始。假如你有6个程序员一起工作,你就会有6个程序员的分支,如果你使用 merge, 那么你的代码历史树就会有六个branch跟这个主的branch交织在一起。所以 rebase 和 merge 的使用场景一定要区分清楚。

3.1 场景一

假如你一直在某个子分支开发功能,在还没开发完的时候,你的同事告诉你,你引用的其他模块的内容有更新,你需要拉取一下最新的 master 代码更新一下该引用模块。

该情况下推荐使用rebase。该模块的内容更新和你修改的功能无关,合并代码也不会影响你的功能,无需保留该记录。如果使用 merge,在开发周期长的情况下,会创造很多无用的commit记录。

3.2 场景二

你和同事两个人在开发同一个模块,你同事开发的部分功能已经合并到了master,通知你更新一下最新的模块代码。

该情况下推荐使用merge。你和同事开发的是同一模块,很可能他的某个改动会导致你的功能出问题,如果出了问题,保留记录能便于后期排查问题。(当然如果从逻辑上可以判断不会有影响,使用rebase也是可以的)。

3.3 场景三

现在有个子分支模块已经开发完,master分支需要将这个分支的内容合并进来。

该情况下推荐使用merge。使用merge可以留有提交记录,如果该模块出了问题,方便后面排查问题。

4、冲突解决

合并分支时如果冲突,需要解决冲突。在不同的分支链中,合并有可能有冲突,因为同一个文件有可能内容不一样。而由于在不同的分支链中,故也没有落后/靠前的说法,故冲突会发生,若在同一个分支链,那肯定是不会有冲突的,落后方直接合并到靠前方即可,文件内容最终跟靠前方一样。

解决冲突:

  1. vi 修改文件内容,自己决定合并后的文件内容应该是怎样的
  2. git add a.txt (假设冲突的文件是a.txt,这次操作是告诉git,a.txt文件的冲突解决了)
  3. git commit -m “解决冲突”

不同合并方式解决冲突的区别:

merge 操作遇到冲突的时候,当前merge不能继续进行下去。手动修改冲突内容后,add 修改,commit 就可以继续往下操作,而rebase 操作的话,会中断rebase,同时会提示去解决冲突。解决冲突(vi 或则其他工具)后, 将修改add后执行 git rebase --continue 继续操作(会要求写入comment),或者git rebase —skip忽略冲突,之后push到远端。任何时候都可以通过命令 git rebase --abort 终止rebase,分支会恢复到rebase开始前的状态。

  • 使用merge命令合并分支,解决完冲突,执行git add .和git commit -m’fix conflict’。这个时候会产生一个commit。
  • (交互式)使用rebase命令合并分支,解决完冲突,执行git add .和git rebase --continue,不会产生额外的commit。这样的好处是‘干净’,分支上不会有无意义的解决分支的commit;坏处,如果合并的分支中存在多个commit,需要重复处理多次冲突。

git pull和git pull --rebase区别:

  • git pull做了两个操作分别是‘获取’和合并。
  • git pull --rebase就是以rebase的方式进行合并分支得到一条干净的分支流,默认为merge。
git pull = git fetch + git merge FETCH_HEAD 
git pull --rebase =  git fetch + git rebase FETCH_HEAD

git rebase --continue; 让 rebase 过程继续执行。
git rebase --abort; 发生代码冲突后,放弃合并,回到操作前的样子。
0

评论区