..

git rebase的使用场景

本来打算写一篇关于自己投资知识水平的文章,其实都差不多快写好了,但最近这几天每天连着亏,感觉文章内容又能扩充了,回头再发吧。今天这篇文章是关于我最近对git rebase命令的使用心得。适合掌握git基础使用,没怎么用过git rebase的人阅读。

对于git的初学者来说,能使用

git add git commit git push 就能进行最基本的使用了。

接下来的话,还需要掌握

git branch

git checkout

git pull/fetch

git merge

等命令的使用,也就是对git的分支工作模式有所了解。在不太频繁的情况下,还需要用到git log,git reset等命令用于版本回退。

如果熟悉了上述命令,应该就可以使用git和他人协同进行开发工作了。那么git rebase究竟是做什么用的呢?

首先,从实践经验来看,即便完全不使用git rebase命令,一般也不耽误和别人一起协同开发。我参加开发工作的头一年基本没主动用过rebase命令。但最近我从实际工作中对rebase命令的使用来看,我认为虽然rebase命令使用的场景有多个,但使用这个命令的目的都是一致的:为了更清晰整洁的git提交记录。

在说具体场景前,我先说一下我对这个“rebase”这个名字的理解。这个名字说实话有一定迷惑性,因为他的使用场景确实比较多,但我觉得可以这样理解:re-base,即变换当前分支的base。比如我当前在dev分支上,执行git rebase master,作用就是把dev分支的”base“分支(不管dev之前的base分支是谁)变为master分支。这应该就是rebase命令最原本的含义。网上有大量图文教程描述这个过程。

那么rebase的实际工作场景是什么?这是很多教程没有提到的,我遇到过如下场景:

  1. 主干分支的代码更新了 这应该是最常见的一种场景,而且只会在多人协同开发时遇到。

比如,我从master分支checkout出dev分支进行开发,但在我开发dev的同时,master分支的代码已经更新了(比如,同事把bugfix分支合并到了master上)。此时,可能存在如下操作:

【不正确的操作】本地的master没更新(git pull),我在本地把dev合并到了master上,此时不会产生任何冲突,合并后尝试git push master。此时,会push失败,如果尝试pull,有可能产生代码冲突,如果我在本地尝试解决代码冲突,我有可能覆盖我同事的代码。 【不正确的操作】首先更新本地master分支,然后尝试将dev合并到master分支上,有可能产生代码冲突,和上个例子一样,可能覆盖同事的代码变动。 不过上述两个例子,都需要在本地执行git push master,那么对于一个权限配置正常的、使用master-dev分支模型进行开发的Github、Gitlab仓库来说,是不应该开放git push master这样的权限的,因为这样的话显然对master分支来说比较危险。我曾经有过一次疏忽,以为自己是在feature分支上开发功能,但其实是在master分支上。我一路git add, commit, push,没有感到任何异常,但是其实是推到master上了。一个好的防呆设计,不应该依赖人的可靠性,而是在人变得不可靠的时候,避免因为不可靠带来的潜在损失。

当然还有一个前提,就是对于主干分支代码更新的前提下,什么情况下会出现上述两种操作方式呢?如果仓库是运行在github或gitlab上,代码合并走的是PR或MR模式,那就应该不会出现上面的情况。因为,如果进行PR或MR时,代码并没有产生冲突的话,是不用在意主干分支发生了更新这件事的。所以,当PR或MR时,发现系统提示存在合并冲突时,才需要考虑进行下面的操作。

【正确的操作】我应该先更新本地master代码,并尝试将master往dev分支上进行一次合并。具体的操作可以是:git checkout dev, git merge master。这样的话,就会把master分支上后来出现的那些修改,合并到dev分支上,如果有冲突就处理冲突。但这样的缺点是,会在git历史记录上行程一个master到dev的“反向合并”,会让分支记录显得杂乱。 【也许是更好的操作】先更新本地master代码,用rebase替换merge,也就是git checkout dev, git rebase master。也就是,将dev分支的“base”变为最新的master代码,有冲突解决冲突即可(但是,如果dev上有多个commit,master的后来改动会被记录到哪个commit上,我还需要确认)。使用rebas这个方式,可以避免git历史记录里的反向合并。从最终的代码合并结果上来看,和merge命令是等价的。 2. 整理git历史记录 这个场景只适用于一个人的git仓库,或者,多人协作情况下自己开发的分支。

比如,我在dev-wangyufeng分支上开发了一个很大的功能,前前后后一共有5个commit,但其实这5个commit都完成的是一个完整的功能,其中有些commit是进行了bug的修复、添加了代码注释,诸如此类。如果我把dev-wangyufeng直接合并到master并push,就会在git历史记录里把我的这几个啰嗦的commit都添加进去。

如果我想在合并分支前,整理好我要提交的commit记录,我会使用git rebase -i,交互式地整理我的开发分支上的commit记录。我可以进行合并、commit message的修改,commit顺序的变化等。

我使用git rebase -i的方法一般是,后面跟的参数是checkout分支时,当时master分支的那个commit id,比如git rebase -i xxxxxx。另外这个命令有一个挺有意思的地方,它列出的commit列表,从上到下是按从旧到新的顺序排列的,和git log正好相反,刚开始的时候有点令人迷惑,而且在列出的commit里,还不包含参数里指定的xxxxxx这个commit。

如果在整理分支前,手滑给git push了,只要分支还没合并,在做任何修改后,用git push –force-with-lease就可以更新了。使用任何带“force”参数的命令都请小心。

但是公共分支是没办法整理的,或者说整理后的代价很大。这里有点区块链的意思,修改了一个历史commit,这个commit后边的commit全都得重新生成,那这对同事来说可能会比较困扰。

  1. 消除分支记录 我听说过这个用法,但是从来没用过,它的意图是让分支记录在git记录里都看不到,只能看到唯一一根master主干分支的记录,把开发分支的commit挪到master上展现。我不这么用是因为一是我不太喜欢这样,二是身边确实也没人和我协同开发时能跟我一起使用这样的方式。

所以我对git rebase使用场景的认识就是以上三点。其实,我也听过一种说法,就是认为应当尽可能少的使用rebase,背后的思想是尽可能少地修改git历史记录,甚至完全不修改。因为有人认为,git的历史记录就应当如实反映一切真实发生过的事情,git历史记录不对人的可读性负责,而是对历史记录负责,对代码版本的完全可追溯性负责。也有人认为,git历史记录越整齐越简洁越好,git历史记录完全是为可读性服务的。

我个人更倾向于前者的观点,也就是git应当尽可能记录真实发生过的历史,但也应当对历史记录的“信息密度”进行适当的控制,那些太没信息量、太冗余的信息就不要污染仓库了,对程序员们的阅读来说也是个负担。