本文让你熟练使用 rebase,学会以下两种操作,从此拒绝杂乱无章的 git 提交。

用法一: 合并当前分支的多个commit记录

你可能出现过对同一处代码进行多次处理的场景。这会导致如下提交记录:

$ git log --pretty=format:'%h: %s'
d2399da: feat: modify c
0134695: feat: modify b
eb63848: feat: modify b
51c0bca: feat: modify b
4cb600e: feat: modify a
d29f331: Initial commit

其实,中间的对 b 的3次提交完全可以合并成一次 commit,这个时候 rebase 就很有用了。

step1. 找到想要合并的 commit, 使用 rebase -i

git rebase -i 4cb600e

注意 git rebase -i [startPonit] [endPoint]

  • 前开后闭 区间 这里的 [startPonit] 是指需要合并的 commit 的前一个 commit (即当前示例中的 “4cb600e: feat: modify a”)。 因为, 三个 commit 肯定要基于上一个 commit 合并成了新的 commit。
  • 谨慎使用[endPoint] 省略, 即默认表示从起始 commit 一直到最后一个,但是一旦你填写了, 则表示 [endPoint] 后面的 commit 全部不要了!

step2. 进入 Interact 交互界面

进入 Shell 终端选择交互界面,让你进行变基选择操作:
在这里插入图片描述

说明

  • 最上面三行, 就是刚刚选中的三个 commit, 按时间顺序依次往下排序(和 git log 的展示顺序是反的, 大家查看的时候要注意)
  • 前面的三个 Pick 其实就是下面 Commands 展示的7种命令中的第一个p, 也就是使用 commit。

step3. 使用 s 命令 合并到上一个 commit

  1. i 进入操作, 将第二、三个 commit 的 pick 改成 s
  2. Esc 退出操作
  3. 输入:wq保存并退出
    在这里插入图片描述

step4. 修改 commit 记录

接下来会弹出第二个页面, 分别展示三个 commit 的提交信息:
在这里插入图片描述

这里三个信息都是一样的,我们选用第一个的提交信息,将其余的全部注释掉,重复上述步骤, 保存退出即可。
在这里插入图片描述

step5. 查看最新合并情况

会发现原三个一样的提交现在合并成了一个新的 commit 。在这里插入图片描述

rebase 的其他用法

命令缩写含义
pickp保留该 commit
rewordr保留该 commit,但需要修改该 commit 的注释
edite保留该 commit, 但我要停下来修改该提交(不仅仅修改注释)
squashs将该 commit合并到前一个 commit
fixupf将该 commit合并到前一个 commit,但不要保留该提交的注释信息
execx执行 Shell 命令
dropd丢弃该 commit

用法二: 避免出现分叉合并

接下来,我将用实际示例和场景,来分析 rebase 是如何解决分叉合并的,在此之前, 我先做如下规定:

  1. 有两个分支: develop(主分支)、feature(需求分支);
  2. 新需求按时间顺序ab、…等(a 需求最早, b 其次, 以此类推);
  3. 原 commit a 变基之后(hashId 改变) 叫 a'

场景1: 合并时, 最舒服的情况

在这里插入图片描述
此时的合并有两点好处:

  1. 没有冲突
  2. 没有多余的 commit 提交
    其实这种情况下, rebase 和 merge 的效果是一样的。

请大家记住这个场景, 后面所有的 rebase 都是奔着这个目标来的。

场景2: 各分支都有自己新的 commit

develop 新增需求 a: “feat: a”

feature 新增需求 b: “feat: b”在这里插入图片描述

● develop 直接 merge feature

在这里插入图片描述
会出现以下结果:

  1. develop 会保留 feature 的所有 commit(hashId不变);
  2. 按提交顺序排序;
  3. 产生新的 commit 点(Merge branch ‘XXX’ into develop)
    在这里插入图片描述
● develop 直接 rebase feature

在这里插入图片描述
会出现以下结果:

  1. develop 分支的 a 会被排在合进来的 feature 分支 b 的上面(尽管 a 是先完成的)
  2. develop的原 commit a 被移除 产生了新的 commit a’(hashId 已变)
  3. 从 feature 合进来的 b 不变 (不会对合进来的 commit 进行变基)
    在这里插入图片描述
