Git 强制合并 dev 分支到 master 分支(覆盖所有本地文件)

Git force merge dev branch to master branch (overwrite all local files)

我在 master 分支中有一个旧的、不同步的项目代码。几个文件已被删除、添加、重写和彻底编辑。我想强制将开发分支中稳定的最新代码合并到 master 中。我试图强制合并,但仍然遇到合并冲突。

以下是我遵循的步骤:

➜  project git:(development) git checkout master
➜  project git:(master) git pull
➜  project git:(master) git merge -s recursive -X theirs development

CONFLICT (modify/delete): vendors/popup/magnific-popup.css deleted in HEAD and modified in development. Version development of vendors/popup/magnific-popup.css left in tree.
CONFLICT (modify/delete): vendors/popup/jquery.magnific-popup.min.js deleted in HEAD and modified in development. Version development of vendors/popup/jquery.magnific-popup.min.js left in tree.
CONFLICT (modify/delete): vendors/owl-carousel/assets/animated.css deleted in HEAD and modified in development. Version development of vendors/owl-carousel/assets/animated.css left in tree.

... several similar conflicts

我尝试对主分支中的所有文件执行 rm

➜  project git:(master) git reset --hard HEAD
➜  project git:(master) rm -rf *
➜  project git:(master) git merge -s recursive -X theirs development

仍然遇到同样的冲突。我做错了什么?

编辑:

这是一张图表,可以更好地理解正在发生的事情。有的时候还推送了一些不相关的历史。

分支只是一个指针,master只是另一个分支。如果您没有在进行过程中合并更改,并且 dev 代表项目的当前稳定状态,那么一个可以避免您经历所有冲突的选项是:

git branch -m master <new-name>
git branch -m dev master

可能需要调整跟踪关系,具体取决于您如何设置任何遥控器。

这只会将 master 设置为与您的 development 分支相同 - 在 development 的最后一次合并之后提交的 master 中的任何内容都将丢失。

git checkout -B master development

我认为您根本不想要这种合并。我认为您想要的是 "theirs strategy" 合并,Git 不提供。它仍然可以完成:参见 VonC's answer here。但是请继续阅读以确保您想要的,然后再使用它。


这里发生了两件重要的事情,它们都被您的命令序列捕获了:

git checkout master
git merge -s recursive -X theirs development

(我省略了没有做任何事情的步骤)。

首先是大多数合并——包括来自默认 -s recursive 合并策略的合并——通过找到一个共同的起点来工作:共享的 合并基础 提交。将其绘制为一个相当简单的案例,请考虑以下提交图:

          C--D--E   <-- master (HEAD)
         /
...--o--B
         \
          F--G--H   <-- development

显然你自己的图表会更复杂,但最终,这一切都是一样的:Git 接受 当前提交 ,如你所指示的当前分支 master,即提交 E,作为合并过程的三个输入之一。 Git 接受您 指定 的提交,在本例中提交 H 因为 development 是您说要查看的那个,作为第二个合并过程的三个输入。

第三个 输入是根据图表计算的。我们从两个提示提交中的每一个开始,然后开始向后走,Git 始终如此。提交 E 导致回到 D,这导致 C,然后是 B。同时提交 H 导致 GFB。提交 B 两个 分支上,并且在图形术语中,它是 最低共同祖先 .1 (非正式地,它是最接近两个分支提示的那个。"Closest" 在复杂的图形中分解,但对于像这样的简单图形,这就是最低共同祖先的意思。)这使它成为最好的共享提交,因此合并基础。

现在发生的事情在一般情况下非常简单,但在某些细节上变得棘手。 Git 从 合并基础 提交开始 "desired result"。也就是说,Git开头的内容是commitB中快照的内容。对于这些内容,Git 需要应用组合 所做的任何更改以及他们所做的任何更改 取得。所以 Git 需要 运行 两个 git diff 命令:

  • git diff --find-renames <em>hash-of-B</em> <em>hash-of-E</em>:我们在 master
  • 上所做的更改
  • git diff --find-renames <em>hash-of-B</em> <em>hash-of-H</em>:他们在 development
  • 上改变了什么

如果合并基础接近两个提示,就像这里一样,可能这两个差异中的每一个都没有显示那么多。将两组更改结合起来将简单明了。如果我们更改了 README.md 的第 12 行,而他们根本没有触及 README.md,Git 会接受我们的更改,让我们的 README.md 版本进入新的合并犯罪。如果他们触及 README.md 的不同行,Git 会将我们的两个更改放在一起:两者都应用于 B:README.md 以生成新提交的 README.md。对所有文件重复此过程。

