(译)第三部分:什么是rebase?

内容提要

第一部分中,我们讨论了什么是commit hash,其中一个很重要的特点就是commit无法被修改。hash值是根据存储在commit中的信息生成的,所以修改一个commit或者commit hash,你必须要创建一个全新的commit。我们还讨论了每一个commit存储了它的前一个commit的hash值。我们所没有讨论的是它对我们Git历史的影响。

实际上,commit hash是基于他们本身存储的信息生成的,而这些信息其中就包含了前一个commit的hash值,所以想修改你的commit历史基本上是不可能的。每一个commit就像是链条上的一环,紧紧连接着上一环。

如果你有如上图一样的一条金属链,在不打断他们的前提下是不可能把前一环和后一环连接起来的。然而,在Git环境中这将会更糟。这样的类比在这里是不靠谱的,因为在一条金属链上你可以焊接一个新环来把前一环和后一环重新连接起来。但是在Git中,你无法做到这一点。

如果你想要在commit历史的中间删除某一个commit,那后一个commit将会指向一个不存在的commit hash。因为你无法在不改变hash的情况下来修改commit,所以你不能简单地生成一个新的commit来引用前一个commit,而后一个commit仍然引用了最原始的commit hash。

如果你改变了一个commit的某个属性,那生成的hash值将不再一样,后一个commit也不会引用到新的commit。结果就是你不得不去修改后一个commit来引用到新的commit hash,这同样会引起commit hash的改变,就这样一路下去直到链条的末尾。

这时候轮到rebase上场了。如果你还记得第二部分,当我们将feature1分支合并到master分支后,有一副图展示了各个commit之间的关系。

Merge可以很好地工作,但是伴随着所有的fork和横纵交叉的commit关系,Git仓库的图形很快就会失控。下图只是一个我平时工作的Git仓库的小片段。

如果你使用一个Git GUI软件,很有可能你也见识过类似的东西。Merge是在不同分支之间移动差异的最简单的方式,因为它避免了破坏commit历史和所引发的蛋疼。然而,一旦你对rebase的工作方式有了比较深刻的理解,你将会从中收益。举个栗子,如果我们在demo仓库中rebase feature1分支到master分支(译者注:这句话的意思是切换到feature1分支,执行git rebase master命令),将会得到一个非常漂亮干净的历史,如图:

注意到没?现在的历史是一条直线了。Git到底是怎么做到的呢?如果你还记得的话,我们的Commit 3Commit 4是共享Commit 2作为其共同父节点的,Commit 3引用了Commit 2作为其前一个commit。现在你也许会疑惑为什么看起来Commit 3像是将Commit 4作为其前一个commit。

还记得我刚说过的,如果想从中间打断链条,你必须从这个点上开始重现创建其之后的commit,直到结尾。没错,这实际上就是rebase做的事情。

仔细看的话,你会发现Commit 3Commit 5Commit 6的commit hash已经全部改变了。这3个commit是在feature 1分支上提交的。通过将feature 1分支rebase操作到master分支上,从master分支分叉出来的的第一个commit开始,git重写了feature 1所有的commit,直到结束。它将分之上的每一个commit之间的差异存储在一个临时文件中,然后开始重写我们的分支历史。而这一次,分支是从masterCommit 4开始的。

Git给分支上的每一个commit创建了一个新的commit,当然跟着修改的还有commit hash值。当它创建新的commit的时候,第一个commit被改为引用到master分支的最新的commit(Commit 4),而不是原来的(Commit 2)了。这个重新提交你的变更作为新的commit的流程被称为“你的commits在master分支上的重播”。

注意:不要让术语混淆。Rebase到master分支不会修改master分支本身,它的意思是你的分支commits将会紧跟着master分支上最新的commits(译者注:这里的你的分支指的是feature 1)。

你会注意到上图中master分支仍然指向Commit 4,它的commit hash值是没有改变的。如果我们现在切换到master分支,然后把feature 1分支合并到master分支,这将不会产生一个合并commit。这仅仅是一个快进提交,意思就是git将会简单地将指向master分支的指针笔直地移到指向feature 1分支的指针位置上。

如果不把feature 1分支合并到master分支,我们还有更多的事要做,更多的commit要提交,我们可能会再fork一个仓库。我们的下一个master分支的commit将会指向Commit 4作为它的父节点,而feature 1分支的第一个commit也是指向Commit 4作为它的父节点。为了得到一条笔直的提交历史,我们需要再切换到feature1分支,然后再次rebase到master分支。这种情况很常出现,比如你在github上提交了一个pull request然后它过期了。如果项目的维护者没有合并你 的pull request,而是在这个项目上继续做一些其他工作,那么你的pull request就需要再来一次rebase操作以获取一个干净的git历史。把你做的工作rebase到原仓库分支上才可以让这个pull request能够在合并进去的时候采用简单的快进方式。接受一个pull request只是一个简单的合并。如果在提交pull request之前就rebase了你做的工作,那么这个merge就是一个快进方式的merge,这也能保证原仓库的干净。(译者注:这里的干净指的是没有额外的合并信息。)

危险!!!

这部分内容是对rebase的使用进行一些警告,主要还是在多人协作上需要注意。因为rebase是一种改写commit的操作,所以相对比较危险,作者给出的意见是:

Undoing a rebase is not easy, and often impossible so you really need to pay attention to what you’re doing. The benefits of rebasing are great, but not if you don’t know what you’re doing.

撤销一个rebase操作不简单,而且经常是不可能的。你必须很注意自己在干什么。rebase让人受益,当时如果你不知道你在干什么的话,别用reabse。

这部分内容不翻译了,有兴趣自己看原文吧。

英文地址:http://codetunnel.com/merge-vs-rebase-part-3-what-is-a-rebase/

文章目录
,