引言
工作一年多,使用过的git命令一只手都数的过来
git add, git commit, git push, git stash, git pull, git merge, git log
理论上来说,上面这些命令能应付绝大部分的使用场景,但是,我们看一下git graph,我的天!为什么这么乱啊?
大多数的软件公司,不太会在意commit信息是否混乱(命名不规范、分叉),当然,并不是所有公司都像Google一样,对于commit的命名都辣么严格。假如,你当前的公司对于git的提交非常严格,那么这篇博客会帮助你学会使用git rebase重构提交记录。
Git Rebase 和 Git Merge
rebase的中文名叫“变基”,就是改变一次提交记录的base。我们不妨假设:git rebase ≈ git merge,并且使用两种命令实现同一工作流来对比它们的不同
我们假设两名开发人员合作开发,张三负责dev_a分支,李四负责dev_b分支,两人阶段性的合入dev分支,那么从张三的角度来想,可能的工作流程是这样的:
- 个人在
dev_a分支上开发自己的功能 - 在这个期间其他人可能不断地向
dev分支合并代码 - 个人开发功能完成后通过
merge的方式合入别人开发的功能

对应git命令如下:
git checkout dev_a
git pull origin dev = git fetch origin dev + git merge dev
同样走完这样一个工作流我们如果使用git rebase来实现,结果如下:

rebase之中

rebase之后

对应代码:
git checkout dev_a
// 本地功能开发...
git fetch origin dev
git rebase dev
git checkout dev
git merge dev_a
git br -d dev_a
由此可见:
- 两者都可以用于本地代码合并
- git merge保留真实的用户提交记录,且在merge的时候生成一个新的提交记录
- git rebase会改写历史记录,这里的改写不仅限于树的历史结构,树上的节点
commit id也会改写,收益是可以保证提交记录非常清爽
如何使用 git rebase -i 修改历史提交记录
git rebase -i,中文名叫交互式变基。意思就是在变基的过程中是可以掺入用户交互的,通过交互过程我们可以主动改写历史提交记录,包括修改、合并和删除等。我们以上面使用rebase后得到的提交记录为例,来进行历史提交记录的修改,在修改之前,提交记录是这个样子的

使用git rebase -i 修改历史提交的过程主要包含三步:
- 列出一个提交记录的范围,并指出你在这个范围内需要怼那些记录进行什么样的修改
- 执行上述修改,如果遇到冲突需要解决
- 完成
rebase操作
以上面截图中的提交记录为例,来对历史提交的commit msg进行修改,操作步骤如下:
// 查看最近6次提交记录,选择对哪一条记录进行修改
git rebase -i HEAD~6

执行完上述命令后,会以vim的方式打开一个文件(我设置成了vs code,习惯了图形化操作,不习惯vim编辑)
文件中显示了最近6次的提交信息,从上到下,由远到近。
从下面的注释可以看到,我们分别把每一行前面的pick修改成r, s, d的方式就可以实现对历史记录的修改,合并和删除。首先我们尝试修改提交信息,把第二行前面的pick改成r,保存退出(vim党自行研究吧)
除了修改提交的commit msg之外,我们也可以通过把pick改为edit,结合git reset --soft HEAD^的方式对档次提交的改动内容进行修改
合并与删除历史提交的操作步骤与编辑类似,只需要把pick分别改为s和d即可,各位看官可以自行尝试。如果在rebase的过程中遇到了冲突,需要手工解决,然后使用git rebase --continue完成rebase操作。git rebase的提示还是非常友好的,它会告诉你需要进行哪些操作解决当前的问题

使用 git rebase -i 必须遵守的规则是什么?
从修改历史提交记录这个功能来看,交互式变基是一个非常强大的功能。但是使用这个功能必须要遵循一个铁则:不要对线上分支的提交记录进行变基!
引用git官方指导文档的话来说大概是这样:
如果你遵循这条金科玉律,就不会出差错。 否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。
在说为什么不能对线上提交执行交互式变基之前,先说一下如果要对线上功能执行这个操作要怎么做
首先,你需要在自己本地变基成功,然后使用git push -f强行push并覆盖远程对应分支,之所以需要执行覆盖式push是因为如果你不覆盖,当前变基过后产生的新提交会与远程合并,导致你在本地的变基行为失去意义。因为我们上面提到过,从变基那个节点开始往后的所有节点的commit id都会发生变化。
同样的原因,即使你使用git push -f使远程分支发生了变基,如果你的同事的开发分支中还存在你执行变基操作(不论是修改、合并还是删除)时针对的那些分支,那么当你的同事merge你的提交之后,你所有想使用变基改变的东西都回来了!
如果打破了 git rebase -i 的使用规则应该怎么补救
此处我们尝试通过要点描述的方式,说明线上提交执行变基会导致什么结果以及如何避免这个结果:
- 你在本地对部分线上提交进行了变基,这部分提交我们称之为
a,a在变基之后commit id发生了变化 - 你在本地改变的这些提交有可能存在于你的同事的开发分支中,我们称之为
b,他们与a的内容相同,commit id不同 - 如果你把变基结果强行
push到远程仓库后,你的同事在本地执行git pull的时候会导致a和b发生融合,且都出现在了历史提交中,导致你的变基行为无效 - 我们想要的是你的同事拉取线上代码时跳过对
a和b的合并,只是把他本地分支上新增的修改合并进来
讲了这么多,最终的结论就是,使用变基解决变基带来的问题。即你的同事使用git rebase的方式把他本地的修改rebase到远程你执行过rebase的分支上
简言之,就是你的同事使用git pull --rebase而不是git pull来拉取远程分支。在这个操作的过程中,git会对我们上面提到几个要点的信息进行检查并把真正属于同事本地的修改合入远程分支的最后。
所以我们应该如何使用 Git Rebase
鉴于上面描述的git rebase可能带来的问题,最后要回答的一个问题是我们应该如何在日常工作中使用git rebase,同样借用git官方文档中的一句话:
总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史,从不对已推送至别处的提交执行变基操作,这样,你才能享受到两种方式(
rebase和merge)带来的便利。