Git时光穿梭--git rebase用法详解

2,858 阅读8分钟

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。

使用完之后,就会进入我们命令行编辑模式了 hello 2019-07-29 at 9 26 08 PM

这里,我们可以看到我们的最近两次提交的信息,每条信息由三个部分组成:command + commit id + commit message。

然后下面列举了各种可选的command,我们就是通过修改command来执行rebase操作的。现在默认状态是pick,它其实什么都没做,如果我们现在退出,等于我们没有做任何修改。

修改commit message

现在我发现之前的commit message写的不好,我希望修改它。这其实是很常见的需求,我们可以怎么做呢?

首先我们还是进入这个交互式界面

$ git rebase -i HEAD~2

然后我们只需要把pick这个单词修改为r或者reword就行了。这个举动表示我们希望修改这个commit的commit message。

hello 2019-07-29 at 9 32 25 PM

修改完之后,我们直接保存退出就行了。保存退出后我们会立刻进入一个新的交互式界面,这个界面是让我们为这次提交编写commit message hello 2019-07-29 at 9 32 36 PM

这里,我们只要修改一下,把commit message 修改成任何我们想要的信息就行了 hello 2019-07-29 at 9 32 50 PM

然后我们再来看一下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

进入交互式界面后,原来是这样的 hello 2019-07-29 at 10 35 10 PM

现在给它们调换顺序 hello 2019-07-29 at 10 35 43 PM

保存,退出,就行了

$ 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

看到这个界面 hello 2019-07-29 at 10 44 39 PM

然后把对应的commit 的command修改成s,意思就是把当前的commit和它之前的合并在一起 hello 2019-07-29 at 10 44 49 PM

然后保存、退出就行了。退出后会立刻进入一个新的交互式界面,在这里你可为合并后的commit添加新的commit message。 hello 2019-07-29 at 10 45 11 PM

$ 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

原来是这样的 hello 2019-07-29 at 11 04 03 PM

修改成这样的 hello 2019-07-29 at 11 04 33 PM

保存退出就行了

$ 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时间线更加的简洁。

欢迎关注 github.com/fish-stack/…

好了,不知道大家有没有看过《🐒🐒🐒🐒 🐒🐒🐒🐒 🐒🐒🐒🐒》?''