I just encountered this issue for the third time in my life as a coder. This time I decided to find the root cause, so I spend some time to produce a minimum case.
https://github.com/myst729/git-diff-merge-loss
The steps:
Two features checkout their own branches (a
and b
) respectively, from the same base master
.
The change of feature A has been merged: https://github.com/myst729/git-diff-merge-loss/pull/3/files?w=1
Feature B has only one line changed. The point is, this line (line 70) is within the moved block in feature A https://github.com/myst729/git-diff-merge-loss/pull/4/files
Feature B cannot be merged unless conflict resolved. However, line 70 in feature B has been moved to line 43 in master
now. It's not even inside the conflict block. See line 43 in https://github.com/myst729/git-diff-merge-loss/pull/4/conflicts , or run git merge master
locally on branch b
This is a minimum case, but in real development, the feature may be more complicated, thus the change may be accidentally excluded when resolving conflicts. And ideally, two features that have dependent relationship should not be developed in parallel. But sometimes we have to form a special task force and rush for a deadline.
So the questions are:
Just to supplement my earlier comments, I'm going to turn them into an answer. What I said was:
You just have to use your brains when you resolve the conflict. However, you'd use them a lot better if you had a better view of what's happened! Configure your merge conflict style to
diff3
so that you are shown the original state of affairs.
To see what I mean, consider first what your merge-conflicted file shows. As you display in your screen shot, on the one hand we have HEAD, which is branch b
:
<el-form-item label="node:" prop="orgIdList">
<org-tree-cascader
:onlyAuthorized="true"
:defaultProp="{
expandTrigger: ExpandTrigger.HOVER,
multiple: true,
value: 'id',
label: 'name',
emitPath: false,
checkStrictly: false,
}"
class="item-width"
placeholder="selectnode"
@updateOrgListValue="(handleUpdateOrgIdList as any)"
ref="orgTreeCascaderRef"
></org-tree-cascader>
<!-- <el-select
v-model="state.form.orgList"
:loading="state.orgLoading"
multiple
collapse-tags
clearable
placeholder="selectnode"
size="mini"
class="item-width"
popper-class="exp-list-filter-select"
>
<el-option
v-for="item in state.orgList"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select> -->
</el-form-item>
On the other hand, we have master
, which is empty. This greatly reduces the likelihood that a human being is going to notice what has happened.
What has happened?
In the LCA (the commit from which b
"split off" from master
), there is only one occurrence of the entry for
<el-form-item label="node:" prop="orgIdList">
It is at line 61, and its checkStrictly
is true
.
In b
, into which we are merging, there is also only one occurrence of the entry for
<el-form-item label="node:" prop="orgIdList">
It is also at line 61, but it differs from the LCA in the value of checkStrictly
, which is now false
.
In master
, which we are merging, there is still only one occurrence of
<el-form-item label="node:" prop="orgIdList">
but it is at line 34, and its checkStrictly
is unchanged from the LCA.
What happened may thus be easily summarized: b
changed one line of the group, but master
moved the whole group.
Ah. It's because I know how to "ask questions" when we are paused during a merge conflict. In particular, I can say:
% git show :1:index.vue # the LCA version
% git show :2:index.vue # the `b` version
% git show :3:index.vue # the `master` version
So far, so good; but the practical problem, as you rightly say, is that the display of the merge-conflicted file itself, which I displayed at the start of this answer, is not very enlightening. In the merge-conflicted file, the line
<el-form-item label="node:" prop="orgIdList">
occurs twice — once at line 34, where master
has it, and again in the display of HEAD, showing where b
has it. But, as you rightly complain, the chances of a human being noticing this and working out what has happened, or even realizing that anything interesting has happened, seem very slim.
This is because, in part, your eye is drawn to the conflict area — and line 34, which is sort of the giveaway here, is not in that area.
But now let's say you have configured merge.conflictStyle
as diff3
. Then the LCA, which was not present in your version of the conflicted file, is present! Here is the entire display of the conflicted region in diff3
style:
<<<<<<< HEAD
<el-form-item label="node:" prop="orgIdList">
<org-tree-cascader
:onlyAuthorized="true"
:defaultProp="{
expandTrigger: ExpandTrigger.HOVER,
multiple: true,
value: 'id',
label: 'name',
emitPath: false,
checkStrictly: false,
}"
class="item-width"
placeholder="selectnode"
@updateOrgListValue="(handleUpdateOrgIdList as any)"
ref="orgTreeCascaderRef"
></org-tree-cascader>
<!-- <el-select
v-model="state.form.orgList"
:loading="state.orgLoading"
multiple
collapse-tags
clearable
placeholder="selectnode"
size="mini"
class="item-width"
popper-class="exp-list-filter-select"
>
<el-option
v-for="item in state.orgList"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select> -->
</el-form-item>
||||||| 1e59f94
<el-form-item label="node:" prop="orgIdList">
<org-tree-cascader
:onlyAuthorized="true"
:defaultProp="{
expandTrigger: ExpandTrigger.HOVER,
multiple: true,
value: 'id',
label: 'name',
emitPath: false,
checkStrictly: true,
}"
class="item-width"
placeholder="selectnode"
@updateOrgListValue="(handleUpdateOrgIdList as any)"
ref="orgTreeCascaderRef"
></org-tree-cascader>
<!-- <el-select
v-model="state.form.orgList"
:loading="state.orgLoading"
multiple
collapse-tags
clearable
placeholder="selectnode"
size="mini"
class="item-width"
popper-class="exp-list-filter-select"
>
<el-option
v-for="item in state.orgList"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select> -->
</el-form-item>
=======
>>>>>>> master
Between the display of b
at the top and master
(still empty) at the bottom, we now have the LCA. And thus we can now see clearly what happened between the LCA and b
: the value of checkStrictly
has changed.
We still do not necessarily realize what happened with master
; it looks like it simply deleted this stretch, whereas in fact it moved it, and it now appears at line 34, which is not in the conflicted area. But we do understand why there's a conflict! One side changed this text, the other side "deleted" it. That was not at all obvious before, because we didn't know what the initial state of things was.
But we are now a bit more likely to discover this, because the line
<el-form-item label="node:" prop="orgIdList">
now appears three times: once in the b
version, once in the LCA
version, and once at line 34! That fact should be enough to get us thinking.
And that brings us back to my original comment. The best tool for working out what to do now is to use your brains. You have two things to decide: where should the <el-form-item label="node:" prop="orgIdList">
entry go, and what should the value of its checkStrictly
be? That is the problem that Git has set us; it's a true conflict, a decision that Git cannot perform automatically on its own.
The point is merely to gather enough information so that you know that that is the decision you have to make. What I'm suggesting is that with your display of the merge conflict, you are unlikely to work it out. With diff3
, you are much more likely, and you could then use git show
, as I demonstrated earlier, to put your finger on the history of what has happened and decide how to resolve it.
Maybe I'm burying the lede here... Keep in mind that moving code is not a "thing" in Git's idea of a diff. If you move a line from one place to another distant place in the same file, that's two separate hunks: one where a new line appeared, and one where a line was deleted.
So in this case, nothing is going to get Git to see the code at line 34 as part of the merge conflict. It just isn't! It's a different area of the file from the conflict area — a place where some new lines appeared, that's all. And reading the conflicted file is never going to call this to your attention, as it is just not where the conflict is.
On the other hand, you, a human, can see quite well what happened if you say git diff b...master
. That diff will show you how code appeared at line 34 and disappeared at line 61, and you, the human, will know what this means: the code was moved.