合理地使用 git rebase 代替 git merge 操作

目录
[隐藏]

先将本文主要观点写在前面:

  1. 同一分支上开发,每次提交改动记录较少,建议使用 rebase
  2. 从公共分支合并至个人的特性分支,建议使用 rebase
  3. 不同分支合并,存在较多的改动记录时,建议使用 merge
  4. 从个人的特性分支合并至公共分支,应当使用 merge不要使用 rebase 操作

如果你平时习惯于使用默认的 git merge 操作,那么本文或许会对你有所帮助。

合理使用 git rebase 取代 git merge 操作,可以让变更历史记录更为清晰,易于根据历史记录快速排查问题。

1 git merge 简介

git merge 操作示例:

git checkout feature && git merge master
# 或者
git merge master feature

以上操作为将 master 分支最新内容 mergefeature 分支。操作成功后,将会在 maser 分支上自动生成一个 Merge commit

git merge

以上为不同分支上的操作。在同一分支上操作的示例:

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 记录产生。

git rebase

以上为不同分支上的操作。在同一分支上操作与 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>

git-rebase

以上操作完成了同一分支上的 rebase 操作。当出现冲突时,则需继续 git rebase 之后的解决冲突相关的操作。

  • rebase 的优点:更简洁的项目历史,没有 merge commit
  • rebase 的缺点:变更比较多时,因为会逐个变更地执行 rebase,出现冲突概率高时需不断地解决冲突,执行起来非常繁杂

3 小结:git mergegit 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 : 一个分支名称

命令的作用:

  1. 首先会执行 git checkout 切换到 to 分支
  2. fromto(HEAD) 之间所标识范围内的提交写到一个临时文件中。若 from 为分支名称,则找到 fromto 共同的祖先 commit,将此 committo(HEAD) 之间所标识范围内的提交写到临时文件。
  3. 将当前分支强制重置(git reset –hard)到 base
  4. 从 2 中临时文件的提交列表中,一个一个将提交按照顺序重新提交到重置之后的分支上

注:

  1. 如果遇到提交已经在分支中包含,跳过该提交。
  2. 如果在提交过程遇到冲突,衍合过程暂停。用户解决冲突后,执行 git rebase --continue 继续变基操作,或者执行 git rebase --skip 跳过此提交,或者执行 git rebase --abort 就此终止变基操作切换到变基前的分支上。
  3. 操作结束后,当前分支为 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

git-rebase-i

每一行开头的 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 -> 显示引用记录,在弹出的引用记录列表里可以直观的进行操作。

git reflog

4.6 rebasegit 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 分支模式?
点赞 (3)

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Captcha Code