Skip to content

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

@bitfishxyz

Description

@bitfishxyz

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

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions