我在 git 中有两个分支。
分支开发和我的功能分支 1300。
分支1300 是对分支开发的完全重构。现在我已经完成了 1300,并希望将其合并到 dev 分支中。
如何将强制 1300 合并到 dev 中。
当我进行正常的合并并在 intellij 中接受他们的合并时(theris 是 1300),我在 1300 中删除的文件仍在 dev 中。
强制合并的任何提示?
提前致谢
Git 中没有"强制合并"这样的东西。 幸运的是,无论如何,这不是你想要的。 你想要的是git merge -s theirs
. 不幸的是,这并不存在。 幸运的是,有一些方法可以伪造它。 参见 是否有"他们的"版本的"git merge -s ours"? 我可以将其作为重复项关闭,但相反,我将写一个长格式的答案。
Git 提交,包括合并提交
"merge"这个词有两个或更多的含义,这取决于你是把它用作动词、合并还是形容词或名词:合并提交(形容词)或合并(形容词变成名词,因为形容词在英语中是习惯的:这称为名词化)。 名词版本,合并,只是在你的仓库中,作为一个提交,就像任何其他提交一样,除了它不是通常的单父,而是有两个。1
请记住,在 Git 中,提交由两件事组成:快照(一组保存的文件)和一些元数据:有关提交的信息,例如谁、何时以及为什么进行。 在元数据中,Git 存储一些早期提交的哈希 ID。 这些是相关提交的父母。 大多数普通提交都有一个父哈希 ID;使提交成为合并提交的原因是它有两个父级,而不是通常的父级。 但它只有一个快照。 该快照是合并结果。
我发现图片在这里非常有帮助。 首先,让我们在 Git 中绘制一个简单的提交字符串(如果你愿意的话,是一个"分支")的图片:
... <-F <-G <-H
在这里,H
代表某些提交链中最后一个提交的实际哈希 ID。 由于H
是提交,因此它同时具有数据(快照)和元数据。 快照保存所有文件,就像您或任何人提交时它们的形式一样。H
的元数据包含早期提交G
的实际原始哈希 ID。 我们说H
指向G
.
当然,G
也是一个提交:快照(截至当时的文件)和元数据。 此元数据指向更早的提交F
。F
和G
一样,有一个快照和元数据,所以它指向后面。
所有这些意味着,只要我们能找到最后一个提交,我们就可以找到链中的所有提交。这就是 Git 分支名称的作用:它让我们找到链中的最后一个提交。 它只保存该提交的哈希 ID。 无论名称包含什么哈希 ID,这都是该链中的最后一次提交。 所以这可能看起来像:
...--F--G--H <-- main
I--J <-- feature
在这里,分支名称feature
指向提交J
,指向提交I
,指向提交H
,指向G
,依此类推。 事实上,还有另一个名称main
,指向H
并不能阻止我刚刚列出的所有提交都在两个分支上。 然而,main
指向H
的事实确实会阻止提交I
和J
在该分支上。 提交到并包括H
都在两个分支上,而最后两个仅在feature
上。
我们可以随时添加和删除我们喜欢的任何名称。 只有几个限制。 例如,名称必须满足某些测试:main
和feature
都可以,但hello..world
不是因为禁止双点。 名称还需要指向实际提交。阿拉伯数字我不会涵盖所有内容,但您应该明白:我们可以在某些(合理)限制内创建和销毁任何名称。 所以我们可以在上图中添加一个名称dev
,如下所示:
...--F--G--H <-- dev, main
I--J <-- feature
如果我们现在git checkout dev
或git switch dev
,以便它成为当前名称- 我们将在此过程中将提交H
作为当前提交- 然后进行一些新的提交,我们将需要重新绘制图形。 在这里,我把feature
放在上面,dev
放在下面:
I--J <-- feature
/
...--F--G--H <-- main
K--L <-- dev (HEAD)
这张图向我们展示了我们有两个仅在feature
上提交的提交(I-J
),两个仅在dev
上提交。(HEAD)
表示法表明我们当前正在使用名称dev
,因此使用 commitL
,这是仅在dev
上使用的名称之一。
1从技术上讲,任何具有两个或更多父级的提交都是合并提交,但两个就足够了,并且通常有两个。
2这没有技术原因,Git 正在获得一项功能,其中某些名称将放宽此约束,但目前所有名称始终需要它,分支名称仍然需要它。
合并为动词的机制
当您选择进行合并提交(通常通过运行git merge
)时,您可以选择要绑定在一起的两个(或根据脚注 1 的更多)提交。 其中之一是当前提交,您可以在运行git merge
之前选择它,通常使用git checkoutbranch
或git switchbranch
。 另一个是在命令行上命名的提交。 因此,我们可以运行:
git checkout dev
git merge feature
例如。3然后 Git 会自行找到第三个提交。 第三个提交是合并基,合并基提供了合并操作的起点:合并过程,或合并为动词。
您遇到的问题是,如果您的图片如下所示:
I--J <-- feature
/
...--F--G--H
K--L <-- dev (HEAD)
—我删除了名称main
以防止它弄乱图片; 它是否存在并不重要 -git merge
动作,合并为动词过程,将通过以下方式工作:
- 找到两个分支上的最佳提交,在这种情况下显然是提交
H
; - 将
H
中的快照与L
中的快照进行比较(dev
),看看我们改变了什么; - 将
H
中的快照与J
中的快照进行比较(feature
),看看它们更改了什么;
合并 - 这些更改并将合并的更改应用于
H
中的快照;以及 - 使用这些组合更改生成最终合并结果。
结果将如下所示:
I--J <-- feature
/
...--F--G--H M <-- dev (HEAD)
/
K--L
请注意分支名称dev
现在如何指向新提交,M
,作为执行此合并过程的结果。
3您可以添加git merge --no-ff
以防止 Git 执行快进而不是合并。 我想人们可以将其视为强制合并 - 但这仍然不是您想要的。:-)
有一个ours
策略
Tim Biegeleisen 在评论中提到的策略是 Git 如何实际实现合并为动词的过程。 当您运行git merge
时,它会进行一系列初步测试并处理各种命令行选项。 其中一个允许的选项是-s
,-s
采用策略参数。
Git 内置的开箱即用策略是:
-s recursive
:这是通常的默认。 它做了我们上面讨论的组合。-s resolve
:这是-s recursive
的变体,它也结合了。 我们不需要在这里讨论它们之间的技术差异,因为您的问题是您想避免合并。-s octopus
:这是一种用于合并两个以上提交的特殊用途算法。 这不是你想要的答案。-s ours
:这是另一种特殊用途的算法。 这几乎是你想要的!
ours
策略的作用很简单:为了创建新快照,它会完全忽略您使用git merge
命令指定的其他提交。 然后,它说新快照应与当前提交完全匹配。 因此,如果我们在dev
上运行git merge feature
,我们会得到:
I--J <-- feature
/
...--F--G--H M <-- dev (HEAD)
/
K--L
这看起来和以前完全一样,直到我们在提交M
中查看快照。以前,M
中的快照是合并两个分支上的工作并将合并更改应用于合并基础快照的结果H
。 现在,M
中的快照与提交L
中的快照完全相同。
这太接近了,但仍然不是您想要的,因为您说您想要的是像往常一样M
,但使用来自提交J
的快照。如果 Git 有办法开箱即用,它将被拼写为-s theirs
. 显然 Git 曾经有过-s theirs
(这一定是在 Git-1.6 之前),但现在没有-s theirs
了。
有很多方法可以伪造它。有关这些不同的方法,请参阅链接的答案(应该是重复的)。确保你真的想伪造它。您最终将得到一个合并提交M
其快照与您选择的任何"端"文件匹配。
正如那里的几个答案所指出的那样,要警惕-X ours
和-X theirs
,它们的含义非常不同。 这些选项——我喜欢称它们为扩展选项,以明确它们不是策略(-s
)选项——被传递给策略,随心所欲地使用。 默认递归/解析代码使用它来自动解决冲突。 它不会完全忽视一方的变化。 相反,它接受任何一方的更改,但是当这些更改发生冲突时,使用一方的更改解决冲突,仅针对该特定差异大块抛弃另一方的更改。