Git时光穿梭--git rebase用法详解
上次看《12猴子》,是个时空穿梭题材的电影。这个电影的剧情设计的非常精妙,找到了当年看无间道的感觉。女主也很美,值得一看。
其实,每次看到时空穿梭的题材,我总是有个想法:如果从2019年穿越到1989年,那么这个穿越后的1989年,还是原来的1989年吗?
就是说对时空穿梭这个行为有两种理解,假设原来的时间线是这样的:1989 --> 1999 --> 2009 --> 2019。而我身在2019年,现在我穿越到了1989年。
经典的穿越的观点认为:我真的穿越到了1989年,那个时空凭空的出现了我这个人。在这种假设中最大的问题在于:原来的1999年、2009年、2019年到底有没有发生过?如果它们是已经过去的时光,那么就出现了1989年是2019年之后的年份,这怎么理解呢?如果1999年、2009年、2019年没有发生过,我又是从哪里来的呢?还有就是经典的祖父悖论。
我个人更加倾向于这样认为,如果真的可以发生穿越,那也是这样的穿越:我穿越到了1989年,它的时间线是这样的1989 --> 1999 --> 2009 --> 2019 --> 1989。第二个1989和第一个1989不是同一个1989。这两个时空里面的一切都完全相同,除了一点之外,那就是后一个1989有一个更加漫长的历史。原来的1989 --> 2019这段历史因为我的穿越被抹去,除了站在上帝视角,这段历史无法被观测到。
好了,闲扯半天,成功摸鱼。接下来进入正题:git rebase命令详解。
在讲解git rebase命令之前,我们需要明确两点:
- git 的时间线
- commit的本质
git的时间线
我们的git的提交历史其实有两个时间线:一个用git log来查询,一个用git reflog来查询。
# 初始化一个git仓库
$ mkdir git-history
$ cd git-history
$ git init
# 添加三个commit记录
$ touch a.txt
$ git add .
$ git commit -m 'init'
$ touch b.txt
$ git add .
$ git commit -m 'add b.txt'
$ touch c.txt
$ git add .
$ git commit -m 'add c.txt'
那么现在我们的时间线是这样的:
$ git log
commit 93f0032324d14fc53bd851d4408fe4b839f69c0b (HEAD -> master)
Author: J J <wow@github.com>
Date: Mon Jul 29 20:54:50 2019 +0800
add c.txt
commit f7dc40b1f1d55a5ea7e5847229ce7e46989be460
Author: J J <wow@github.com>
Date: Mon Jul 29 20:54:13 2019 +0800
add b.txt
commit 7f1b160e4e74d3abb19710ee52e62bd49ea8e5c5
Author: J J <wow@github.com>
Date: Mon Jul 29 20:53:24 2019 +0800
init
那么接下来我们来版本回退,穿越历史。假设我们想回到'add b.txt'也就是f7dc40b1f1d这个版本,我们要怎么做呢?很简单,通过git reset命令就行了。
$ git reset --hard f7dc40b1f1d
$ ls
a.txt b.txt
这时候我们再来看下时间线:
$ git log
commit f7dc40b1f1d55a5ea7e5847229ce7e46989be460 (HEAD -> master)
Author: J J <wow@github.com>
Date: Mon Jul 29 20:54:13 2019 +0800
add b.txt
commit 7f1b160e4e74d3abb19710ee52e62bd49ea8e5c5
Author: J J <wow@github.com>
Date: Mon Jul 29 20:53:24 2019 +0800
init
可以看到,我们确实成功的穿越了,其实这和我之前说的第一种穿越模式很像。
但是git其实还有一个时间线,可以这样查询:
$ git reflog
f7dc40b (HEAD -> master) HEAD@{0}: reset: moving to f7dc40b1f1d
93f0032 HEAD@{1}: commit: add c.txt
f7dc40b (HEAD -> master) HEAD@{2}: commit: add b.txt
7f1b160 HEAD@{3}: commit (initial): init
可以看到,在这个时间线中,我们穿越这个动作本身都被记录了下来。虽然看起来我们回到了第二次提交的版本,其实这只是看起来。按照这个时间线,其实就符合我之前提到的第二种穿越模式。
上面就是简单的介绍了git的时间线,git reflog显示的才是我们git真正的时间线。不过一般的情况,我们就按照git log的时间线来理解就行了。
commit的本质
要理解git rebase,首先要理解我们的commit。 每次我们提交了一个commit后,我们的git记录的不是新的状态。不是说我们每次commit,它就把当前的仓库中的所有文件复制一遍,记录下来。其实它记录的是变化。
在上面的例子中,一开始仓库的初始状态为空,然后我们添加了a.txt,这里git记录的是添加了a.txt这个动作本身。同理,git记录了添加b.txt和添加c.txt这两个操作。
所以,在这个仓库中,git的记录的是:
- 初始状态空
- 添加了a.txt
- 添加了b.txt
- 添加了c.txt
正是这个原因,git的版本控制不会占用太多的磁盘空间,在版本切换时也会非常的快。 这个一定要理解,不然后面git rebase的时候你会搞不清我们到底做了什么。
git rebase
因为我们还要用到c.txt,所以接下来我们通过git reset命令,退回到第三次commit的状态。
版本切换时,你需要知道这个版面对应的commit id。如果你忘记了,可以通过git reglog来查询。 这里我就是:
$ git reset --hard 93f0032
$ ls
a.txt b.txt c.txt
你的commit id和我的应该不一样,不要直接复制我的代码!
然后我们开始使用git rebase
$ git rebase -i HEAD~2
git rebase 命令都是这样的,-i表示我们在命令行以交互式的方式操作。后面的HEAD~2或者HEAD~3表示我们想处理最近的2次或者3次commit。
使用完之后,就会进入我们命令行编辑模式了

