How to approach a large merge with many conflicts that are not all best handled by the same person?

Viewed 56

Say we have a develop branch and a feature branch. The develop branch receives many updates, very frequently, from dozens of developers. It often gets very far ahead of the feature branch.

On one occasion, a merge from develop into feature results in 300 low-level conflicts. These conflicts touch many different areas of the codebase, and no single developer is familiar with all of it. Therefore, it is difficult for any single developer to be able to resolve every conflict on his local machine in order to push a logically correct, conflict-free version of the project.

I understand that the best solution would be to avoid this situation. However, precluding that - due to organizational dysfunction, fait accompli, etc., this situation exists - how best to use git to extricate oneself?

My understanding of git is that it is not possible to commit anything that remains in a conflicted state; by staging, you are indicating that it is not conflicting. Yet, that seems to require that any and all conflicts become the unshared, un-collaborated-upon responsiblity of the person doing the merge. This is undesirable because the conflict resolution is a large task, with parts best handled by separate people.

Two potential ways out occurred to me. However, neither one sounds great. First, I contemplated trying to resolve what conflicts I could, committing those resolutions only, and then leaving other developers to the parts to which they are suited. However, as far as I know, git does not support any kind of interim merge commits, and indeed won't let you begin a merge and subsequently commit unless you locally add every conflicted file, thus marking it resolved. I believe this answer supports my apprehension.

That led me to my second idea, which was to merely fix what I could, and commit the rest including conflict markers. Fortunately, I was able to resolve all high-level conflicts (file renames/deletions, etc.) but if I hadn't been, those would remain an issue. Setting aside that the feature branch would be in a non-compiling state, I am concerned that git would no longer consider these files to be in conflict; it would mean that we would have to rely on our IDEs and other tools to detect the git conflict markers.

My preference would be to only commit files that are actually resolved, allowing other developers to pull them, while files that are not actually resolved remain indicated as conflicted, to them.

I also noticed that there is a --no-commit option available to git merge, and I am wondering if this is the way to go, since I presume that it would allow me to only commit files I've actually reviewed, understood, and resolved, while leaving the remaining conflicts uncommitted locally.

Is there a more effective strategy to use git to split responsibility for this merge among several developers?

2 Answers

There are ways to cope with it... and they are not beautiful. Say, merge on a separate branch that will be used to... let's say, stabilize it..... keep a list of the conflicted files. Commit the results with conflicts (awful, I know..... it will make sense in the end, continue reading). Let the developers now work on that branch to solve the conflicts (provide the list of files, assign who will work on what and so on).... keep on working on this branch (with pull requests, if you deem it necessary).... normal development flow... well, normal within merge conflict resolution.

When it is finally stabilized, all conflicts resolved and so on (and here's the beauty of git), just squash all revisions used to stabilize it and amend the merge revision to make it look like there was no stabilization process.... it will look like you simply merged without any need for stabilization. So... something like:

git checkout -b stabilize main
git merge the-other-branch
# you get a gazillion conflicts....
git status > status_merge.txt # keep the list of stuff
git add .
git commit -m "Merging with conflicts.... this is not finished"
# let's set a temporary branch on this revision, so that it is easier to go into the final steps
git branch broken-merge
git push the-remote stabilize

Now pass around the list of files to work on and let your developers have fun on top of that branch until it is finally resolved.... when it is resolved, you can come back to wrap it up:

git fetch the-remote
git checkout the-remote/stabilize
git reset --soft broken-merge # this is your magic wand in action
# now, all the changes that developers had to do to stabilize the branch are in index
git commit --amend -m "Real merge, stabilized"
# and now you have the _real_ results of the merge _replacing_ the old completely broken revision, like it never happened... and you get the credit for it :-)
git branch -f main # let's set main over here... this assumes that main hasn't moved since stabilization started
git push the-remote main
# take a bow

And that's it. It's not the most elegant, I agree.... but it would work.

If you would like to keep the work that was used to stabilize the branch, I would recommend to create a new revision with the plumbing command that allows us to create them out of thin air, so replacing the second part of the recipe starting with the fetch:

git fetch the-remote
git branch temp $( git commit-tree -p main -p the-other-branch -p the-remote/stabilize -m "Merging main and the-feature-branch" the-remote/stabilize^{tree} )
# commit-tree is the plumbing command that creates revisions at will. 
# That command will spit out a revision ID that, thanks to $( )
# we use as input to git branch temp to tell git on what revision
# we want temp to be.
#
# Said revision has main, the-feature-branch _and_
# the-remote/stabilize as parents _and_ has the tree
# (files/contents) of the-remote/stabilize, which is
# the final result of the merge.
#
# if you like how it looks/feels, let's put main over here
git checkout main
git merge temp # this should do a ff to that revision we created
# we can now delete temp
git branch -d temp
git push the-remote main

As a final detail, this assumes that main/the-feature-branch do not move while stabilization is taking place..... if that is not the case (the branches do move), then you need a little more work to get it right.

branches (main, the-feature-branch) do move while stabilization is taking place

In the first recipe, second part, it would work like this:

git fetch the-remote
git checkout the-remote/stabilize
git reset --soft broken-merge # this is your magic wand in action
# now, all the changes that developers had to do to stabilize the branch are in index
git commit --amend -m "Real merge, stabilized"
git branch temp
# and now you have the _real_ results of the merge _replacing_ the old completely broken revision, like it never happened... and you get the credit for it :-)
# unfortunately main has moved, so we need to _merge_ this into main
git checkout main
git merge the-remote/main # or pull... but we have already fetched recently, right? so no _real_ need to pull
git merge temp # merge the stabilized result of the merge between main and the-feature-branch started back in time
# you might get conflicts coming from the work that was introduced in main after the point where we started to stabilize
# assuming that the merge is finished:
git push the-remote main
# we can now delete temp
git branch -d temp
# take a bow

If you are going with the second proposal (keeping the stabilization effort in history), the second part of the recipe becomes:

git checkout main
git pull # get the contents of main and the new position of the stabilization branch (that is finished, right?)
git branch temp $( git commit-tree -p broken-merge^ -p broken-merge^2 -p the-remote/stabilize -m "Merging main and the-feature-branch" the-remote/stabilize^{tree} )
# just like the previous commit-tree with the recipe with
# main/the-feature-branch not moving _but_ using the
# old positions of main/the-feature-branch as parents
git merge temp # merge back into main.... could get conflicts because of the things that have gone into main since we started to stabilize
# we can now delete temp
git branch -d temp
git push the-remote main

Unfortunately, any commit you make after git merge is the "final result" as far as Git itself is concerned. That's why eftshift0 suggests making an extra, bogus, temporary branch on which you and others build a bunch of temporary results to make progress towards the final real merge.

The way this works in a sort of graphical nutshell is like this:

                 ----------
                /          \
       o--...--o   <-- br1  \
      /         \            \
...--*                   \
      \         /              \
       o--...--o   <-- br2      \
                \                \
                 -----------------1--2--3   <-- eww

The entire "eww" branch consists of repeated attempts to refine the deliberately-crappy merge whose first attempt is the shreds left by Git at the first scream (). Once you finally come up with the pile of gold that you're sure must be underneath all that other ... stuff, you return to the attempt to merge br1 and br2 again. You run git merge again, which gets all the same conflicts, but then you remove everything and substitute in the files from n, which is the pile of gold.

Git badly needs some better tools here. There is mhagger's imerge, which takes a very different approach and might also be useful here.

Related