Git: 合并文件的合并结果子集
Git: Merge result subset of merged file
假设 master
上有一个 fileA
(假设它由 1000 行组成)
我们在 feature_branch
,其中 fileA
的长度为 1050 行。
那怎么可能,在 master
和表演
git merge feature_branch
fileA
最终(又)有 1000 行?
合并消息显示没有冲突,并告诉我
auto merging of fileA
更新:重要的是:
使用包含 仅 特定文件的新虚拟项目进行测试时,
从 master
/1000 行开始并且
合并 feature_branch
/1050 行,
是否以 1050 行结尾。
估计是主项目的历史问题吧?
TL;DR
这可能是完全正常的。例如,也许基本版本也有 1050 行,其中一项更改是 删除 50 行。将 "delete 50 lines" 与 "change something else, or change nothing at all" 结合使用会导致 "delete 50 lines" 作为对该文件所做的最终更改。
合并前须知
关于在 Git 中合并,有几点需要记住。首先最重要的是Git使用merge作为动词,to merge,意思是合并变化,Git 还使用 merge 作为形容词或名词,a merge commit 或简称 a merge,指的是至少有两个 parent 提交。
另一个是 git merge
并不总是真正合并。它可以做一些它称之为 fast-forwarding 的事情。在这种情况下,Git 根本不执行动词形式——这里没有 to merge 发生——并且不进行合并提交。因此,请确保您的合并是真正的合并,我将在下面进行描述,而不是其中之一 fast-forward non-merges。
还请记住 Git 存储快照:每次提交都是 每个 源文件的完整副本。这会影响动词形式 to merge 的工作方式。
"To merge"不代表"to copy"
Merge-as-a-verb 是 合并自某些共同点 以来的变化的行为。要考虑这一点,请考虑两个人进行了两次不同的更改:爱丽丝修正了第 12 行单词 "misspelled" 拼写错误的地方,鲍勃修正了第 20 行句子中单词错误的地方。 Alice 提交她的拼写修复,Bob 提交他的 wrong-word 修复。
当 Alice 去合并 Bob 的修复时,反之亦然,Git 不能带走 另一个修复。因此,将这两个更改合并到文件 README.txt
必须将 Alice 的版本与 merge base 版本进行比较,然后将 Bob 的版本与 相同的 进行比较合并基础版本。 Alice 所做的任何更改都会进入 变更集: "what Alice changed." 的列表 Bob 所做的任何更改都会进入单独的变更集:Bob 更改的内容。
Git 然后 组合 两个变更集,将 both 组变更应用于基础文件,得到最终的结果。如果 Alice 和 Bob 在不同的行上进行了更改(如本例所示),Git 将接受两组更改。如果他们更改了 same 行,Git 将查看他们是否对这些行进行了相同的 change,如果是,复制一份该更改。否则(他们更改了相同的行,但方式不同)Git 声明合并冲突,留下 you 来解决这个问题。
当合并结果让您大吃一惊时,请检查您的假设
首先要做的是确保您进行了真正的合并。如果是这样,下一步就是找出 通用基础版本 是什么。您可以使用 git merge-base
命令执行此操作:
git merge-base --all a1fc931 4056ca3
例如,如果您要合并的两个提交哈希是 a1fc931
和 4056ca3
。要查找提交哈希,您可以查看 git log --graph --oneline
;或者,如果合并完成,您可以使用特殊的 Git 语法来处理您刚刚进行的合并的 parent:
git merge-base --all HEAD^1 HEAD^2
如果合并因合并冲突而停止,您可以通过以下方式找到合并基础:
git merge-base --all HEAD MERGE_HEAD
因为 MERGE_HEAD
将记录其他提交的哈希值。
找到合并基础——这将是一些丑陋的大哈希 ID——你现在可以生成 Git 看到的两个变更集:
git diff --find-renames <base> <left>
git diff --find-renames <base> <right>
这里的<base>
是git merge-base --all
找到的merge base hash ID。 (如果它找到了不止一个,那么你就处于一种更加困难的特殊情况下;幸运的是,它只打印了一个哈希 ID。) <left>
部分是 HEAD^1
或 HEAD
,取决于合并是否完成:这是您开始合并时的提交。 <right>
部分是另一个提交,它是 HEAD^2
或 MERGE_HEAD
.
两个git diff
的输出是你改变了什么(左边)和他们改变了什么(在右边)。 Git 正在合并或已经合并这些更改以生成失败或成功的合并。
再说几句(和一张图)关于合并基础和fast-forwarding
在 Git 中寻找合并基础的行为包括扫描 提交图 — 或根据需要尽可能多地扫描 — 以找到 被合并的两个提交的最低共同祖先。如果我们绘制图形,左边是较早的提交,右边是较晚的提交,如下所示:
o--o--L <-- yourbranch (HEAD)
/
...--o--*
\
o--o--R <-- otherbranch
其中每一轮 o
代表一个提交,然后 merge base 是两个分支在过去加入的第一个提交:commit *
,在这种情况下。因此 Git 将比较提交 *
与提交 L
,并将 *
与提交进行比较R
,弄清楚你改变了什么,他们改变了什么。
Fast-forwarding 当分支序列看起来更像这样时发生:
...--o--* <-- yourbranch (HEAD)
\
o--o--R <-- otherbranch
在这里,合并基础提交 是 您当前的 (HEAD) 提交。如果 Git 将提交 *
与提交 *
进行比较,会有什么变化?
在这种情况下,Git 不必进行任何实际合并。它可以直接检出提交 R
,因为将 "nothing changed" 与 "whatever changed between *
and R
" 组合只会让您再次提交 R
。所以 Git 默认情况下这样做,导致:
...--o--o
\
o--o--R <-- yourbranch (HEAD), otherbranch
你只需要得到每个文件的版本。
您可以强制 Git 进行真正的合并,并进行真正的合并提交,结果是:
...--o--*---------M <-- yourbranch (HEAD)
\ /
o--o--R <-- otherbranch
其中 M
是合并提交(合并作为形容词)。不过,存储在提交 M
中的 快照 将与提交 R
中的快照完全匹配。 Git 将您的 "no change at all" 与他们的更改相结合,将它们应用于基础 *
,并构建一个匹配 R
.
的新提交
假设 master
上有一个 fileA
(假设它由 1000 行组成)
我们在 feature_branch
,其中 fileA
的长度为 1050 行。
那怎么可能,在 master
和表演
git merge feature_branch
fileA
最终(又)有 1000 行?
合并消息显示没有冲突,并告诉我
auto merging of fileA
更新:重要的是:
使用包含 仅 特定文件的新虚拟项目进行测试时,
从 master
/1000 行开始并且
合并 feature_branch
/1050 行,
是否以 1050 行结尾。
估计是主项目的历史问题吧?
TL;DR
这可能是完全正常的。例如,也许基本版本也有 1050 行,其中一项更改是 删除 50 行。将 "delete 50 lines" 与 "change something else, or change nothing at all" 结合使用会导致 "delete 50 lines" 作为对该文件所做的最终更改。
合并前须知
关于在 Git 中合并,有几点需要记住。首先最重要的是Git使用merge作为动词,to merge,意思是合并变化,Git 还使用 merge 作为形容词或名词,a merge commit 或简称 a merge,指的是至少有两个 parent 提交。
另一个是 git merge
并不总是真正合并。它可以做一些它称之为 fast-forwarding 的事情。在这种情况下,Git 根本不执行动词形式——这里没有 to merge 发生——并且不进行合并提交。因此,请确保您的合并是真正的合并,我将在下面进行描述,而不是其中之一 fast-forward non-merges。
还请记住 Git 存储快照:每次提交都是 每个 源文件的完整副本。这会影响动词形式 to merge 的工作方式。
"To merge"不代表"to copy"
Merge-as-a-verb 是 合并自某些共同点 以来的变化的行为。要考虑这一点,请考虑两个人进行了两次不同的更改:爱丽丝修正了第 12 行单词 "misspelled" 拼写错误的地方,鲍勃修正了第 20 行句子中单词错误的地方。 Alice 提交她的拼写修复,Bob 提交他的 wrong-word 修复。
当 Alice 去合并 Bob 的修复时,反之亦然,Git 不能带走 另一个修复。因此,将这两个更改合并到文件 README.txt
必须将 Alice 的版本与 merge base 版本进行比较,然后将 Bob 的版本与 相同的 进行比较合并基础版本。 Alice 所做的任何更改都会进入 变更集: "what Alice changed." 的列表 Bob 所做的任何更改都会进入单独的变更集:Bob 更改的内容。
Git 然后 组合 两个变更集,将 both 组变更应用于基础文件,得到最终的结果。如果 Alice 和 Bob 在不同的行上进行了更改(如本例所示),Git 将接受两组更改。如果他们更改了 same 行,Git 将查看他们是否对这些行进行了相同的 change,如果是,复制一份该更改。否则(他们更改了相同的行,但方式不同)Git 声明合并冲突,留下 you 来解决这个问题。
当合并结果让您大吃一惊时,请检查您的假设
首先要做的是确保您进行了真正的合并。如果是这样,下一步就是找出 通用基础版本 是什么。您可以使用 git merge-base
命令执行此操作:
git merge-base --all a1fc931 4056ca3
例如,如果您要合并的两个提交哈希是 a1fc931
和 4056ca3
。要查找提交哈希,您可以查看 git log --graph --oneline
;或者,如果合并完成,您可以使用特殊的 Git 语法来处理您刚刚进行的合并的 parent:
git merge-base --all HEAD^1 HEAD^2
如果合并因合并冲突而停止,您可以通过以下方式找到合并基础:
git merge-base --all HEAD MERGE_HEAD
因为 MERGE_HEAD
将记录其他提交的哈希值。
找到合并基础——这将是一些丑陋的大哈希 ID——你现在可以生成 Git 看到的两个变更集:
git diff --find-renames <base> <left>
git diff --find-renames <base> <right>
这里的<base>
是git merge-base --all
找到的merge base hash ID。 (如果它找到了不止一个,那么你就处于一种更加困难的特殊情况下;幸运的是,它只打印了一个哈希 ID。) <left>
部分是 HEAD^1
或 HEAD
,取决于合并是否完成:这是您开始合并时的提交。 <right>
部分是另一个提交,它是 HEAD^2
或 MERGE_HEAD
.
两个git diff
的输出是你改变了什么(左边)和他们改变了什么(在右边)。 Git 正在合并或已经合并这些更改以生成失败或成功的合并。
再说几句(和一张图)关于合并基础和fast-forwarding
在 Git 中寻找合并基础的行为包括扫描 提交图 — 或根据需要尽可能多地扫描 — 以找到 被合并的两个提交的最低共同祖先。如果我们绘制图形,左边是较早的提交,右边是较晚的提交,如下所示:
o--o--L <-- yourbranch (HEAD)
/
...--o--*
\
o--o--R <-- otherbranch
其中每一轮 o
代表一个提交,然后 merge base 是两个分支在过去加入的第一个提交:commit *
,在这种情况下。因此 Git 将比较提交 *
与提交 L
,并将 *
与提交进行比较R
,弄清楚你改变了什么,他们改变了什么。
Fast-forwarding 当分支序列看起来更像这样时发生:
...--o--* <-- yourbranch (HEAD)
\
o--o--R <-- otherbranch
在这里,合并基础提交 是 您当前的 (HEAD) 提交。如果 Git 将提交 *
与提交 *
进行比较,会有什么变化?
在这种情况下,Git 不必进行任何实际合并。它可以直接检出提交 R
,因为将 "nothing changed" 与 "whatever changed between *
and R
" 组合只会让您再次提交 R
。所以 Git 默认情况下这样做,导致:
...--o--o
\
o--o--R <-- yourbranch (HEAD), otherbranch
你只需要得到每个文件的版本。
您可以强制 Git 进行真正的合并,并进行真正的合并提交,结果是:
...--o--*---------M <-- yourbranch (HEAD)
\ /
o--o--R <-- otherbranch
其中 M
是合并提交(合并作为形容词)。不过,存储在提交 M
中的 快照 将与提交 R
中的快照完全匹配。 Git 将您的 "no change at all" 与他们的更改相结合,将它们应用于基础 *
,并构建一个匹配 R
.