● rebase 终极版

上述两种操作虽然都能完成合并,但是结果却不是我们想要的。直接 merge 会产生多余提交,而直接 rebase 虽不会产生多余提交,但是会改变自身原有的 commit,这对上游的主分支来说是灾难性的。试想一下,如果上游的 master 主分支被你悄悄修改了 commit,而其他所有人还是基于原来的 commit 进行的开发,可想而知,到时候合代码就会如同车祸现场一样,遍地冲突…

所以,正确的操作是:先提前将 master 主分支的新代码 rebase 到自己的分支,再在主分支上 merge 自己的分支。

这样有两点好处:

  • rebase 的时候,虽然自己的新 commit,但是我们还没有提交上去,所以 commit 的变动对我们没有影响;
  • 如果有冲突,可以提前在自己的分支解决掉, 不会带到主分支上。

我们来看下完整示例:

step1: feature rebase develop
# feature分支
git fetch origin
git rebase develop

# 或者 直接一个命令
git pull develop --rebase

在这里插入图片描述
会出现以下结果:

  1. develop 的新需求 a 会先排在 b’ 的前面(a 先提交上去了嘛);
  2. feature 的 commit b 会重新排在第一个 变成 b’(a 插队了嘛,顺序就变了)。

到这里, 你应该有所察觉了!: 没错! 这一步相当于 回到了场景1的模式, 我当前的 feature 相当于先把需求b 拎出来, 同步完最新的 develop, 再把需求b放在了最后面。

所以, 接下来 merge 的时候 就很舒服了~!

step2: develop merge feature
# develop分支
git merge rebase_new

在这里插入图片描述
在这里插入图片描述
而这, 也是 rebase 为什么不会产生多余的 commit 记录的原因了。

rebase时如何解决冲突

  1. 先解决冲突 再保存
  2. git add .
  3. git rebase --continue
    注意! 不是commit ! 不是commit ! 不是commit !

使用 rebase 的注意点

警告!

先引用官网上的一段话:

如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基。

如果你遵循这条金科玉律,就不会出差错。 否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。

因为变基最强大也是最危险之处, 就在于, 它能改变原 commit 的 hashId, 而一旦你对历史提交做出改变, 那么 从变基那个节点开始往后的所有节点的 commit id 都会发生变化。 这对线上环境来说, 是不可控的!

注意一:不要基于 rebase 的分支 切新分支

在这里插入图片描述
如果 feature 在写完新需求 b 后,切了新分支 feature_B,然后又 rebase 了 develop,那么新分支feature_B 无论是合进 develop 还是 合进 feature 都会有冲突, 出现重复的 b(其实是 b 和 b’)
在这里插入图片描述在这里插入图片描述
除非 你能百分百确保 你的分支已经完成新需求, rebase 操作结束之后, 再切新分支, 这时 他们才是同步的。

注意二:不要对已经合并到主分支的本地修改进行变基

首先, 自己的分支, 如果想对已经推送的 commit 进行修改, 可以在修改完后, 使用 git push -f 强行 push 并覆盖远程对应分支。

但是如果这些修改 已经被合到了其他分支(比如主分支),那又会出现冲突,因为其他分支保存的是你 rebase 之前 合进去的 commit。
在这里插入图片描述

注意三:不要在预发布、正式分支上使用 rebase -i

从变基那个节点开始往后的所有节点的 commit id 都会发生变化。这个就不再赘述了。
所以可以想象一下, master上有100个 commit,你悄悄改了第50个 commit,那从 50—100 的所有 commit 全部改变了, 这时, 别人的分支合进来, 就会有51个冲突, 解决完冲突之后, 就会产生 51*2 个相同的提交记录, 恐怖如斯!

总结

git rebase 是一条集增删改查于一体的强大命令,它既可以对自己的分支进行修修剪剪,也可以在合并其他分支的时候缝缝补补,让我们的 commit 提交看上去干净清爽。

记住一条原则:只对尚未推送或未分享给别人的本地修改执行变基操作,清理历史, 从不对已推送至别处的提交执行变基操作

这样,你才能享受到两种方式(rebase 和 merge)带来的便利。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