重塑提交历史,让PR更优雅:Git Rebase的实践运用

不知道大家有没有这个坏习惯:就是平常在开发项目时虽然会用 Git 做版本控制,但 commit message 都是乱写一通(反正也没人看),什么 Update codeAdd some files 都出来了,光看 message 根本不知道做了什么

而 commit 的大小方面,有些 commit 大到不行,一次就修改个四五百行,好像恨不得一口气写完整个功能,因此根本无法进行 code review;而有些 commit 却又小到只是修个代码缩进问题,明明不需要独立成一个 commit,在写其他功能时看到顺便修一下就好了

以上这些情况如果只发生在自己的个人项目里那倒没关系。但如果是真的要在团队内跟人合作,或是要参与 Github 上破千个 contributor 的开源项目,那这种类型的 commit 保证被喷到飞起来,不然就是被丢在一旁根本没人想帮你 review。

但同样身为程序员,我也知道脑袋进入开发专注模式之后,一晃眼就是几千行起,中间根本没时间慢慢 commit;或是有时太专心开发根本没发现程序有缩进问题,只好另外开一个 commit 来修复。

所以今天的主题就是要教大家怎么用 Git rebase 整理 commit,除了避免送出 PR 之后面临根本没人理你的窘境里,commit 整齐一点也会让 bug 更好找、心情更愉悦,可说是一举多得啊~

git add --patch

在讲 rebase 之前,这边要先介绍一个待会会用到的指令 git add --patch ,可以用来把 一个commit 切成好几次 commit

譬如说我想把这次 index.js 新增的内容分几次提交,那就下 git add -p index.js ,接着按 e 进到编辑画面,有 + 开头的就是你这次新增的内容

这时就可以把想 add 的加号留着,其他不想 add 的部分删掉,如此一来 git 就只会把有加号的那几行加入暂存区,而其他删掉的部分就继续留在工作区。

若是习惯用 GUI 的话,大部分的 IDE 也会提供 add patch 功能,只要把想加入 Staging 选起来,然后点 Stage Selected Ranges 就好了。

Git Rebase的几种用法

接着就要进入本文的主题 git rebase ,我的本地仓库里的 master 有 README.mdfile1file2 ... file5 共 6 个文件,而历史纪录如下图:一开始先是 Init,接着连续新增五个文件,接着就要用这个仓库来尝试 rebase

调整 commit 顺序

第一次 rebase 的目标是要尝试调整 commit 的顺序。

首先执行下指令 git rebase -i 4a16df-i 是 interactive 的意思,而 4a16df 是第一个 Init 的 commit ID,代表我要 用交互模式来调整 Init 之后的 commit ,按下 Enter 后就会看到这个编辑画面(在 Vim 里面);

这个画面很重要:意思是现在的 master 是从 Init 开始, 按照顺序把这些 commit 的变更叠加起来而成的(从上到下)

所以想要变更 commit 的顺序也很简单,只要调整 pick 的顺序,把 Add file3 移到最下面,Git 就会按照 1 -> 2 -> 4 -> 5 -> 3 的顺序把 commit 叠加起来,产生最新的 master;

保存退出后就完成了第一次 rebase,很简单吧!完成后 master 会变成下面那个 branch ,顺序是 1 -> 2 -> 4 -> 5 -> 3 ,从历史纪录也可以看出来 Add file3 确实变成最后一个 commit 了。

结束 rebase 之后,之前 master 的所在位置会被改名叫 ORIG_HEAD,所以如果对于 rebase 后的结果不满意的话,只要下 git reset --hard ORIG_HEAD 就能回到之前 1 -> 2 -> 3 -> 4 -> 5 的顺序。

所以要调整 commit 的顺序,其实就是进入到交互模式的rebase,接着调整一下 pick 的顺序就可以了~如果对于怎么操作还是不太清楚,也可以看看这个我录的小短片,看完应该就会喽。

删除 commit

刚刚提到 git rebase 时 git 会按照顺序把 commit 叠加起来,所以要删掉某一个 commit 也很简单,就是在 rebase 时把前面的 pick 指令改成 drop 就好了。当 git 从上往下读取到 drop 指令,他就会直接把那个 commit 丢掉,只把 pick 开头的 commit 拿去用。

举个例子,假如我今天要把 Add file4Add file5 两个 commit 删掉,那就执行一下 git rebase -i 4a16df ,接着把不要的 commit 改成 drop,

rebase 执行完之后,因为 Add file3 是最后一个 commit,所以 master 就会被移到 Add file3 的地方, file4file5 也会从 git 提交历史中消失。

而 ORIG_HEAD 就跟先前一样被 Git 放到 rebase 之前的位置,所以如果不小心误删了很重要的 commit,不要紧张,只要 git reset --hard ORIG_HEAD 就好了;

到这边大家应该对于 rebase 更有概念了,简单来说就是先挑一个 rebase 的基准点(在这边是 Init),然后就可以对他后面的 commit 进行各种调整。

修改 message

除了调整顺序、删掉 commit 之外, rebase 还有一个指令 reword 可以用来修改 commit message。

比如说我想把 Add file4 改成 Finish file4 ,那就在 rebase 时把 pick 改成 reword,那 Git 在使用那个 commit 时就会自动打开你的编辑器(Vim)让你改,改完之后他再继续 pick 后面的 Add file5