这里,我们可以看到我们的最近两次提交的信息,每条信息由三个部分组成:command + commit id + commit message。
然后下面列举了各种可选的command,我们就是通过修改command来执行rebase操作的。现在默认状态是pick,它其实什么都没做,如果我们现在退出,等于我们没有做任何修改。
修改commit message
现在我发现之前的commit message写的不好,我希望修改它。这其实是很常见的需求,我们可以怎么做呢?
首先我们还是进入这个交互式界面
$ git rebase -i HEAD~2
然后我们只需要把pick这个单词修改为r或者reword就行了。这个举动表示我们希望修改这个commit的commit message。
修改完之后,我们直接保存退出就行了。保存退出后我们会立刻进入一个新的交互式界面,这个界面是让我们为这次提交编写commit message

这里,我们只要修改一下,把commit message 修改成任何我们想要的信息就行了

然后我们再来看一下git log
$ git log
commit bd0cb6668487ccf18a584e3af8d766eb7735b085 (HEAD -> master)
Author: J J <wow@github.com>
Date: Mon Jul 29 20:54:50 2019 +0800
ADD c.txt
commit f7dc40b1f1d55a5ea7e5847229ce7e46989be460
Author: J J <wow@github.com>
Date: Mon Jul 29 20:54:13 2019 +0800
add b.txt
commit 7f1b160e4e74d3abb19710ee52e62bd49ea8e5c5
Author: J J <wow@github.com>
Date: Mon Jul 29 20:53:24 2019 +0800
init
可以看到我们修改成功了。
但是这里要注意一点,第三个commit的commit id已经变化了。这是因为每一个commit都是唯一,我们上面的操作等于把之前的修改删除,然后又创建了一个新的修改。
这里大家可以自行通过git reflog来查看下。
重排序
除了修改commit message,我们还可以对我们的commit进行重排序。
目前我们的commit顺序是init --> add b.txt --> ADD c.txt,现在我想把它修改为init --> ADD c.txt --> add b.txt,那么我们可以怎么做呢?
很简单,我们只需要在git rebase的时候将原来的commit 重新排序就行了
首先
$ git rebase -i HEAD~2
进入交互式界面后,原来是这样的

现在给它们调换顺序

