git场景化操作(四):merge与rebase详解

1,703 阅读7分钟

Git Merge

1.merge命令

$ git(test) > git merge feature001  ## 将feature001这个分支合入test分支

2.merge的几种形式

2.1 fast-forward

如下图,当你从master分支拉取一个分支出来进行特性分支的开发,在你开发完成后此时master分支仍是你拉取时的状态

         A---B---C feature
         /
D---E---F master

这时将feature分支merge到master中,git会默认使用快进的方式,也就是直接将master的HEAD指向C

          A---B---C feature
         /         master
D---E---F 

这种方式当然很简单粗暴,但会带来一个问题,也就是会在master分支中看到feature分支的提交记录(如图中的A/B/C三次提交的提交记录都会展示在master分支中),且回退时也不太好回退。这时就需要使用no-ff模式

实际操作演示:

git checkout -b feature001 ##从test分支中拉取feature分支

## 下面对该分支做两次修改和提交
vi readme.txt 
git commit -am"dev feature" 
vi readme.txt
git commit -am"dev feature"

git checkout test ##切回test分支
git merge feature001 ##合并feature分支入test,此时git会提示你为fast-forward模式

git log --graph

* commit 02bf77d326664db21533f1406614a293e4ddb483 (HEAD -> test, feature001)
| Author: xxx <xxx@xxx.com>
| Date:   Mon Jan 11 00:43:36 2021 +0800
|
|     fix feature001
|
* commit 4478047255d76ccf82add89fe7f2a7434caf7699
| Author: xxx <xxx@xxx.com>
| Date:   Mon Jan 11 00:42:37 2021 +0800
|
|     dev feature001
|
*   commit c18240f00cf8eaa28d7cd75ec64e17dfd3858a6b
.....

## 可以看到test和feature指向同一commit且test分支中也包含了feature分支上的commit记录

2.2 no-fast—forward

还是上面那个例子

         A---B---C feature
         /
D---E---F master

merge时使用git merge --no-ff feature命令的话,则如下如所示,会产生一个全新的提交G,这样在master上看就看不到ABC三次提交的commit记录了,而且回滚秩序向上回滚一个commit即可

          A---B---C feature
         /         \
D---E---F-----------G master

因此在你希望集成分支上不包含特性分支上的commit记录时,可以在merge时使用--no-ff参数

实际操作演示:

git checkout -b feature002 ##从test分支中拉取feature分支

## 下面对该分支做两次修改和提交
vi readme.txt 
git commit -am"dev feature" 
vi readme.txt
git commit -am"dev feature"

git checkout test ##切回test分支
git merge --no-ff feature002 ##合并feature分支入test,此时git会提示你为fast-forward模式

git log --graph

*   commit 0baad230d3f5272881a4568b46c66460db771fef (HEAD -> test)
|\  Merge: 02bf77d 5a70aa9
| | Author: xxx <xxx@xxx.com>
| | Date:   Mon Jan 11 00:54:42 2021 +0800
| |
| |     Merge branch 'feature002' into test
| |
| * commit 5a70aa941faade4571040044bce9234833c3f096 (feature002)
| | Author: xxx <xxx@xxx.com>
| | Date:   Mon Jan 11 00:54:28 2021 +0800
| |
| |     fix feature002
| |
| * commit 70740239cfcb6bdcda4999f9a00767aed35d0ff1
|/  Author: xxx <xxx@xxx.com>
|   Date:   Mon Jan 11 00:54:12 2021 +0800
|
|       dev feature002
|
* commit 02bf77d326664db21533f1406614a293e4ddb483
...

## 可以很明显的看到,这种模式下test分支不在包含有feature分支的commit记录

Git Rebase

rebase命令

git rebase -i commitID
git rebase branch_name

rebase的使用场景

1.整理commit记录

有时我们可能会有如下诉求,当前分支提交记录如下:

