问题
I did a bit unusual setup where repositories were first set up, instead of populating the master
branch with project files, a transfer
branch was created and populated. Then, files were pull into development
via PR and then again into master
via another PR.
And, going forward, topic branches are created off development
branch. Once done, PR was created for topic
--> development
and another PR for development
--> master
. But, I keep getting merge conflict for second PR development
--> master
such as Added in Both, Edited in Both etc on Azure DevOps.
Right now, only squash merge is used to complete PR into both development
and master
.
What I want to know is whether those merge conflicts I am seeing happen due to the original setup of the repository or squash merge. The diff
of PR for development
and master
also contains a lot of changes I do not expect, too.
I can't get my head around it.
回答1:
The conflicts happen because you are using "squash" merge and continuously reintegrate one branch. With "squash" you are not doing a real merge (in other words: the history is not connected). Instead you are creating a single new commit on master
branch.
Later, when you try to merge development
to master
, all previous changes from development would be merged again (but they already exist in master
). That's why you see conflicts.
I see 2 options:
- Don't use "squash" at all for re-integration branches and use regular merges
- Re-create the
development
frommaster
after every single merge
回答2:
Just to build on knittl's existing answer, here's a scenario you can enact in the comfort of your own home to demonstrate how easily a so-called "squash merge" can result in a conflict.
We start by making file on master
and committing it:
$ git init
$ echo "a" >> a.txt
$ git add .
$ git commit -m'start'
Now we create develop
and modify that file:
$ git branch develop
$ git checkout develop
$ echo "b" > a.txt
$ git add .
$ git commit -m'changed a to b'
We return to master
and do a "squash merge", and it appears to work fine:
$ git checkout master
$ git merge --squash develop
$ git commit -m'a squash commit from develop'
So far so good. Now we make a terrible mistake: we do it again. We switch to develop
and modify that file some more:
$ git checkout develop
$ echo "c" > a.txt
$ git add .
$ git commit -m'changed b to c'
And we return to master
and do a "squash merge" again:
$ git checkout master
$ git merge --squash develop
Aaaaaand we get a conflict. Game over.
What happened? Well, the problem is that a "squash merge" is not a merge. It's a sort of self-inflicted patch. It constructs a configuration of the index (staging area), and you commit it. In my enactment above, we committed it as a separate step; with GitHub, it is committed for you. But the point is that that commit, although it contains the changes that would accrue from a real merge, is not a merge commit: it is just a normal commit, as if you had done those changes yourself, working directly on master
.
Well, what does it mean to make the changes that would accrue from a real merge? To answer that, you have to know how a merge works:
A merge starts by calculating the common point from which the merging branches diverged: the merge base.
It then calculates two diffs: From the merge base to the end of the first branch, and from the merge base to the end of the second branch.
Finally, it enacts both diffs on the merge base.
Okay, so try that yourself, using a thought experiment.
For the first "squash merge" in my scenario above, the merge base is "start", where a.txt is "a". So:
On
master
, it is still "a", so there is no diff to enact.On
develop
, it is "b", so the diff is change-"a"-to-"b".
So, to form the "squash merge" commit, we just change "a" to "b".
Very well, let's proceed to the second "squash merge" in my scenario. Here's the thing: the merge base is still "start", where a.txt is still "a". So:
On
master
, the diff is change-"a"-to-"b".(Note that
master
doesn't "know" that this happened because of any kind of merge; it thinks that this change was performed independently by someone working directly onmaster
.)On
develop
, the diff is change-"a"-to-"c".
But you can't do both; that's a conflict!
So you see, the reason why reusing a branch that was previously squash-merged is troublesome is that the merge base does not move (and nothing in the history involves any merging); and so every successive squash merge is a potential conflict with an earlier "squash merge" of the same branch.
回答3:
The existing answers are great for explaining why this happened. I'd like to simply quantify an easy rule of thumb to determine which Merge Types you can use for which branches. Azure DevOps has the following options when completing a PR:
- Merge (no fast-forward)
- Squash Commit
- Rebase and fast-forward
- Semi-linear merge
It's OK to use any of those options when completing a PR from topic
into development
depending on your preference. But all commits in development
and master
are sacred and should not be changed. Note that options 2-4 all potentially rewrite commits on the source branch. For that reason you should:
Only use the first option of Merge when completing a PR when the source branch is
development
ormaster
.
Azure DevOps allows you to lock down merge types into a branch as the target, but unfortunately I don't think you can do it for when the branch is the source.
来源:https://stackoverflow.com/questions/65041738/git-merge-conflicts-during-prs