代码之家  ›  专栏  ›  技术社区  ›  Blazinator

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

  •  2
  • Blazinator  · 技术社区  · 7 年前

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

    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 回复  |  直到 7 年前
        1
  •  4
  •   torek    7 年前

    你真的做不到 那个 ,因为这将涉及更改某些现有提交。在本例中,现有提交 F 商店 B 的哈希ID作为其父项。任何现有的提交都不能更改:提交是完全只读的(事实上,Git的所有内部对象都是只读的)。

    我想你是想假装 F 商店 E 的哈希ID作为其父项,而不更改与任何现有提交关联的快照。你 可以 ,您可以做许多类似的事情。在我们研究这些之前,让我们先看看 git rebase .

    使用的问题 git再基 下面是rebase函数 正在复制 提交(到目前为止一切都很好),每一份副本都像 1. 通过使用 git cherry-pick (这就是事情开始出错的地方)。为了准确地选择一个提交,Git将:

    • 比较提交的快照与提交的父快照,例如。, J vs公司 I F vs公司 B . 你可以自己跑步 git diff <hash1> <hash2> ,或者更简单地说 git show <hash2> .

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

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

    像往常一样,新的提交会进入当前分支,使分支的提交时间更长,并更改 头部 提交以命名新提交。

    合并步骤,加上Git正在将快照(提交)转换为变更集(差异),意味着最终挑选的结果可能与原始快照截然不同。这一切都取决于有多不同 <哈希1> 不管是什么 头部 当您开始此过程时。

    这个 git再基 命令本质上自动化了一次挑选一系列提交的过程。在所有复制结束时, git再基 强制分支标签指向最终复制的提交。这将允许您复制 F 到一个新的 F' 谁的父母是 E ,然后复制 G 到一个新的 G' 谁的父母是 F' ,等等:

    A--B--F--G--H--I--J   <-- (original)
        \
         C--D--E   <-- (target of rebase)
                \
                 F'-G'-H'-I'-J'   <-- branch
    

    1. 有时 git再基 字面上的运行 吉特樱桃采摘 ,有时它使用的方法通常会产生相同的结果,但在某些奇怪的情况下,不会产生相同的结果。


    现在,假设我们不做这些,而是创建一个Git对象,Git可以在任何时候使用它时将其“放在一边” 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来“转眼” F Frepl公司 每次它都将与commit一起工作 F 出于任何原因。有一种方法可以做到这一点:我们给予 Frepl公司 一个特殊的名字, refs/replace/ big-ugly-hash-id 哪里 big-ugly-hash-id 是提交的实际哈希ID F .

    进行替换的Git命令是 git replace . 旁视是自动的:Git总是这样做,除非我们运行 git --no-replace-objects . 一切都结束了 没有 更改任何 现有的 Git对象,所以它只是添加到存储库中。

    替换对象的最大缺点是 git clone 默认情况下 没有 复制替换对象(它不提取它们,也不提取它们的名称)。这意味着克隆在视图中没有替换项,并且从不将其放在一边。您可以显式地将替换项添加到fetch refspec以获取它们,但这有点麻烦。这个 git push 默认情况下,操作也不会传输它们。

    最大的优点是,它们不要求每个人都停止使用原始的提交,转而使用新的和改进的提交。如果你 可以 但是,让每个人都切换,您可以使用 git再基 复制多个提交并移动分支标签。或者,您可以使用 git替换 首先创建替换对象,然后运行 git filter-branch 没有筛选器,但告诉它筛选发生替换的分支。

    什么 git筛选器分支 是否要复制 每一个 提交(在要求筛选的一个或多个分支上)。至少在逻辑上;有很多优化–提取每个提交,从最旧的提交到最新的提交,到临时目录,按一定顺序应用每个过滤器,然后使用过滤器所做的任何操作进行新的提交。如果新提交与原始提交完全相同,则两次提交实际上只是一次提交,否则副本是一次新的不同提交。每个新副本的默认父级是在父级的早期副本中进行的提交(尽管有 --parent-filter 让你改变它!)。所有这些都是通过替换lookaside的手法实现的, 2. 所以当Git去复制 F ,它实际上是复制的 Frepl公司 相反,然后 Frepl公司 返回到 E . 如果我们有这个过滤器名称 branch ,结果是Git复制 A 然后 B 然后 C 然后 D 然后 E 然后 Frepl公司 然后 G 然后 H 然后 ,然后 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
    

    请注意 树枝 要提交的点 J' ,其父级为 I' 但其树(快照)与 J . 犯罪 我是 点返回到 H' ,它指向 G' ,它指向 Frepl公司 (我们运行时制作的副本 git替换 :在过滤过程中保持不变)。这指向 E (这是它自己的未更改副本),它指向 D ,等等返回到 A. .

    如果我们把所有的 refs/original/ 名称如下 refs/original/refs/heads/branch ,即“水泥到位”任何替换承诺。然后我们可以删除 refs/replace/ 的名称 Frepl公司 看起来我们只有一次承诺 A-B-C-D-E-Frepl-G'-H'-I'-J' 在存储库中。这个 哈希ID 许多提交中的个已更改,因此此存储库不再与原始存储库兼容;但如果我们从名字开始 树枝 ,我们只能在所有正确的位置看到闪亮的新复制提交。


    2. 当然,如果你跑 git --no-replace-objects filter-branch ,这将禁用替换lookaside。可能从来没有任何理由这样做。