【问题标题】:How to force git to remerge a commit it thinks is already merged如何强制 git 合并它认为已经合并的提交
【发布时间】:2017-10-12 08:03:07
【问题描述】:

我有三个分支,masterfeatureintegrationintegration 表示部署到特定开发服务器的软件配置。特征分支先合并到integration分支,在对应的服务器上测试,再合并到master

                \          \
o----------------o----------o             (master)
    \    \                              
     \    o----o-------o--o---o----o      (integration)
      \       /       /      /    /   
       o-----o-------o--o---o----o--o     (feature)

我的目标是将我的integration 分支更新为与master 相同,然后将我的feature 分支合并到其中,这样integration 分支就是master 分支加上我的@ 中的更改987654333@分行。

为此,首先将我的integration 分支与strategy=ours 合并到master 中,然后我将integration 分支快进到与master 分支相同。

                \          \
o----------------o----------o--------X           (master, integration)
    \    \                          /    
     \    o----o-------o--o---o----o             
      \       /       /      /    /   
       o-----o-------o--o---o----o--o            (feature)     

然后我的计划是将feature 分支合并到新更新的integration 分支中。

                \          \
o----------------o----------o--------X-           (master)
    \    \                          / \ 
     \    o----o-------o--o---o----o   Y          (integration)
      \       /       /      /    /   /   
       o-----o---o---o--o---o----o---o          (feature)          

但是,当我这样做时,我更新的 integration 分支与集成分支上的先前提交相同,例如X 和 Y 是相同的,而不是 master 分支加上我在功能分支中的更改的总和。

那么,谁能推荐一种方法让我按照我想要的方式配置分支?

【问题讨论】:

  • 为什么不直接将master合并到feature中呢?
  • 从哲学上讲,因为我想将 Integration 分支所代表的服务器重新设置为与 master 相同的基线。实际上,因为我已经处于显示的第二个配置中,所以将主控或集成到功能中是相同的操作。如果我将功能重置为它的分支上的倒数第二个提交,那么只需快进即可合并。如果我从该分支上的最后一次提交提交,那么我只会得到最后两次提交之间的增量——这与最后一次提交是“git commit --allow-empty”提交无关。并且“X”提交已被推送,所以我无法重置它。
  • 我仍然对你的历史是什么样子以及你想做什么感到有点困惑。如果将integration 合并到master 中,则只有master 应该继续前进,integration 不受影响。
  • 在显示的前两个示例之间,我将integration 合并到master 中,正如您所说,这将master 向前移动。该合并使用了ours 策略,因此新提交 X 与之前对master 的提交相同。然后我将master 合并到integration 中,后者将integration 快速转发到与master 相同的提交X。这是第二张图。现在我想将master 合并到我的feature 分支中,以便将所有其他进入master 的工作合并到我的分支中。但是 git 将合并视为快进,因此我丢失了对 integration 分支的更改。
  • 中,首先将我的功能分支合并到 master 与 strategy=ours,然后我将功能分支快进到与 master 分支相同。 我认为根据图表,您的意思是“将integration 合并为master”和“快进integration”。 (而且好像是你在cmets里说的。)

标签: git merge branch


【解决方案1】:

[编辑:有问题已修复,现在回答中删除] 除了问题中的轻微故障(图表显示了git checkout master && git merge -s ours integration && git checkout integration && git merge --ff-only master的效果,它在你所说的任何地方都使用integration@987654323 @), Git 似乎表现得像 Git 应该做的那样。

我的目标是将我的集成分支更新为与 master 相同,然后将我的功能分支合并到其中,以便集成分支是 master 分支加上我的功能分支中的更改。

这不一定是个好主意,因为 Git 不会像我认为的那样进行合并。 :-)

git merge 所做的事情很复杂,因为它充满了微小的细节,但它的方式却足够简单,而您在此处绘制的图表是一个很好的开始。让我们从“之前”开始。我将对其稍作修改以添加HEAD 符号并挑选出三个提交,LRB

                \          \
B----------------o----------L         <-- master (HEAD)
    \    \
     \    o----o-------o--o---o----R   <-- integration
      \       /       /      /    /
       o-----o-------o--o---o----o--o   <-- feature

每个合并策略-s recursive 是默认值,而您使用的 -s ours 是另一个)完全控制接下来发生的事情。但是让我们暂时假设您运行git merge -s recursive integration 而不是git merge -s ours integration。这个合并策略的第一步是定位 merge base 提交。 Git 通过向后走图(沿所有内部箭头的方向,总是向后)来找到当前分支的提示提交和命名分支的提示提交之间的共同祖先。

当前分支是HEAD,即master,它定位commit L(这里的L 代表Left、Local 或--ours)。命名的提交是 integration,它定位提交 R(R 代表 Right、Remote、otherR 或 --theirs)。 B 是合并基础,如果我们同时从 LR 向后遍历图形,我们(或 Git)将找到第一个共享提交。

所以现在,在普通的合并中,Git 将运行相当于:

git diff --find-renames B L > /tmp/left
git diff --find-renames B R > /tmp/right

然后它会尽力计算一组新的文件,将 /tmp/left 和 /tmp/right 中的两个更改集合并,并将它们应用到 B 中的文件。

如果一切顺利,Git 将使用合并的文件与两个父级(首先是 L,然后是 R)进行新的提交。这实际上是您得到的新图表:

                \          \
o----------------o----------L--------X   <-- master (HEAD)
    \    \                          /
     \    o----o-------o--o---o----o   <-- integration
      \       /       /      /    /
       o-----o-------o--o---o----o--o   <-- feature