改完的历史纪录长这样:因为 commit ID 有部分是经由 message hash 而成,所以 reword 并不会改写原本的 commit ,而是产生另外一个新的 commit 跟 branch,并且把 master 移到新的 branch 之上。

若对于 reword 的结果不满意,那就 reset 回去 ORIG_HEAD 就好了,反正所有 rebase 前的 commit 都会储存在 .git 文件夹里面 ,rebase 只是把 master 换到另外一个 branch 而已。如果觉得看图不够清楚的话,可以看看这个 20 秒小短片,看完就知道怎么修改 message 了。

说实话这大概是我最常用到的 rebase 指令,因为我在开发时 message 基本上都是乱写一通,如果不修改一下直接 push 上去,隔天睡起来我可能就不知道那个 commit 在干嘛了

合并 commit

有时若在送出 PR 之前发现一些格式上的修改,或是觉得 commit 太小太散了,那也可以用 rebase 来合并多个 commit,譬如说我想把 Add file3Add file4 ,那就把 Add file4 的 pick 改成 squash,代表我想要把他跟上一个 commit(Add file3) 合并。

执行之后就跟下图展示的一样,只要把原本的两个 commit message 删掉,然后写上 Add file3--4 就好了。rebase 结束后 Git 会产生一个新的 Add file3-4 ,他的变更内容就是原本两个 commit 变更内容的总和。然后 ORIG_HEAD 一样会在原地,不满意随时可以 hard reset 回去

想看实际操作的话可以看看这个小短片,用起来真的很简单哦。

拆分 commit

因为一个 commit 可以有好几种拆法,譬如说五五分、三七分等等,不像合并就是两个 commit 凑起来就完成了,所以过程也复杂许多

如果我想要把刚刚的 Add file3--4 拆回来,那就要用到 edit 指令,他的功能是这样的:Git 在遇到 edit 指令时,他会先使用那个 commit,接着就先暂停下来,一直到我执行 git rebase --continue 才会继续 rebase。

所以做法上会是这样:我要等 Git 在使用完 Add file3--4 之后会暂停,然后马上用 reset 把 Add file3--4 拆掉,接着手动分别 commit 两个文件。

如下图,在执行完 edit b19b0e5 Add file3--4 之后,Git 会使用 Add file3-4 并且暂停下来,我们要趁这个卡在中间的时候把 Add file3--4 拆成 Add file3Add file4

因为目前是暂停在 b19b0e5(Add file3-4) 结束之后,所以这时可以直接用 git reset @^Add file3--4 拆掉,让 file3 跟 file4 回到工作区

拆掉后,再分两次 commit 这两个文件,到这里我们已经把 Add file3--4 拆成 Add file3Add file4 了;

但因为 rebase 还没完全结束,刚刚是做到一半暂停,后面还有一个 pick 8c96f5 Add file5 要做,所以要执行 git rebase --continue 让 rebase 继续,完成后看 git log 就可以看到 Add file3Add file4 又被拆回来了~

edit 指令算是 interactive rebase 里面比较复杂的,所以我也录了一个小短片,看影片跟着动作做几次就会觉得没那么难了。

一开始用 edit 时常常会搞到晕头转向不知道自己在哪个 commit,但熟了之后就会发现他真的很神,可以让你在历史纪录里面飞天遁地,想改哪就改哪,也可以对过去 commit 做一些奇怪的事(修改时间、作者、提交信息等等)。

另外,虽然这边没有示范,不过只要用一样的方法搭配开头讲的 git add --patch ,就可以把一个巨大 commit 拆成好几个 commit 喽。

总结

今天关于 rebase 的介绍就到这里,总共讲了 pick、drop、reword、squash、edit 五个指令以及他们的 use case,这些指令不用硬记,因为 rebase 的介绍会把所有可用的指令都列出来(下图),如果曾经用过看一眼就会想起来了~

另外,虽然有些指令像 fixup、exec 没有讲到,但只要看一下他的叙述再试一下应该就会用了,反正如果不小心 rebase 坏了,那大不了再 hard reset 回 ORIG_HEAD 就好,就像什么事情都没发生过,我想这种近乎时光机的功能就是使用 Git 最大的好处吧。

最后再提醒一下,因为 rebase 会 变更历史纪录 ,所以最好是在 push 之前就做好 rebase,不然就是确保要这个 branch 只有你自己在修改,否则你也 rebase、他也 rebase,最后只会把历史纪录搞得一团乱哦。就这样,希望今天的内容对大家有帮助。

相关推荐
唔知小罗1 小时前
git config是做什么的?
git
用户3157476081351 小时前
成为程序员的必经之路” Git “,你学会了吗?
面试·github·全栈
墨染8662 小时前
HP G10服务器ESXI6.7告警提示ramdisk tmp已满
github
油泼辣子多加4 小时前
2024年11月13日Github流行趋势
github
叫我龙翔5 小时前
【项目日记】仿mudou的高并发服务器 --- 整体框架搭建 ,实现时间轮模块
运维·服务器·网络·c++·github
不是鱼6 小时前
新人程序猿必备的git技能(超实用基础版)
git·github
Srlua8 小时前
从创建 GitHub 项目到推送代码:Git 命令行操作全流程
github·gittee
Exclusive_Cat10 小时前
Git的使用(基础语句)
git
江上清风山间明月10 小时前
git撤销、回退某个commit的修改
git·commit·版本·撤销·回退·特定
cui_win10 小时前
Redis高可用-主从复制
redis·git·github·主从复制·哨兵