【问题标题】:How to merge two branches without a common ancestor?如何合并没有共同祖先的两个分支?
【发布时间】:2009-09-28 18:56:55
【问题描述】:

我在项目中间开始使用 Git,前两次提交只是一些初始设置(.gitignore 和 .gitattributes),第三次提交 M2 添加了SVN 中继:

I1 -- I2 -- M2 -- N -- .. -- Z

我在一个名为svn的分支中导入了SVN历史,其中M1是SVN主干(与M2内容相同, .gitignore 和 .gitattributes 除外):

A -- B -- ... -- K -- L -- M1

问:合并两个分支的最佳方法是什么?

我可以将M1M2合并成M3,然后rebase,但是我不知道如何删除 I1I2 提交,如果我可以安全地删除 M3 提交(我找到了一些保留合并提交的建议,但在这种情况下 M3 不再需要了)。

A -- B -- ... -- K -- L -- M1
                             \
                              M3 -- N' -- .. -- Z'
                             /
               I1 -- I2 -- M2 -- N -- .. -- Z

另一种方法是手动挑选 N .. Z 提交到 svn 分支,但我想避免这种方法。

最优雅的解决方案是重新设置由 N 引入的更改 .. Zsvn 分支之上提交,但我没有'还没有找到没有共同祖先的两个分支所需的语法。

【问题讨论】:

    标签: git merge rebase


    【解决方案1】:

    我会做以下事情:

    git checkout M1
    git cherry-pick I1
    git cherry-pick I2
    

    这会将 .gitignore 和 .gitattributes 添加到包含更好历史记录的分支中。

    然后在那个之上设置新的提交:

    git filter-branch --parent-filter 'if $GIT_COMMIT = $hash_of_N; then printf -- '-p
    $hash_of_cherrypicked_I2\n'; else cat; fi'
    

    这样做的缺点是你重写了历史。

    所以另一种方法是创建一个类似于the one for the Linux kernel 的脚本 并将其放入您的存储库中。

    【讨论】:

      【解决方案2】:

      免责声明我自己只在玩具存储库中使用过一次“移植点”。但这是一个您可能没有听说过的晦涩功能,并且_可能_对您的情况有所帮助。

      您可以使用“嫁接点”来伪造祖先信息。请参阅,例如,What are .git/info/grafts for? 或立即前往the git wiki entry on graft points

      本质上,你会创建一个文件.git/info/grafts,让 git 认为提交 M1 是提交 M2 的祖先:

      $ cat .git/info/grafts
      <your M2 commit hash> <your M1 commit hash>
      

      随后,看起来 M2 是一个空提交,只是将 I2M1 合并到一个公共树中。

      主要缺点:嫁接点没有提交;因此,它没有被检出,但需要手动添加到存储库的每个本地工作副本中。



      更新: 改用git replace --graft

      如上所述,移植点已被取代。运行

      git replace --graft <your M2 commit hash> <your M1 commit hash>
      

      创建嫁接。这存储在.git/refs/replace/ 中。尽管 git 默认不会获取或推送这些 ref,但可以使用以下方式在存储库之间同步它们:

      git push origin 'refs/replace/*'
      git fetch origin 'refs/replace/*:refs/replace/*'
      

      (StackOverflow: How to push 'refs/replace' without pushing any other refs in git?)

      【讨论】:

      • 您可以使用“git filter-branch”根据嫁接来改写历史,将嫁接亲子变成真实亲子。但这改写了历史。
      • @Jakub:您能否举一个使用带有移植物的过滤器分支的示例(作为能够投票的答案)?我可以重写历史。
      • IIRC,在移植文件中,您需要提及当前提交及其父级的 all。所以在一行上会有 M2、M1 和 I2 的哈希值——除非你想摆脱提交 I1 和 I2。通过在移植行中只有 M2 和 M1,看起来 M2 将 I1 和 I2 的内容提交到 M1 之上。
      • 值得注意的是,嫁接已被“git replace”取代,作为伪造共同祖先的一种方式。
      • @stsquad 和 git replace 实际上 提交到存储库。或者至少可以。
      【解决方案3】:

      最优雅的解决方案是将 N .. Z 引入的更改重新设置在 svn 分支之上,但我还没有找到没有共同祖先的两个分支所需的语法。

      尝试首先将 I1 和 I2 挑选到 M1 上,然后使用命令 git rebase --onto M1' M2 Z(其中 M1' 是 M1-I1-I2 分支)。我不确定 rebase --onto 在没有共同祖先的情况下是否有效,但如果没有,则有可能使用补丁。使用git format-patch 生成 M2..Z 的补丁,然后使用git am 将它们应用到 M1 之上。 Here are some experience reports 使用它来转换旧的 SVN 和 CVS 存储库。

      【讨论】:

        猜你喜欢
        • 2023-03-20
        • 2014-03-19
        • 1970-01-01
        • 2012-08-23
        • 1970-01-01
        • 2014-12-28
        • 1970-01-01
        • 2020-02-21
        • 2015-02-01
        相关资源
        最近更新 更多