(我没有命名BR,因为它们不再特别,但暂时离开了L。)但是你使用了-s ours,这个策略实际上并没有运行git diff。它只是使用与前一次提交相同的树,即L 本身。所以提交LX 代表相同的来源 ...但不相同的提交历史,因为现在integration 已合并到master,丢弃在此过程中会引入的任何差异。

这就是问题所在

因为 Git 中的任何东西都有“意义”——Git 只是遵循一组程序化的程序,所以唯一的意义是我们赋予它的意义,或者我们在编写这些程序化程序时使用的想法—— -s ours 是:我刚刚合并的分支中的一切都很糟糕;永远不要再使用它。其目的是杀死分支。

让我们看看现在当我们运行git checkout integration &amp;&amp; git merge --ff-only master 时它是如何工作的(或者默认情况下没有--ff-only,如果我们没有弄乱各种Git 配置变量的话)。快进操作,实际上根本不是合并,只是移动了一个分支标签。 git checkout 步骤将我们的 HEAD 更改为指向 integration,以便移动的是 integration。这会产生您的第二张图表:

                \          \
o----------------o----------o--------X   <-- master, integration (HEAD)
    \    \                          /
     \    o----o-------o--o---o----o
      \       /       /      /    /
       o-----o-------o--o---o----o--o   <-- feature

现在我们可以运行git merge feature(有或没有明确的-s recursive)。这将遍历图表以找到合并基础,这是从Xfeature 的尖端可到达的第一个提交。从X开始,我们向下和向左移动两次到达这个:

                \          \
o----------------o----------o--------X   <-- master, integration (HEAD)
    \    \                          /
     \    o----o-------o--o---o----I
      \       /       /      /    /
       o-----o-------o--o---o----B--R   <-- feature

我再次标记了基本提交和右侧提交,但保留了名为 X 的左侧提交。我又添加了一个字母I,这是integration提示,在它被转发之前。

现在 Git 将运行:

git diff --find-renames B X
git diff --find-renames B R

并合并更改。

BX 的变化是:扔掉integration 上的大部分内容,这很明显,因为从IX 的变化是扔掉在integration 上所做的一切——回到master 中所做的一切。

我们可能,取决于IX,可以保留从BI 发生的任何事情。我们绝对保留从BR 发生的任何事情,除非它与B-to-X 更改相冲突。这些组合更改,BX 加上 BR,形成了合并结果,如果一切顺利,Git 将合并提交 Y 与第一父级 X 和第二父级R.

你不能从这里(很容易)到达那里

最大的问题是git merge -s ours,它记录合并发生,但仅使用来自master(源代码)。稍后的合并使用记录的先前合并来找到一个新的、更好、更简单的合并基础......并且从该合并基础工作表明,在合并 feature 时,un-do 很重要(来自那个基础)integration 上的所有更改,显然它们很糟糕,应该被丢弃。

实际上可以构造一棵不同的树:您可以运行git merge --no-commit,它执行树合并(差异组合)工作并设置所有内容,以便下一个git commit 将与通常的两个父母一起进行合并提交,但尚未实际进行该提交。接下来,您可以对工作树和git add 生成的文件进行任何您喜欢的更改回到索引中。例如,您可以运行git diff &lt;hash-of-X&gt; &lt;hash-of-I&gt; | git apply,将所有未完成的更改拉回工作树中。 (这假设它们会很好地应用。将 --full-index 添加到 diff 并将 -3 添加到应用以在需要时获得完整的三向合并。我认为 Git 不能很好地处理这里的重命名。)

不过,最终,您正在与设计背道而驰:只要您不使用 -s ours,合并行为以及在先前合并后新计算的合并基数的设计就可以正常工作。如果你确实使用了-s ours,它可能是为了“杀死”一个分支,而不是继续使用它,并且合并基础算法就是这样做的。

【讨论】:

  • ours 合并的意图使集成分支与主分支完全相同。如果有更好的方法,我想知道。
  • 好吧,那么显而易见的问题是:你为什么要关心集成分支是否与主分支暂时匹配? 不过,你可以做到这样,根本不使用git merge,因此没有记录合并。
  • 使下一次提交的内容匹配(精确 100%)之前提交的内容:git read-tree --reset -u &lt;hash&gt;--reset 表示“丢弃当前索引,替换为目标哈希”,-u 表示“更新工作树以匹配新索引”(-u 在技术上不是必需的,但有助于管理一个自己的理智)。这与存储库顶层的git checkout &lt;hash&gt; -- . 相似但不完全相同。
  • 这是否会达到你最终想要的结果,我不确定——你现在可以完成合并过程,但是,按照图表找到合并基础并查看哪些提交得到了差异,看看是不是你想要的。
  • “你为什么关心...?” master 自从feature 开始开发以来已经更新了好几次。 integration 分支代表特定服务器的配置。我想将master 的所有更新合并到integration 中以使其保持最新状态,然后覆盖feature 中的工作。从理论上讲,我可以将合并到 master 中的所有内容单独合并到 integration 中,但如果在处理过程中出现合并冲突,我的处理方式与我不再拥有相同配置之前的处理方式不同。
猜你喜欢
  • 1970-01-01
  • 2011-10-12
  • 2013-10-23
  • 1970-01-01
  • 2011-11-05
  • 2019-04-25
  • 2021-04-25
  • 2011-03-16
  • 1970-01-01
相关资源
最近更新 更多