git rebasing无法正常工作?

Ken*_*son 3 git mercurial rebase atlassian-sourcetree

基于对重新基准的理解以及我对Mercurial中的重新基准工作的了解,Git的重新基准似乎没有按照我期望的方式工作。我生成了一个示例来说明这种奇怪的行为,并希望有人可以解释为什么Git的行为方式如此。考虑DAG的这种状态:

起始状态

在这种情况下,我已经在master上进行了提交f7和f8,但是我想将这些新提交移到feature分支上。即我在错误的分支上做了提交,并且想纠正错误。我可以从SourceTree执行变基:

选择变基

SourceTree证实了我的意图:

变基确认

但是结果根本不是我所期望的:

重新计算结果

尽管节点在DAG中的位置正确,但分支头不正确!通过将f7和f8重新设置到f6上,我希望看到主服务器重置为其原点位置,并希望Feature / AAA-1升级到f8。像这样:

调整基准

这是我期望的行为,基于Mercurial中的重新基准,并且通常只是基于重新基准正在执行的操作。为什么Git会这样做,如何使它正常运行?

tor*_*rek 5

来自Mercurial,您期望一个提交永久附加到特定分支。也就是说,如果您设法隔离地提取某些提交,则您会看到以下内容:

I am a commit on branch foo.
I change file bar.
Run Code Online (Sandbox Code Playgroud)

Git不能这样工作:提交独立于任何分支(名称),实际上,分支名称(如果有的话,标签)可以被剥离并粘在其他地方。除了对试图解释混乱的人来说,它们没有用1

在Mercurial中,当您“重新设置”某些变更集时,您(至少实际上)将它们作为差异与它们的基础进行比较,然后您切换到想要它们的另一个分支并对该另一个分支进行新的提交。Mercurial曾经(也许现在仍然)将这一第一步称为“嫁接”。这些新的提交现在永久地附加到(并且仅附加到)另一个其他分支:

master:    f1 - f2 - f3 - f4 - f7 - f8
                             \
feature/AAA-1:                 f5 - f6
Run Code Online (Sandbox Code Playgroud)

变成:

master:    f1 - f2 - f3 - f4 - f7 - f8
                             \
feature/AAA-1:                 f5 - f6 - 9 - 10
Run Code Online (Sandbox Code Playgroud)

此时,您可以“安全地撤消” f7和f8,使它们脱机master,并且仅在另一个分支上完成复制后,您的重新配置才完成。

请注意,我在此处在左侧绘制了分支标签。这是安全的,因为所有提交都永久固定在其分支上,因此,一旦变更集位于其分支的行上,它就始终位于其分支的行上。唯一一次违反“变更集在其分支的(单条)行上”规则的情况是,当变更集附加到(恰好)两个分支时:合并位于其主分支上,但绘制连接到另一个分支。

另一方面,在git中,提交可以被视为“在” 零个或多个分支上(不存在“恰好是1或2”约束),并且一个提交在“ on”上的分支集作为分支名称是动态的。可以随时添加或删除。(另请注意,单词“ branch” 在git中至少具有两个含义。)

Git的rebase与Mercurial的rebase非常相似:它实际上复制了提交。但是要开始有一个重要的区别:副本不是专门“放在”任何分支上(实际上,使用git称为“分离的HEAD”,rebase进程不会任何分支上运行)。然后,最后还有一个更重要的区别。

和以前一样,我们可以开始绘制图形,但是这次我将以不同的方式绘制它:

                     f7 <- f8   <-- master
                    /
f1 <- f2 <- f3 <- f4
                    \
                     f5 <- f6   <-- feature/AAA-1
Run Code Online (Sandbox Code Playgroud)

这次,标签在右侧,带有箭头。该名称master实际上直接指向commit f8,它f8是指向back f7f7指向back f4等。

这是什么意思的是,现在,提交f1通过f4的“关于” 两个分支。使用git时,最好将这些提交“包含在”两个分支(的历史)中。这些承诺中没有任何内容可以说它们最初是在哪个分支上创建的:它们带有其父指针,源树ID,作者和提交者名称(以及时间戳等),但没有“源分支名称”。(我认为从hg到git的新手经常会觉得这很令人沮丧。)

如果你现在问git的变基f7f8feature/AAA-1,git会做出两次提交的复印件:

                     f7 <- f8
                    /
f1 <- f2 <- f3 <- f4
                    \
                     f5 <- f6 <- f7' <- f8'
Run Code Online (Sandbox Code Playgroud)

'标记,或f7prime和f8prime,表示这些是原始副本git cherry-picks,类似于hg的嫁接)。但是现在我们要解决一个关键的区别,那就是让您绊倒的那个:git现在可以“剥离”原始master标签,并使其指向最尖端的新提交。这意味着最终图形如下所示:

                     f7 <- f8   [abandoned -- was master]
                    /
f1 <- f2 <- f3 <- f4
                    \
                     f5 <- f6   <-- feature/AAA-1
                             \
                              f7' <- f8'   <-- master
Run Code Online (Sandbox Code Playgroud)

Mercurial 不能做到这一点:分支标签不能被剥落,乱洗和重新粘贴到其他地方。事实并非如此,这就是其改组工作原理不同的原因。

在git中,您要做的只是简单地将两个提交樱桃拾取到feature/AAA-1分支中,然后将其从master分支中删除:

$ git checkout feature/AAA-1
$ git cherry-pick master~2..master   # copy the commits
$ git checkout master
$ git reset --hard master~2          # back up over the originals
Run Code Online (Sandbox Code Playgroud)

这里的想法是,您根本不master进行基础调整,甚至也没有真正地对功能分支进行基础调整:相反,您只是将两个提交复制到功能分支中,然后将它们从master删除。


1这有点夸大其词,因为存储库之间的转移-git fetchgit push-也使用分支和标签标签。另外,您需要一些引用以使提交保持活动状态,否则git的垃圾回收器最终将它们收为“无法访问”。