如果我们和他们都触及相同文件的 相同 行,但是,Git 通常会声明合并冲突并停止,留下我们收拾残局。这就是 -X theirs 选项的用武之地:这是一个 扩展选项 ,传递给 -s 策略。2在这种情况下,recursive 策略将 theirs 扩展选项视为含义:在发生冲突的情况下,丢弃我的零钱并使用他们的

请注意,如果没有冲突,Git将使用我们的更改!有时这就是我们想要的。如果我们想要的,-X theirs是正确的想法。如果不是我们想要的,-X theirs就是错误的想法。所以这就是您需要决定的地方:我们是使用 -X theirs,还是使用必须像 VonC 的答案那样构建的 -s theirs


1它是最低的共同祖先,因为计算机科学家将他们的树倒过来绘制:

     A
     |
     B
   /   \
  C     D
 / \     \
E   F     G

此处E和F的共同祖先是C、B、A,但C是最低的。 F和G的共同祖先是B和A; B最低。 Git的commit graph是有向无环图或DAG,而不是树,所以"LCA"不像树那么简单,可以有多个LCA(或者没有LCA在all) 给定图中的两个节点。 Git 明智地处理所有这些,对于明智的定义。

2Git 调用 -X 一个 策略选项 ,但这听起来与 -s <em>策略</em> 参数。它实际上是该策略的一个选项,因此 Git 的名称选择不当。我认为 eXtended option 是一个更好的名字,部分原因是它解释了 -X.


如果您不想-X theirs:请参阅我链接的其他答案

没什么好说的:-s theirs 曾经是 Git 合并策略。它不再是了。您仍然可以通过多种方式合成它。我最喜欢的实际上是 Michal Soltys' answer.

中的管道命令变体

如果你确实想要-X theirs:为什么会抱怨冲突?

我在上面提到合并过程——git merge合并 部分——是"pretty simple ... but gets sticky in some details."你刚刚点击了其中一个细节。

Git 通过区分它找到的合并基础获得的大量 git diffs,与您指定的两个提示提交中的每一个相比,有很多文件是 一侧完全删除,但另一侧修改

...--B--o--o--...--o--o   <-- master (HEAD)
      \
       o--o--...--o--o   <-- development

在那个巨大的匿名链中的某个地方 o 沿着顶部提交,"we"(基本 vs 主)删除 一些文件。 "They"(基础与开发)更改 该文件。 Git 不知道如何结合 "deleted" 和 "changed"。

我称这些 高级别 冲突,因为它们是对文件的本质或存在的更改,而不是对文件中各个行的更改。好吧,我也这么称呼,因为Git本身就调用了"low level merge driver"结合个别行的代码部分,所以这些肯定是相反的,high水平.这些类型的冲突——无论是 add/add、modify/delete、rename/delete 还是什么——总是 导致 -s recursive git merge停下来让你收拾残局。扩展选项仅提供给策略调用的 low 级别合并 driver

事实上,不会停止的唯一策略是-s ours策略。当您使用 -s ours 策略时,那个 完全忽略 "their" 提交中的内容。 Git 甚至根本不关心合并基础——它只是使用 我们的 提交中的内容作为合并结果。

当然,-s ours需要我们的。你想要的东西需要 他们的,至少对于这个特殊情况,也许对于所有情况。但是如果你真的不想想要-s theirs的等价物,而是-X theirs在这些情况下获取他们的整个文件,你就无法解决每个问题这些冲突是事后发生的。

可以使用脚本(您必须自己编写)来完成。虽然这有点棘手。诀窍是使用 git ls-files --stage:你会看到,对于 Git 抱怨的每个文件:

CONFLICT (modify/delete): vendors/popup/magnific-popup.css deleted in HEAD and modified in development. Version development of vendors/popup/magnific-popup.css left in tree.

Git 将在阶段 #1 中有一个条目用于名称 (vendors/popup/magnific-popup.css),在阶段 #3 中有另一个条目(他们的),但在阶段 #2 中没有条目(我们的) .要解决此问题,请使用 git add 将工作树中的第 3 阶段条目写入第 0 阶段的索引:git add 将删除第 1 阶段和第 3 阶段的副本。您可以收集所有此类文件名并将它们全部传递给 git add.