在双向合并 git 个分支时跳过文件
Skip files while merging git branches in both directions
我的 git 存储库中有两个分支。有一个配置文件在两个分支中应该是不同的。我将 config merge=ours
添加到 .git 属性以在合并时保留配置文件。
从 B 分支合并到 A 分支时,配置文件不会合并。见下图
*---*---*---*--*(A)(HEAD) //config file in branch A remains
\ /
*---*---*(B)
但是,当我从 A 分支合并到 B 时,我的配置文件也被合并
*---*---*---*--*(A)
\ / \
*---*---*----*(B)(HEAD) //config file in B gets merge with A
有人能告诉我是什么原因吗?从两个方向合并时如何保留文件?
尽管我发现其他讨论仅在一个方向合并时保留文件,但我找不到关于这种特定情况的讨论。
合并驱动程序 在 .gitattributes
文件中定义只有当所有三个输入文件都不同时才使用。
这意味着设置merge=ours
(并定义ours
合并驱动程序)经常会失败。我建议不要为此烦恼:只需定义您自己的流程来处理这些问题。
更多详情
首先要记住 git merge
并不总是进行真正的合并:
-s
/ --squash
指示它根本不进行真正的合并,但它仍将使用 merge-as-a-verb 操作(并禁止 fast-forwarding).
--no-ff
将禁止 fast-forward not-a-merge 操作,强制对这种情况进行真正的合并。
--ff-only
如果 fast-forward not-a-merge 不可能,将导致命令失败(并且不合并)。
在查看本文的其余部分时请记住以下几点:
我们这里只考虑解析和递归策略;章鱼合并和-s ours
完全不同。
其中一个输入提交总是 HEAD
。另一个是您在命令行上命名的提交。 git merge
命令将自行定位合并基础提交;这是第三次输入提交。如果有多个合并基础提交,-s resolve
将随机选择一个并且 -s recursive
将首先合并合并基础——这是内部递归合并——然后进行新的提交以用作外部合并的合并基础。
如果合并基础提交是 HEAD
提交,则 fast-forward 是可能的。如果允许,Git 将不会进行合并,而只会检查命令行中指定的其他提交,调整当前分支以指向该提交。
如果合并基础提交是另一个提交,则不需要合并:当前分支是最新的并且没有任何反应。
剩下真正的合并案例:涉及三个不同的提交。这些提交中的每一个都有许多文件的完整快照,并且 Git 将:
- 将合并库中的每个文件与
HEAD
中的每个文件进行比较,看看我们更改了什么;
- 将合并库中的每个文件与另一个提交中的每个文件进行比较,以查看它们更改了什么;和
- 合并这些更改。
为了合并这些更改,Git 将使用 low-level 合并驱动程序。您可以使用 merge=<em>name</em>
.gitattributes
设置来定义这种驱动程序。 (注意:recursive/resolve 合并代码将执行自己的 high-level 合并以处理 newly-created 文件、删除的文件和重命名的文件,然后再到达 low-level 合并处理程序。此 high-level 合并可能会产生冲突,这将导致合并停止,无论 low-level 驱动程序可能做什么。)
这就是问题所在:high-level 合并代码处理任何 high-level 冲突, 不关心 运行 low-level 完全合并代码 如果可以跳过它。只要 to-be-merged 文件的原始哈希 ID 在多个提交中匹配,此 high-level 代码就会跳过 low-level 代码。
也就是说,假设 我们 更改了文件 F
而 他们没有 。然后合并基础中 F
的散列匹配他们提交中 F
的散列,但是 我们的 提交中 F
的散列不同。 Git 假定正确的操作是获取我们的文件:它甚至从未 运行s low-level 合并驱动程序。使用默认的 low-level 驱动程序,这很好。使用 ours
合并驱动程序,这仍然很好:Git 占用了我们的 F
.
但是假设我们没有改变F
而他们改变了。然后合并基础中 F
的散列匹配我们提交中 F
的散列,但不匹配他们提交中的散列。 Git 假定正确的操作是拿走他们的文件。它永远不会 运行 成为 low-level 驱动程序,即使它是 ours
合并驱动程序。
如果 Git 有一个 .gitattributes
设置强制它为每个文件 运行 low-level 驱动程序,这将解决问题。但事实并非如此。
我的 git 存储库中有两个分支。有一个配置文件在两个分支中应该是不同的。我将 config merge=ours
添加到 .git 属性以在合并时保留配置文件。
从 B 分支合并到 A 分支时,配置文件不会合并。见下图
*---*---*---*--*(A)(HEAD) //config file in branch A remains
\ /
*---*---*(B)
但是,当我从 A 分支合并到 B 时,我的配置文件也被合并
*---*---*---*--*(A)
\ / \
*---*---*----*(B)(HEAD) //config file in B gets merge with A
有人能告诉我是什么原因吗?从两个方向合并时如何保留文件? 尽管我发现其他讨论仅在一个方向合并时保留文件,但我找不到关于这种特定情况的讨论。
合并驱动程序 在 .gitattributes
文件中定义只有当所有三个输入文件都不同时才使用。
这意味着设置merge=ours
(并定义ours
合并驱动程序)经常会失败。我建议不要为此烦恼:只需定义您自己的流程来处理这些问题。
更多详情
首先要记住 git merge
并不总是进行真正的合并:
-s
/--squash
指示它根本不进行真正的合并,但它仍将使用 merge-as-a-verb 操作(并禁止 fast-forwarding).--no-ff
将禁止 fast-forward not-a-merge 操作,强制对这种情况进行真正的合并。--ff-only
如果 fast-forward not-a-merge 不可能,将导致命令失败(并且不合并)。
在查看本文的其余部分时请记住以下几点:
我们这里只考虑解析和递归策略;章鱼合并和
-s ours
完全不同。其中一个输入提交总是
HEAD
。另一个是您在命令行上命名的提交。git merge
命令将自行定位合并基础提交;这是第三次输入提交。如果有多个合并基础提交,-s resolve
将随机选择一个并且-s recursive
将首先合并合并基础——这是内部递归合并——然后进行新的提交以用作外部合并的合并基础。如果合并基础提交是
HEAD
提交,则 fast-forward 是可能的。如果允许,Git 将不会进行合并,而只会检查命令行中指定的其他提交,调整当前分支以指向该提交。如果合并基础提交是另一个提交,则不需要合并:当前分支是最新的并且没有任何反应。
剩下真正的合并案例:涉及三个不同的提交。这些提交中的每一个都有许多文件的完整快照,并且 Git 将:
- 将合并库中的每个文件与
HEAD
中的每个文件进行比较,看看我们更改了什么; - 将合并库中的每个文件与另一个提交中的每个文件进行比较,以查看它们更改了什么;和
- 合并这些更改。
为了合并这些更改,Git 将使用 low-level 合并驱动程序。您可以使用 merge=<em>name</em>
.gitattributes
设置来定义这种驱动程序。 (注意:recursive/resolve 合并代码将执行自己的 high-level 合并以处理 newly-created 文件、删除的文件和重命名的文件,然后再到达 low-level 合并处理程序。此 high-level 合并可能会产生冲突,这将导致合并停止,无论 low-level 驱动程序可能做什么。)
这就是问题所在:high-level 合并代码处理任何 high-level 冲突, 不关心 运行 low-level 完全合并代码 如果可以跳过它。只要 to-be-merged 文件的原始哈希 ID 在多个提交中匹配,此 high-level 代码就会跳过 low-level 代码。
也就是说,假设 我们 更改了文件 F
而 他们没有 。然后合并基础中 F
的散列匹配他们提交中 F
的散列,但是 我们的 提交中 F
的散列不同。 Git 假定正确的操作是获取我们的文件:它甚至从未 运行s low-level 合并驱动程序。使用默认的 low-level 驱动程序,这很好。使用 ours
合并驱动程序,这仍然很好:Git 占用了我们的 F
.
但是假设我们没有改变F
而他们改变了。然后合并基础中 F
的散列匹配我们提交中 F
的散列,但不匹配他们提交中的散列。 Git 假定正确的操作是拿走他们的文件。它永远不会 运行 成为 low-level 驱动程序,即使它是 ours
合并驱动程序。
如果 Git 有一个 .gitattributes
设置强制它为每个文件 运行 low-level 驱动程序,这将解决问题。但事实并非如此。