先将本文主要观点写在前面:
- 同一分支上开发,每次提交改动记录较少,建议使用
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 分支模式?