【问题标题】:git pull --rebase --preserve-merges rewrites merge commit with unmodified parentsgit pull --rebase --preserve-merges 用未修改的父项重写合并提交
【发布时间】:2015-05-12 02:27:03
【问题描述】:

考虑历史

   D  origin/master
  /
 | C  master
 | |\
 | | B
  \|/|
   A :
   |
   :

git pull --rebase --preserve-merges 之后,我希望历史会是

   C'  master
   |\
   D |
   | B
   |/|
   A :
   |
   :

但事实证明

   C'  master
   |\
   | B'
   |/|
   D :
   |
   A
   |
   :

换句话说,B 提交被重写为 B',即使不需要它,因为父母双方都未修改。我知道我正在重新定位到 D,但这是有问题的,因为 B 合并中​​的所有冲突都必须再次解决。

当前的功能有什么好处吗?

有没有办法获得我想要的功能?

【问题讨论】:

    标签: git git-merge git-rebase git-pull


    【解决方案1】:

    我无法真正回答“好处”问题,但我可以描述为什么 rebase 会按原样运行以及如何获得所需的结果。

    Rebase,无论是否交互,都接受“三个类似分支或提交”的参数,它称之为 newbaseupstreambranch:

    git rebase [-i | --interactive] [options] [--exec <em>cmd</em>] [--onto <em>newbase</em>]
                         [upstream [branch]]

    如果您将其中的部分或全部排除在外,rebase 仍会使用它们,它只是自行找到它们:

    • branch 默认为当前分支,或HEAD(包括分离的头部)。
    • upstream 默认为当前分支的上游,由git branch --set-upstream-to 或类似设置。无论您在此处指定什么或默认值,都会将其提供给git rev-list,以使其停止接受提交,即,将被重新定位的提交是由git rev-list <em>upstream</em>..HEAD 打印的提交。
    • newbase 默认与 upstream 具有相同的提交 ID。

    (如果你在一个足够新的 git 中提供 --fork-point 以拥有 --fork-point,这些都会有所修改,但这里的一般想法仍然适用:要重新设置的提交是根据上游的“之后”选择的,向上到并包括 HEAD,默认情况下。)

    当您让git pull 为您运行git rebase 时,它会提供,作为upstream,当前分支的实际上游(同样,在较新的 gits 中由 fork-point 计算修改,但这应该没有如果您从中获取的实际上游没有完成自己的变基,则效果)。 (--onto 通常也与上游相同。在这种情况下,它等于git rebase [options] --onto origin/master origin/master master。)

    如您所见(通过运行git rev-list origin/master..master),这意味着“请将BC 重新设置为D”。添加--preserve-merges 只需使用交互式机制并根据请求保留合并,在根据请求重新设置两个提交之后。

    如果你只是简单地运行git fetch,你将像往常一样获得提交D,并将你绘制的图形作为第一个:

       D  origin/master
      /
     | C  master
     | |\
     | | B
      \|/|
       A :
       |
       :
    

    您现在可以尝试手动运行git rebase -p 以变基提交C,将B 指定为其“上游”,以便变基不会复制提交B,并指定D 作为 --onto 提交,以便 C 重新基于 D

    $ git rebase -p --onto origin/master master^2 master
    

    不幸的是,这会产生一个新的合并提交,其两个父级是origin/master(提交D)和原始master^(提交A)。 (这不是很明显的原因,尽管它显然与交互保留模式在内部的工作方式有关。)

    因此,在这种情况下,诀窍是进行自己的合并:

    $ git branch -m master old-master
    $ git checkout master
    

    (这使得新的master 指向提交D),然后:

    $ git merge old-master^2
    

    (这会对未修改的B 进行新的合并)。

    不过,您还提到:

    ...这是有问题的,因为B合并中的所有冲突都必须重新解决。

    不幸的是,您无法真正避免这种情况,因为DA 可能存在很大差异,以至于实际的合并冲突解决方案不同。

    如果您确定它们(分辨率)不应该(不同),您也可以通过从旧提交 C 获取合并结果来回避这个问题。例如,假设冲突发生在dir1/file1dir2/file2 中,而AD 之间的差异都在README.txt 或类似的地方。然后,当您执行上述合并时,您可以非常简单地“解决”冲突:

    $ git checkout old-master -- dir1/file1 dir2/file2
    

    提取这些文件的提交C 的版本,更新您的索引和工作树以恢复您之前的合并分辨率。你的冲突现在已经解决了(和以前一样),你可以git commit结果合并。

    【讨论】:

      猜你喜欢
      • 2014-02-17
      • 1970-01-01
      • 2014-10-29
      • 1970-01-01
      • 2021-09-13
      • 2018-03-13
      • 1970-01-01
      • 1970-01-01
      • 2018-07-15
      相关资源
      最近更新 更多