合理地使用 git rebase 代替 git merge 操作
- 电脑基础
- 2020-07-20
- 3332热度
- 0评论
先将本文主要观点写在前面:
- 同一分支上开发,每次提交改动记录较少,建议使用
rebase- 从公共分支合并至个人的特性分支,建议使用
rebase- 不同分支合并,存在较多的改动记录时,建议使用
merge- 从个人的特性分支合并至公共分支,应当使用
merge, 不要使用rebase操作
如果你平时习惯于使用默认的 git merge 操作,那么本文或许会对你有所帮助。
合理使用 git rebase 取代 git merge 操作,可以让变更历史记录更为清晰,易于根据历史记录快速排查问题。
1 git merge 简介
git merge 操作示例:
git checkout feature && git merge master
# 或者
git merge master feature
以上操作为将 master 分支最新内容 merge 至 feature 分支。操作成功后,将会在 maser 分支上自动生成一个 Merge commit。

以上为不同分支上的操作。在同一分支上操作的示例:
git add .
git commit -m 'feat: xxxx'
git fetch && git merge
# 或使用 pull: git pull = git fetch && git merge
git pull
merge的优点:操作简单,方便快捷,是一种非破坏性的操作merge的缺点:自动创建的Merge commit会导致变更树和变更历史非常复杂,分析文件变更记录时难理解
2 git rebase 简介
git rebase 通过为原始分支中的每个提交创建全新的 commit 来重写项目历史记录。
git rebase 操作示例:
git checkout feature
git rebase master --no-verify
以上操作会将 master 分支的内容合并至 feature 分支。操作成功后,所有的变更历史都会更新至最新,没有额外的 commit 记录产生。

以上为不同分支上的操作。在同一分支上操作与 merge 不同之处在最后一步。示例:
git add .
git commit -m 'feat: xxxx'
# 或者修订上一次提交
git commit --amend --no-edit
git fetch && git rebase --no-verify
# 或使用 pull: git pull --rebase = git fecth && git rebase
git pull --rebase --no-verify
<解决冲突>
<git add 冲突文件>
<git rebase –continue>

以上操作完成了同一分支上的 rebase 操作。当出现冲突时,则需继续 git rebase 之后的解决冲突相关的操作。
rebase的优点:更简洁的项目历史,没有 merge commitrebase的缺点:变更比较多时,因为会逐个变更地执行 rebase,出现冲突概率高时需不断地解决冲突,执行起来非常繁杂
3 小结:git merge 与 git rebase 使用建议
对比一下两种操作的目录树结构:

使用 `git merge` 操作的目录树例子

使用 `git rebase` 操作的目录树例子
总结与建议:
- 同一分支上,每次提交改动记录较少,建议使用
rebase
git commit <--amend> -m 'fix: fix error for ...'
git pull --rebase
- 从公共分支合并至个人的特性分支,建议使用
rebase
git fetch --all -v
git checkout feature
git rebase master
- 从个人的特性分支合并至公共分支,应当使用
merge, 不要使用rebase操作
git fetch --all -v
git checkout master && git merge feature
# or
git merge feature master
- 不同分支合并,存在较多的改动记录时,建议使用
merge
4 一些 git rebase 的操作技巧
4.1 使用 TortoiseGit 进行 rebase 操作
鼠标右键选同步,设置拉取方式为 获取,然后变基(英文对应的路径:Git Sync -> Fecth & Rebase)。如图示:


4.2 gitlab 开启 fast-forward merge 选项
设置 - 通用 - 合并请求 - Merge method,选择 Fast-forward merge 选项。
- 没有自动创建的
Merge commits - 仅使用 Fast-forward merges 策略
- 当出现冲突时,可以使用
rebase变基方式操作

