当前位置:网站首页 > 历史 > 重塑提交历史,让PR更优雅:Git Rebase的实践运用

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

重塑提交历史,让PR更优雅:Git Rebase的实践运用前言不知道大家有没有这个坏习惯:就是平常在开发项目时虽然会用 Git 做版本控制,但 commit message 都是乱写一通(反正也没人看)

前言

不知道大家有没有这个坏习惯:就是平常在开发项目时虽然会用 Git 做版本控制,但 commit message 都是乱写一通(反正也没人看),什么Update code、Add 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 功能,只要把想加入暂存区的话,先全选起来然后点 Stage Selected Ranges 就好了。

VSCode gitGit Rebase的几种用法

接着就要进入本文的主题 git rebase ,我的本地仓库里的 master 有README.md、file1、file2…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 file4跟Add file5两个 commit 删掉,那就执行一下git rebase -i 4a16df,接着把不要的 commit 改成 drop,

rebase 执行完之后,因为Add file3是最后一个 commit,所以 master 就会被移到Add file3的地方,file4跟file5也会从 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 file3跟Add 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 file3跟Add file4

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

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

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

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

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

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

总结

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

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

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

上一篇: 幼儿角色游戏对其社会性发展的影响研究文献综述
下一篇: Git版本控制工具使用说明和规范

为您推荐

发表评论