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

Bla*_*tor 2 git merge branch rebase

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

A - B - F - G - H - I - J  (branch working)
    \
      C - D - E            (old branch)
Run Code Online (Sandbox Code Playgroud)

变成...

A - B - C - D - E - F - G - H - I - J (continue with branch working)
Run Code Online (Sandbox Code Playgroud)

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

tor*_*rek 5

您实际上不能这样做,因为这将涉及更改某些现有的提交。在这种情况下,现有提交将其哈希 IDF存储B为其父提交。任何现有的提交都不能更改:提交完全是只读的(事实上,Git 的所有内部对象都是只读的)。

\n\n

我假设您想假装F存储E\ 的哈希 ID 作为其父代,而不更改与任何现有提交关联的快照。你可以做到这一点,并且你可以做许多类似的事情。不过,在我们讨论这些之前,让我们先看一下git rebase.

\n\n

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

\n\n
    \n
  • 将提交的快照与提交的父级快照进行比较,例如JvsIFvs B。您可以通过跑步自己完成此操作git diff <hash1> <hash2>,或者更简单地通过git show <hash2>.

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

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

  • \n
\n\n

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

\n\n

合并步骤,加上 Git 将快照(提交)转换为变更集(差异)的事实,意味着最终精心挑选的结果可能是与原始快照截然不同的快照。这一切都取决于您开始该过程时<hash1>的情况有多么不同HEAD

\n\n

git rebase命令本质上自动执行一次挑选一系列提交的过程。在所有复制结束时,git rebase强制分支标签指向最终复制的提交。这将允许您复制到父级为 的F新对象,然后复制到父级为 的新对象,依此类推:F\'EGG\'F\'

\n\n
A--B--F--G--H--I--J   <-- (original)\n    \\\n     C--D--E   <-- (target of rebase)\n            \\\n             F\'-G\'-H\'-I\'-J\'   <-- branch\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

1有时git rebase从字面上运行git cherry-pick,有时它使用通常应该产生相同结果的方法,但在一些奇怪的极端情况下,却不会。

\n\n
\n\n

现在,假设我们不再做这一切,而是创建一个 Git 对象,每当要使用 .Git 时,Git 都可以“旁观”该对象F。这个替换F进入图表如下:

\n\n
A--B--F--G--H--I--J   <-- branch\n    \\\n     C--D--E   <-- some_name\n            \\\n             Frepl   <-- refs/replace/<hash>\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

F现在,我们只需要 Git在每次因任何原因Frepl要使用提交时“把目光转向” 。F有一种方法可以做到这一点:我们给出Frepl一个特殊的名称,其中是 commit 的实际哈希 ID 。refs/replace/big-ugly-hash-idbig-ugly-hash-idF

\n\n

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

\n\n

替换对象的最大缺点是git clone默认情况下不会复制替换对象(它不会获取它们,也不会获取它们的名称)。这意味着克隆人看不到替代者,也永远不会将其放在一边。您可以显式地将替换添加到您的 fetch refspec 中以获取它们,但这有点痛苦。默认情况下,这些git push操作也不传输它们。

\n\n

最大的优点是它们不需要每个人都停止使用原始提交而转而使用新的和改进的提交。不过,如果您可以让每个人都进行切换,则可以使用git rebase复制许多提交并移动分支标签。或者,您可以git replace最初使用来创建替换对象,然后git filter-branch不使用过滤器运行,但告诉它过滤发生替换的分支。

\n\n

所做git filter-branch的就是复制每个提交(在它被告知要过滤的一个或多个分支上)。至少在逻辑上是\xe2\x80\x94;有很多优化\xe2\x80\x94提取每个提交,从最旧的提交到最新的提交到临时目录,按某种顺序应用每个过滤器,然后使用过滤器所做的任何操作进行新的提交。如果新提交与原始提交逐位相同,则这两个提交实际上只是一个提交,否则副本是一个新的且不同的提交。每个新副本的默认父级是在父级的早期副本中所做的提交(尽管有一个--parent-filter可以让您更改它!)。它通过发生替换后备花招来完成所有这一切,2因此,当 Git 去复制时F,它实际上会复制Frepl,然后返回FreplE。如果我们有这个过滤器 name branch,结果是 Git 复制A, then B, then C, then D, Ethen Frepl, then G, then , then H, then I, and then J,给出:

\n\n
A--B--F--G--H--I--J   <-- refs/original/refs/heads/branch\n    \\\n     C--D--E   <-- some_name\n            \\\n             Frepl   <-- refs/replace/<hash>\n                \\\n                 G\'-H\'-I\'-J\'   <-- branch\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,branch指向 commit J\',其父级是I\'但其树(快照)与 的树(快照)相同J。CommitI\'指向H\',它又指向G\',它又指向Frepl(我们运行时创建的副本git replace:它在过滤期间保持不变)。这指向回E(这是它自己未更改的副本),它指向回D,依此类推回回A

\n\n

那么,如果我们扔掉所有refs/original/像 那样的名称refs/original/refs/heads/branch,最终的效果就是“巩固”任何替换提交。然后我们可以删除其refs/replace/名称Frepl,看起来好像我们只A-B-C-D-E-Frepl-G\'-H\'-I\'-J\'在存储库中进行了提交。许多提交的哈希ID已更改,因此此存储库不再与原始存储库兼容;但如果我们从名称开始branch,我们只能在所有正确的位置看到闪亮的新复制提交。

\n\n
\n\n

2当然,如果您运行git --no-replace-objects filter-branch,则会禁用替换后备。可能永远没有任何理由这样做。

\n