Git merge conflicts during PRs

孤街浪徒 提交于 2020-12-14 23:57:12

问题


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 from master 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:

  1. A merge starts by calculating the common point from which the merging branches diverged: the merge base.

  2. 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.

  3. 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 on master.)

  • 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:

  1. Merge (no fast-forward)
  2. Squash Commit
  3. Rebase and fast-forward
  4. 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 or master.

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!