Git:将现有提交从一个分支插入另一个分支的历史记录

时间:2017-12-14 22:37:23

标签: git merge branch rebase

我想从一个分支中插入几个提交到另一个分支的历史记录中,以便:

A - B - F - G - H - I - J  (branch working)
    \
      C - D - E            (old branch)

...变为

A - B - C - D - E - F - G - H - I - J (continue with branch working)

到目前为止我尝试过的任何事情导致多次冲突阻止我继续,但只要后续提交的状态相同,我就不关心它们。提交F的问题是“不知道”如何成为提交E的孩子?

1 个答案:

答案 0 :(得分:4)

您实际上无法 ,因为这将涉及更改某些现有提交。在这种情况下,现有提交FB的哈希ID存储为其父级。不能改变现有的提交:提交完全是只读的(实际上,所有Git的内部对象都是只读的)。

我假设你想假装FE的哈希ID存储为其父代,而不更改与任何现有提交相关联的快照。你可以这个,你可以做很多类似的事情。不过,在我们看一下之前,让我们看一下git rebase

这里使用git rebase的问题在于,复制提交的rebase函数(到目前为止一切都很好),每个副本都像 1 使用git cherry-pick(这是事情开始出错的地方)。为了挑选一个提交,Git将:

  • 比较提交的快照与提交的父级快照,例如J vs IF vs { {1}}。您可以自行运行B,或者更简单地通过git diff <hash1> <hash2>运行。

  • 将此处显示的差异与父项的差异(上面的git show <hash2>)合并到当前或<hash1>提交,并使用合并结果进行新的提交。

  • 将原始提交的消息复制到新提交。

新提交一如既往地进入当前分支,使分支提交更长,并更改HEAD提交以命名新提交。

合并步骤,加上Git将快照(提交)转换为变更集(差异)的事实,意味着与原始结果相比,最终挑选的结果可能是一个非常不同的快照。这一切都取决于启动流程时HEAD<hash1>之间的差异。

HEAD命令基本上可以自动完成一次性提交一系列提交的过程。在所有复制结束时,git rebase强制分支标签指向最终复制的提交。这样,您就可以将git rebase复制到父F的新F',然后将E复制到父G的新G'等等:

F'

1 有时A--B--F--G--H--I--J <-- (original) \ C--D--E <-- (target of rebase) \ F'-G'-H'-I'-J' <-- branch 字面上运行git rebase,有时候它使用的方法通常会产生相同的结果,但在某些奇怪的角落情况下,并不是git cherry-pick。吨。

现在,假设不是所有这些,我们创建一个Git对象,Git可以&#34;放在一边&#34;无论什么时候使用F。这个替换为F的内容如下:

A--B--F--G--H--I--J   <-- branch
    \
     C--D--E   <-- some_name
            \
             Frepl   <-- refs/replace/<hash>

我们将F的提交消息复制到Frepl的提交消息,并复制大多数其他字段,包括提交树对象的内部Git哈希ID。但是,不是指向提交B,而是提交Frepl指向提交E

现在我们只需要Git来#34;转过眼睛&#34;从FFrepl每次出于任何原因即将使用提交F。还有一种方法可以做到这一点:我们为Frepl提供一个特殊名称refs/replace/big-ugly-hash-id,其中 big-ugly-hash-id 是提交{{1}的实际哈希ID }。

进行替换的Git命令是F。旁听是自动的:Git总是这样做,除非我们运行git replace。这一切都是在没有更改任何现有 Git对象的情况下完成的,所以它只是添加到存储库中。

替换对象的最大缺点是git --no-replace-objects默认情况下不会复制替换对象(它不会获取它们,也不会获取它们的名称)。这意味着克隆在视图中没有替换,并且永远不会忽略它。您可以明确地将替换添加到fetch refspecs以获取它们,但这有点痛苦。 git clone操作默认情况下不会转移它们。

最大的好处是,它们不要求每个人都停止使用原始提交,而是支持新的和改进的提交。但是,如果您可以让每个人都切换,您可以使用git push复制许多提交并移动分支标签。或者,您最初可以使用git rebase来创建替换对象,然后在没有过滤器的情况下运行git replace,但要告诉它过滤发生替换的分支。

git filter-branch做的是复制每次提交(在分支或分支上告知要过滤)。它 - 至少在逻辑上;有很多优化 - 提取每个提交,从最旧的提交到最新的提交,到一个临时目录,按某种顺序应用每个过滤器,然后使用过滤器所做的任何事情进行新的提交。如果新提交与原始提交一点一点地相同,则两个提交实际上只是一个提交,否则副本是一个新的不同的提交。每个新副本的默认父级是在父级的早期副本中进行的提交(尽管有一个git filter-branch可以让您更改它!)。它完成了所有这一切,发生了替换后备噱头, 2 所以当Git复制--parent-filter时,它实际上复制F,然后跟随Frepl回到Frepl。如果我们将此过滤器命名为E,则结果是Git会复制branch,然后是A,然后是B,然后是C,然后是{{1然后是D,然后是E,然后是Frepl,然后是G,然后是H,给出了:

I

请注意,J指向提交A--B--F--G--H--I--J <-- refs/original/refs/heads/branch \ C--D--E <-- some_name \ Frepl <-- refs/replace/<hash> \ G'-H'-I'-J' <-- branch ,其父级为branch但其树(快照)与J'的树(快照)相同。将I'点提交回J,返回I',返回H'(我们运行G'时复制的副本:在过滤期间保持不变)。这指向Frepl(这是它自己未更改的副本),它指向git replace,依此类推回E

然后,如果我们抛弃所有D名称(例如A),那么最后的效果就是&#34;水泥到位&#34;任何替换提交。然后,我们可以删除refs/original/的{​​{1}}名称,看起来好像我们在存储库中只有提交refs/original/refs/heads/branch。许多提交的哈希ID 已更改,因此此存储库不再与原始存储库兼容;但是如果我们从名称refs/replace/开始,我们只会在所有正确的位置看到闪亮的新复制提交。

2 当然,如果您运行Frepl,则会禁用替换旁视。可能没有任何理由这样做。