保存,退出,就行了
$ git log
commit 3dffa166f91e7db459d2d13e58d0e5e288cc5491 (HEAD -> master)
Author: J J <wow@github.com>
Date: Mon Jul 29 20:54:13 2019 +0800
add b.txt
commit 413bb5e2870687dd6175c2b0135035a5aa06458e
Author: J J <wow@github.com>
Date: Mon Jul 29 20:54:50 2019 +0800
ADD c.txt
commit 7f1b160e4e74d3abb19710ee52e62bd49ea8e5c5
Author: J J <wow@github.com>
Date: Mon Jul 29 20:53:24 2019 +0800
init
合并
很多时候,我们提交了一次commit之后,可能又发现之前的代码有点小毛病,然后我们不得不提交一个新的commit。这样的commit提交的多了,就会污染我们时间线,让我们后期管理起来不是很方便。
所以我们常常需要合并commit。
现在我们在b.txt中写入一段文字,并且提交。
$ echo 'hello b.txt' > b.txt
$ git add .
$ git commit -m 'edit b.txt'
$ git log
commit f765971025682f52c27c24dc15a7033c519af648 (HEAD -> master)
Author: J J <wow@github.com>
Date: Mon Jul 29 22:41:55 2019 +0800
edit b.txt
commit 3dffa166f91e7db459d2d13e58d0e5e288cc5491
Author: J J <wow@github.com>
Date: Mon Jul 29 20:54:13 2019 +0800
add b.txt
commit 413bb5e2870687dd6175c2b0135035a5aa06458e
Author: J J <wow@github.com>
Date: Mon Jul 29 20:54:50 2019 +0800
ADD c.txt
commit 7f1b160e4e74d3abb19710ee52e62bd49ea8e5c5
Author: J J <wow@github.com>
Date: Mon Jul 29 20:53:24 2019 +0800
init
现在我们希望最近的两次commit合并在一起,我们可以怎么做呢?
首先还是git rebase
$ git rebase -i HEAD~2
看到这个界面

然后把对应的commit 的command修改成s,意思就是把当前的commit和它之前的合并在一起

然后保存、退出就行了。退出后会立刻进入一个新的交互式界面,在这里你可为合并后的commit添加新的commit message。

$ git log
commit 898d7a9e64c486370fac708f1006d833cb062842 (HEAD -> master)
Author: J J <wow@github.com>
Date: Mon Jul 29 20:54:13 2019 +0800
add b.txt
edit b.txt
commit 413bb5e2870687dd6175c2b0135035a5aa06458e
Author: J J <wow@github.com>
Date: Mon Jul 29 20:54:50 2019 +0800
ADD c.txt
commit 7f1b160e4e74d3abb19710ee52e62bd49ea8e5c5
Author: J J <wow@github.com>
Date: Mon Jul 29 20:53:24 2019 +0800
init
综合操作
有了前面两步的铺垫,我们可以把重排序和合并这两种操作给综合起来。
比如说现在我们想给c.txt添加一个文本'hello c.txt',但是我们又不希望污染现在的git时间线,我们该怎么做呢?
首选来修改c.txt
$ echo 'hello c.txt' > c.txt
$ git add .
$ git commit -m 'edit c.txt'
$ git rebase -i HEAD~3
原来是这样的

修改成这样的

保存退出就行了
$ git log
commit c87beb75c417d845eaff58cebcddce82c92eed58 (HEAD -> master)
Author: J J <wow@github.com>
Date: Mon Jul 29 20:54:13 2019 +0800
add b.txt
edit b.txt
commit 35a898027cd25a9c0c0a340bb06151af678f7ead
Author: J J <wow@github.com>
Date: Mon Jul 29 20:54:50 2019 +0800
ADD c.txt
edit c.txt
commit 7f1b160e4e74d3abb19710ee52e62bd49ea8e5c5
Author: J J <wow@github.com>
Date: Mon Jul 29 20:53:24 2019 +0800
init
小结
好了,这里介绍了git rebase命令的基本用法,大家可以在平时多使用,来让自己的git时间线更加的简洁。
好了,不知道大家有没有看过《🐒🐒🐒🐒 🐒🐒🐒🐒 🐒🐒🐒🐒》?''