4.3 git rebase --onto
基本用法:
git rebase --onto base from to
base: 是一个分支名称(代表此分支的 HEAD),或者是一个 commit_id (此 id 不在to上)from: 一个分支名称(此分支与to有共同的祖先 commit),或者是一个 commit_id (此 id 在to上)to: 一个分支名称
命令的作用:
- 首先会执行
git checkout切换到to分支 - 将
from到to(HEAD)之间所标识范围内的提交写到一个临时文件中。若from为分支名称,则找到from与to共同的祖先commit,将此commit到to(HEAD)之间所标识范围内的提交写到临时文件。 - 将当前分支强制重置(git reset --hard)到
base - 从 2 中临时文件的提交列表中,一个一个将提交按照顺序重新提交到重置之后的分支上
注:
- 如果遇到提交已经在分支中包含,跳过该提交。
- 如果在提交过程遇到冲突,衍合过程暂停。用户解决冲突后,执行
git rebase --continue继续变基操作,或者执行git rebase --skip跳过此提交,或者执行git rebase --abort就此终止变基操作切换到变基前的分支上。 - 操作结束后,当前分支为
to
4.4 git rebase -i 交互式 Rebase
交互式 rebase 使你有机会在将 commits 推送到远端分支时更改这些 commit。要使用交互式 rebase,需要添加 -i (interactive) 选项。示例:
git checkout feature
git rebase -i master
以上操作将会打开一个文本编辑器,列出即将移动的所有提交。示例:
pick 34fd80c19 commit message #1
pick d14b6ae48 commit message #2
pick 139ca1752 commit message #3

每一行开头的 pick 表示对该 commit 的指令类型。 git 提供了以下几个指令类型:
- pick:保留该commit(缩写:p)
- reword:保留该commit,但我需要修改该commit的注释(缩写:r)
- edit:保留该commit, 但我要停下来修改该提交(不仅仅修改注释)(缩写:e)
- squash:将该commit和前一个commit合并(缩写:s)
- fixup:将该commit和前一个commit合并,但我不要保留该提交的注释信息(缩写:f)
- exec:执行shell命令(缩写:x)
- drop:我要丢弃该commit(缩写:d)
通过更改 pick 命令或重新排序条目,你可以使分支的历史记录更改为你想要的结果。
例如,如果第二次提交 fix 了第一次提交中的一个小问题,您可以使用以下 fixup 命令将它们浓缩为一个提交:
pick 34fd80c19 commit message #1
fixup d14b6ae48 commit message #2
pick 139ca1752 commit message #3
再例如,在本地开发一个功能过程中,提交了多次。最后希望将它们压缩为一个 commit 来提交:
pick 34fd80c19 commit message #1
squash d14b6ae48 commit message #2
squash 139ca1752 commit message #3
保存并关闭文件时,Git将根据修改后的指令执行 rebase。
4.5 撤销 rebase 变基操作
git reflog 可以查看所有操作过程的引用记录。根据引用记录使用 git reset 命令可实现回退到任一历史状态。
git reflog
# 在输出结果中找到 rebase 操作后进行 commit 时的 ref 标记,然后执行 reset 命令,如:
git reset --hard HEAD@{2}
# 如该 rebase 操作已推送至远端,需推送更新远端仓库,可继续 push 操作
git push --force-with-lease
使用 TortoiseGit 操作也比较简单,依次如此操作:鼠标右键 -> TortoiseGit -> 显示引用记录,在弹出的引用记录列表里可以直观的进行操作。

4.6 rebase 与 git hooks
在前面的示例里,你可能已经留意到了 --no-verify 参数的使用。
由于 rebase 在执行 pick 命令时是采用的逐条 commit 方式,当仓库存在 git hook 时,会导致每一条操作都会执行 hook,使得该操作非常慢,但是 rebase 过程的 hook 并没有什么意义。 添加 --no-verify 参数可以绕过 git hook 的执行。所以推荐的结论就是:
当存在
git hooks时,rebase 操作建议添加--no-verify参数以绕过 hook 的执行。
相关参考
- https://www.zhihu.com/question/60279937/answer/174527256
- https://www.yuque.com/kshare/2019/4d4949f8-5e94-4982-b9c2-84fb17213c0c
- https://www.jianshu.com/p/4a8f4af4e803
- https://juejin.im/post/5a65ac67f265da3e330473f7
- 如何选择 Git 分支模式?