[英]Why does this GIT merge not result in conflicts?
We discovered a serious issue with GIT at work today and I would like to know whether this is a bug or by design, and also, how to resolve this issue.我们今天在工作中发现了 GIT 的一个严重问题,我想知道这是一个错误还是设计使然,以及如何解决这个问题。
Consider the following sequence of events:考虑以下事件序列:
Result : No merge conflict is reported even though the file has been edited in both branches, and even worse, the revert in step 3.2 did not survive, even though it was the most recent commit.结果:即使文件已在两个分支中进行编辑,也不会报告合并冲突,更糟糕的是,即使是最近的提交,步骤 3.2 中的还原也没有保留。
This is a huge problem, as can be seen by the following recent example: A colleague of mine had commited similar changes to different branches, noticed that parts of these changes were malicious and manually reverted parts of them on one of the branches because of that.这是一个巨大的问题,从以下最近的示例中可以看出:我的一位同事对不同的分支进行了类似的更改,注意到这些更改的一部分是恶意的,因此在其中一个分支上手动还原了其中的一部分. After merging the branches, he was surprised to find out that his revert did not make it through the merge.
合并分支后,他惊讶地发现他的恢复没有通过合并。
I uploaded a minimal example to google drive that demonstrates this issue.我上传了一个最小的例子到谷歌驱动器来演示这个问题。 You can merge Master into test1 or vice versa, to see for yourself.
您可以将 Master 合并到 test1 中,反之亦然,以自己查看。
https://drive.google.com/drive/folders/19a-QPwOQKsn9PywUPd2DRnvUOml03nZ-?usp=sharing https://drive.google.com/drive/folders/19a-QPwOQKsn9PywUPd2DRnvUOml03nZ-?usp=sharing
If that's of any concern, I use TortoiseGIT 2.12.0.0 with Git for Windows 2.32.0.2.如果有任何问题,我将 TortoiseGIT 2.12.0.0 与 Git for Windows 2.32.0.2 一起使用。
You got that result because that is the correct result .你得到那个结果,因为那是正确的结果。
Well, let's modify that statement: that is the correct results by the rules of Git merge .好吧,让我们修改该语句:这是 Git merge 规则的正确结果。 (It's also correct by the rules of most other merge programs, as far as I know, but there are algorithms that would at least flag this for attention. Git does not use such an algorithm.)
(据我所知,根据大多数其他合并程序的规则,这也是正确的,但有些算法至少会将此标记为注意。Git 不使用这样的算法。)
If Git's merge result is not the result you wanted, you have remedies: see below.如果 Git 的合并结果不是你想要的结果,你有补救措施:见下文。
When Git does a merge, Git pays attention to three snapshots:当 Git 进行合并时,Git 会注意三个快照:
One snapshot is the current snapshot, ie, the commit whose hash ID you will get if you run git rev-parse HEAD
.一个快照是当前快照,即,如果您运行
git rev-parse HEAD
您将获得其哈希 ID 的提交。 If HEAD
is attached to a branch name (as it normally is), that's the tip commit of the given branch.如果
HEAD
附加到分支名称(通常是这样),那就是给定分支的提示提交。
One snapshot is the one you name on the command line: git merge foo
looks up foo
to get a commit hash ID.一个快照是您在命令行中命名的快照:
git merge foo
查找foo
以获取提交哈希 ID。
The third, and in many ways the most important, snapshot is the merge base .第三个,在许多方面也是最重要的,快照是合并基础。 (Git numbers this one "#1", with
HEAD
/ --ours
being #2 and the other / --theirs
being #3, internally, even though we have to locate the other two inputs before we can locate this merge base input.) The commit hash ID of the merge base is located through the commit graph. (Git 将这个编号为“#1”,
HEAD
/ --ours
为 #2,另一个 / --theirs
为 #3,在内部,即使我们必须先定位其他两个输入才能定位此合并基础输入.) 合并库的提交哈希 ID 通过提交图定位。 In your case, it's the commit just before the commit you are calling commit X.在您的情况下,它是您正在调用 commit X 的提交之前的提交。
Let's draw these commits like this, with newer commits towards the right and single uppercase letters standing in for each actual commit hash ID:让我们像这样绘制这些提交,将更新的提交放在右侧,单个大写字母代表每个实际提交哈希 ID:
X <-- master
/
...--G--H <-- here's where both branches start diverging
\
X'-X" <-- test1
Here, commit X
has the changes you made on master
;在这里, commit
X
包含您在master
所做的更改; commit X'
has the same changes, and X"
undoes those changes, so that the snapshot in X"
exactly matches the snapshot in H
.提交
X'
具有相同的更改,而X"
撤消这些更改,以便X"
中的快照与H
中的快照完全匹配。
Git's merge algorithm consists of doing the following, given that you're on master
(so that commit X
is the current / HEAD
commit) and are merging test1
(commit X"
): Git 的合并算法包括执行以下操作,假设您在
master
(因此提交X
是当前/ HEAD
提交)并且正在合并test1
(提交X"
):
Extract commit H
as "stage 1".将提交
H
提取为“阶段 1”。
Extract commit X
as "stage 2".将提交
X
提取为“阶段 2”。
Extract commit X"
as "stage 3". (Note that we have just established that the contents of X"
match those of H
.)将提交
X"
提取为“阶段 3”。(请注意,我们刚刚确定X"
的内容与H
的内容匹配。)
For each file in the index / stage, that exists in all three stage slots:对于索引/阶段中的每个文件,都存在于所有三个阶段插槽中:
For files that don't exist in all three slots (eg, where renames or copies may have happened), things get more complicated.对于不存在于所有三个插槽中的文件(例如,可能发生了重命名或复制的位置),事情变得更加复杂。 These can result in high level aka tree conflicts, which are treated as conflicts but don't show up as conflicted working-tree copies.
这些可能会导致高级别的树冲突,这些冲突被视为冲突,但不会显示为冲突的工作树副本。 But this case doesn't apply here so we get to ignore it.
但是这种情况在这里并不适用,所以我们可以忽略它。
The meat of git merge
is done, but there are now cleanup steps: git merge
已经完成,但现在有清理步骤:
Files that are correctly merged are dropped to slot 0 (and written out to the working tree if/as needed).正确合并的文件被放到槽 0(如果/根据需要写出到工作树)。
Files that have merge conflicts are left in all three slots;有合并冲突的文件留在所有三个插槽中; the working tree gets Git's best-effort at merging, including conflict markers.
工作树在合并时得到 Git 的最大努力,包括冲突标记。
In the case of conflicts, the merge now stops in the middle;在发生冲突的情况下,合并现在在中间停止; the user must complete it.
用户必须完成它。 The working tree and index copies of each file exist for the user's use here.
每个文件的工作树和索引副本都存在于此处供用户使用。
Otherwise, unless told to stop without committing, git merge
finishes the merge on its own, typically by creating a new merge commit .否则,除非被告知停止而不提交,否则
git merge
自行完成合并,通常是通过创建一个新的合并提交。
If this merge result is not what you wanted, your remedies include, but are not limited to, the following:如果此合并结果不是您想要的,您的补救措施包括但不限于以下内容:
Change the inputs (eg, by adding more commits to one or both branches).更改输入(例如,通过向一个或两个分支添加更多提交)。
Use git merge -n
so that git merge
stops before committing the merge result.使用
git merge -n
以便git merge
在提交合并结果之前停止。 Use the index and working tree files—all staged for commit now, with just one version of each file in index slot #0—to produce the result you'd like instead.使用索引和工作树文件——现在都暂存提交,索引槽 #0 中每个文件只有一个版本——来产生你想要的结果。 Then, commit the result.
然后,提交结果。 Note that this is known as an evil merge .
请注意,这称为邪恶合并。 There's nothing really wrong with it, but if you ever have Git repeat the merge—eg, using the fancy new
git rebase --rebase-merges
code—Git won't know to make the new merge an evil merge, so it's wise to mark this clearly with the commit message or something.它没有什么问题,但是如果你让 Git 重复合并——例如,使用花哨的新
git rebase --rebase-merges
代码git rebase --rebase-merges
不会知道让新合并成为一个邪恶的合并,所以明智的做法是用提交消息或其他东西清楚地标记这一点。
Do the merge, let it be "wrong", commit it (and maybe mark it, especially for later skip-during-bisect), then add a commit to fix things.进行合并,让它成为“错误的”,提交它(并且可能标记它,特别是对于稍后的skip-during-bisect),然后添加一个提交来修复问题。
In the situation you describe : this is the expected result of the git merge
operation -- and torek's answer gives a detailed description of why.在您描述的情况下:这是
git merge
操作的预期结果——torek 的回答详细说明了原因。
On the git side of things :在 git 方面:
One issue with the workflow you mention is that git cherry-pick
doesn't register a link to the original commit.您提到的工作流程的一个问题是
git cherry-pick
没有注册原始提交的链接。 You have to know that somehow, managing a cherry-pick
is a bit like managing a "copy / paste" in code : if a bug is fixed on one side it should be manually ported to the other side.您必须知道,不知何故,管理
cherry-pick
有点像管理代码中的“复制/粘贴”:如果在一侧修复了错误,则应手动将其移植到另一侧。
The same goes for git rebase
by the way : if you apply a rebase action which somehow keeps the original branch and also creates a new branch with some copied commits, that's also copy/pasting commits.顺便说一下,
git rebase
也是如此:如果您应用了一个 rebase 操作,该操作以某种方式保留了原始分支,并且还创建了一个带有一些复制提交的新分支,那么这也是复制/粘贴提交。
A more generic principle is :一个更通用的原则是:
" git merge
succeeds without conflicts" doesn't mean "the resulting code is free of bugs". “
git merge
没有冲突的情况下成功”并不意味着“生成的代码没有错误”。
You should always validate that the resulting code matches your expectations :您应该始终验证生成的代码是否符合您的期望:
This is especially true for your release branch ;对于您的发布分支尤其如此; on occasions, such as the one you describe (the developer merged towards a side branch), it's probably more about your team being aware of the potential traps -- and check the diff.
有时,例如您描述的那个(开发人员合并到一个分支),这可能更多是关于您的团队意识到潜在的陷阱——并检查差异。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.