有问题为什么不问问神奇的 man 呢?
rebase
也算是我比较常用的一个指令了,但是很长时间以来,对这个指令的认识还是不够深刻,于是就找了个时间认真地读了一下 git rebase 的文档。这份文档不需要在网络上查找,在自己的电脑上直接使用 man git-rebase
就可以查看了。在这份文档中,被提到的几个重要的 rebase 参数就是 newbase
、upstream
、branch
。除此之外,-i
也是一个能够极大的提升使用体验的选项,允许我们交互式的选取需要操作的提交。
git rebase [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]
[<upstream> [<branch>]]
三个参数
在执行 git rebase 的之前,如果我们指定了 branch
这个参数,那么 git 会在开始 rebase 操作之前签出到 branch
。
接着,git 会把所有在当前分支但不在 upstream
分支的提交保存到一个临时区域。如果想要在 rebase 开始之前了解哪些提交会被移到临时区域,可以使用 git log <upstream>..<branch>
查看。
然后,如果我们设置了 newbase
,那么 git 会把当前分支重置到 newbase
,否则,就重置到 upstream
,这里的效果就相当于 git reset <newbase>
。
接下来,git 会将之前存放到临时区域内的提交逐个按顺序地重新作用到当前分支上。
根据上面介绍的执行过程,我们可以总结出以下几个知识点:
-
branch 分支或者执行 git rebase 的当前分支,最终会被移动到 upstream 或者 newbase 分支上
-
branch 与 upstream 构成了一个选择器,可以通过这两个参数来把 branch..upstream 之间的提交移动跟随 branch 移动到新的地方
这样看来,rebase 做的事其实是使用 upstream
以及 branch
选取一段提交,然后在把这段提交从原先的分支上剪下来,然后嫁接到 upstream
或者 newbase
上去。
接着结合一个简单的例子来熟悉一下 rebase 中这三个参数的作用,例如,我们有这样一个仓库:
A---B<start>---C---D<feature><HEAD>
/
E---F---G<master>
我们目前位于 feature 分支,现在我们需要使用 rebase 将 feature 分支上的 C、D 两个提交移植到 master 上面。因为最终的目的地是 master 分支,所以可以确定 newbase 参数的值是 master;为了选中 C、D 两个提交,我们可以使用 feature 分支作为 branch 参数,使用 start 分支作为 upstream 参数。最终,我们需要执行的指令就是:
git rebase --onto master start feature
因为,我们目前就在 feature 上面,所以 branch 参数可以省略掉:
git rebase --onto master start
最终我们得到的结果如下所示:
C'---D'<feature><HEAD>
/
E---F---G
A---B<start>
交互式操作后可能会遇到的问题
在交互式 rebase 中,我们可以选取需要操作的提交,但这种可选择的操作有时候可能会给 git 新手造成一种令人慌乱的局面。例如,我们有这样一个仓库:
A---B---C---D<feature><HEAD>
/
D---E---F<master>
然后,我们执行下面的命令:
git rebase -i master
假设只有 B、C 被选取了,那么这个仓库接下来就会变成:
B'---C'<feature>
/
D---E---F<master>
现在,你可能已经发现了一个问题,A 与 D 这两个提交“消失”了,这时候先不要慌,可以先想想办法研究出时光机器回到过去阻止自己。当然,这只是一个玩笑。这两个提交并没有真正的消失,他们只是没有被某个分支引用而已。要让它们重新回到分支树上,我们需要先查询这两个提交的 hash :
git reflog feature
reflog
可以帮助我们查看指定分支的操作历史,包括 commit、rebase 等操作。
查询到 hash 之后,只要在这些被历史遗忘的 commit 上创建新的分支,我们就可以在 git log
中重新见到他们了。