我启动了一个新的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)
关于如何避免这种情况的任何建议也将受到赞赏。
答案 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)