commit 22ad2912ff010751ae35f3963f9b5aa03c8c79c2 (HEAD -> feature002)
Author: xxx <xxx@xxx.com>
Date:   Mon Jan 11 01:00:16 2021 +0800

    fix feature002

commit 5a70aa941faade4571040044bce9234833c3f096
Author: xxx <xxx@xxx.com>
Date:   Mon Jan 11 00:54:28 2021 +0800

    fix feature002

commit 70740239cfcb6bdcda4999f9a00767aed35d0ff1
Author: xxx <xxx@xxx.com>
Date:   Mon Jan 11 00:54:12 2021 +0800

    dev feature002

此时我们想将后面两次bug修复的commit合并为一次,则可以如下操作

git rebase -i 70740239cfcb #以指定的commit为基准,通过不同的指令操作后面的commit

##此时会进入交互模式,在交互模式中将最后一次commit前面的指令从pick修改为s,保存退出即可(此时会进入到下一个交互中,是用来编写合并后的commit信息的)

此时再观察git的提交记录如下

commit 055e66d782c5a7e9fcb2131bc2bd12fd017708dc (HEAD -> feature002)
Author: xxx <xxx@xxx.com>
Date:   Mon Jan 11 00:54:28 2021 +0800

    fix feature002

    fix feature002

commit 70740239cfcb6bdcda4999f9a00767aed35d0ff1
Author: xxx <xxx@xxx.com>
Date:   Mon Jan 11 00:54:12 2021 +0800

    dev feature002

可以看到此时commit记录已经合并成功!

2.分支合并

1.我们先从 master 分支切出一个 dev 分支,进行开发

      D---E---F feature
     /
A---B master

2.此时你的同事在你提交之前先提交了另外一个需求

      D---E---F feature
     /
A---B---C master

3.此时当你想要合并你的分支到master时你就有两个选择:

3.1 直接merge入master

      D---E---F feature
     /         \
A---B---C-------G master

3.2 先rebase master后再merge入master

          D---E---F feature
         /          master
A---B---C

可以发现,在rebase之后feature分支的起点变更为了C,也就是这时处于fast-forward可用模式(当然是否使用可以选择),如果追求一个线性的集成分支提交记录,则这种方式可以满足你的需求;

rebase时发生了什么

  • 首先,git 会把 feature 分支里面的每个 commit 取消掉;
  • 其次,把上面的操作临时保存成 patch 文件,存在 .git/rebase 目录下;
  • 然后,把 feature 分支更新到最新的 master 分支;
  • 最后,把上面保存的 patch 文件应用到 feature 分支上

冲突解决

在 rebase 的过程中,也许会出现冲突 conflict。在这种情况,git 会停止 rebase 并会让你去解决冲突。在解决完冲突后,用 git add 命令去更新这些内容。注意,你无需执行 git-commit,只要执行 continue

git rebase --continue

这样 git 会继续应用余下的 patch 补丁文件。

ps.在任何时候,我们都可以用 --abort 参数来终止 rebase 的行动,并且分支会回到 rebase 开始前的状态

高危操作

如果你的分支会被他人使用(拉取到本地并基于你的分支开发),那么在你提交到远程仓库之后,不要对已提交内容做rebase操作并强制推送到远程分支;

如果你这样做了,其他人如果使用了你rebase之前的提交作为基点,那么在你rebase并推送之后,对于他的分支而言,相当于丢失了基点(因为rebase会导致commit id变化),示例如下

M1---C1---C2---C3 origin/common
    	  	    \---C4---C5---C6 common

如果此时common分支rebase了master并强推到远程分支后,

M1---M2---C1'---C2'---C3'origin/common
 \---C1---C2---C3 
    	  	    \---C4---C5---C6 common

此时common分支再推送远端时,其最近的公共节点为M1,因为C1-C6会被再次合入 最终在远程分支上可以看到多条重复的commit记录,造成混乱