在rebase之后重新插入分支以正确提交

时间:2016-11-29 23:02:05

标签: git rebase

我启动了一个新的git存储库,在没有意识到的情况下写了很多提交给master。

a --> b --> c --> d (master)
             \--> e (bar)

我意识到我想要一个简单的基线大师,所以我重新命名了我的分支并创建了一个孤儿大师,里面只有原始的自述文件。

git branch -m master foo
git checkout --orphan master
rm (everything but the readme)
git add -A
git commit -m "base"

然后我尝试将两个分支重新定位到它上面

git checkout foo
git rebase master
git checkout bar
git rebase master

然而,这产生的是:

o (master) --> a --> b --> c --> d (foo)
          \--> a --> b --> c --> e (bar)
事后看来,这有点明显,虽然不是那么整洁。现在给定此状态,如何将bar分支重新插入到最初分支的提交中,删除重复的提交? (所以它看起来像下面的树。)

o (master) --> a --> b --> c --> d (foo)
                            \--> e (bar)

关于如何避免这种情况的任何建议也将受到赞赏。

3 个答案:

答案 0 :(得分:0)

你是怎么来到这里的

首先,基本问题:git rebase通过复制提交。因此,它需要知道两件事:要复制的内容,以及放置副本的位置。 "在哪里放"更容易看到,特别是当我们像你一样绘制图形时(我在下面做)。

默认情况下git rebase表示这些内容的方式来自单个参数,the documentation调用上游。在您的情况下,上游参数为master

再次显示原始图表,按照我喜欢的格式重新绘制:

a <- b <- c <- d   <-- master
           \
            -- e   <-- bar

请注意,箭头都是向后的。这是有目的的,因为Git坚持做一切倒退。 :-)这在某种程度上确实很重要,但通常我只是完全忽略互连箭头,因为它们比使用更烦人。最重要的部分是分支名称指向分支提示,即最近的提交。

现在,一旦您制作了(孤儿)母版,并重命名旧版<{1}},图片就会如下所示:

foo

您现在在分支o <-- master a--b--c--d <-- foo (HEAD) \ e <-- bar 上运行git rebase master。名称foo告诉rebase 在哪里复制(在master标识的提交之后),以及......

要复制的内容部分更难以看到。参数master实际上被视为 要复制。 Git将复制从当前分支可以访问的所有提交,即master,这些提交 不能从名称foo访问。这意味着提交master有资格进行复制, 1 并且a--b--c--d被排除在外。复制后,o移动标签以指向最终复制的提交。因此,一旦复制完成,我们就得到:

git rebase

因此,既然您已了解o <-- master \ a'-b'-c'-d' <-- foo (HEAD) a--b--c--d [abandoned] \ e <-- bar 如何选择要复制的内容,请将git rebase移至{{1},将其应用于新图片并且正在做HEAD。什么是符合条件的提交?从bar开始,然后回到左边,直到您用完为止,或者点击提交git rebase master或通过从e向左移动提交提交,然后获得o。这是o考虑复制的内容,以及复制它们的地方是&#34;就在a--b--c--e&#34;之后,我们会得到这个:

git rebase

这就是你得到你所拥有的东西的原因。

1 我说&#34;符合条件的&#34;这只是因为他们仍然可以被排除在外,但在这种情况下,他们并不是:他们来自符合条件的&#34;选举&#34;当选&#34;每一次。

如何解决

鉴于rebase副本提交,我们需要的是复制o a''-b''-c''-e' <-- bar / o <-- master \ a'-b'-c'-d' <-- foo a--b--c--d [abandoned] \ e [abandoned] - 我们复制哪一个并不重要,因为它们都是相同的,除了他们的ID - 以便新副本在 e之后。也就是说,我们想要的是:

e'

由于只有一个这样的提交,最简单的方法可能就是创建一个新的c'分支标签,指向o <-- master \ a'-b'-c'-d' <-- foo \ e'' <-- bar ,然后bar c'或{ {1}}。最容易命名的是git cherry-pick,因为现有e指向它:

e'

我们也可以在这里使用e',如果有更多的提交需要复制,那将是可行的方法,但让我们继续......

如何避免这个原来的

真的,有点痛苦。问题是bar仅适用于一个分支,即当前分支。 2 要做你想做的事,你必须从放置副本的位置中分离要复制的内容。您可以通过向git branch -m bar oopsbar # move the name out of the way git checkout -b bar foo^ # or foo~1, to name commit `c'` git cherry-pick oopsbar # copy e' to e'' 命令添加git rebase,然后更改上游参数来实现此目的:

git rebase

上游参数(此处为--onto)告诉git rebase 不要复制的内容。我们事先知道有一个提交,所以我们可以使用git branch -m master foo # as before ... and the same stuff to create the new master ... # but this time: git checkout foo git rebase master # copy a-b-c-d git checkout bar # and we already know there's one commit git rebase --onto master bar~1 # copy bar~1..bar 来识别第一个&#34;停止&#34;点。如果我们不知道有多少会怎么样?

这里一般来说非常困难。我们必须找到一个合适的&#34;停车标志&#34;。这是一个符合标志的适用于此案例的,即使用bar~1&#39> reflog

git rebase

因为bar~1指向原始提交foo(请查看完整的图纸),然后将git rebase --onto master foo@{1} 点提交回提交foo@{1},这是正确的停止登录。

如果我们有更多的分支要复制,可能没有一个符号名称可以找到正确的&#34;停止点&#34;。 Git中没有内置的解决方案,除了手动识别正确的停止点提交并将其作为原始哈希ID提供:

d

或其他什么。那很难看; Git需要某种&#34; multirebase&#34;命令,为您计算出这些点,并运行多个d命令,并允许您中止&#34;外部多重基础&#34;如果需要的话。但是写这样的命令很难。

2 您可以为c提供分支名称。它检查该分支,然后继续执行当前分支上的所有常用内容。所以它可能看起来像它可以在其他分支上运行,但它是一个廉价的黑客,只适用于一个分支,它真的,无论如何通过运行git rebase --onto master 7f3c9a82201ca9 来实现它 - 你可以自己做。 (Rebase有多种版本,它们都是shell脚本,由一个初始脚本驱动,如果被问到git rebase。)

答案 1 :(得分:0)

我能想到的最简单的解决方案就是在foo的历史记录中将bar交互式地-i重新绑定到c并删除重复的提交。

答案 2 :(得分:0)

$ git checkout foo                       # HEAD is now on d commit
$ git checkout -b bar-1                  # Checkout to new branch bar-1 with all commits (HEAD on d commit)
$ git reset --hard HEAD~1                # undo last commit (HEAD on c commit)

$ git checkout --orphan bar-clean        # checkout new bar-clean branch cleaning all commit history (HEAD on c commit)

$ git reflog                             # copy the commit-sha of e commit
$ git cherry-pick <e-commit-sha>         # take e commit, now HEAD is on e commit

$ git branch -D bar                      # delete previous bar branch
$ git checkout -b bar                    # create & checkout to new bar branch with only e commit
$ git branch -D bar-clean bar-1          # delete bar-clean & bar-1 branch
  • 完成。现在,您的图表就像您想要的那样。

    o (master) --> a --> b --> c --> d (foo)
                                \--> e (bar)