Search code examples
gitgit-mergemerge-conflict-resolution

Re-apply merge, after preprocessing the branches?


I frequently have situations, where a git merge contains a lot of conflicts, that could be easily auto-resolved by changing one of the branches to match the other more closely. Sadly, I often notice the patterns only after manually resolving dozens of conflicts, that genuinely need manual review; Aborting the commit to apply the script would mean redoing those.

Is there some possibility to go back from a merge to the branches, without discarding work invested into manually resolving conflicts?


Lacking such a possibility, is it possible to run a script on the conflicted files, and subsequently "auto-resolve" conflicts, where both versions are now the same? E.g., let's say some files contain a lot of conflicts of the form

<<<<<<< HEAD
aaaaaaaa \todo{bbbbbbbb} cccccccc
=======
aaaaaaaa \TODO{bbbbbbbb} cccccccc
>>>>>>> other

It would be easy to resolve these conflict by running a sed one-liner across the affected files, but I don't know a tool, that would then remove the `<<<<<< ======= >>>>>>>" markers.


Usually the workflows that lead to such issues are non-standard situations, such as:

  • Having received an experimentally modified snapshot as a ZIP file, and later needing to merge my changes into the repository.

  • The reverse situation, where I want to merge third-party changes available only as snapshot, into my sources, with no common ancestor state being known: Experienced in hobbyist game modding, where it lead to hundreds of lines differing only by {sir/madam} becoming {reg33?sir:madam}.

  • Files independently created on both branches by exporting from a program. Different workflows led to a nearly identical state, except for minor differences such as \todo[inline] vs \todoinline, which drown out actually relevant differences while resolving merge conflicts.

  • Some files may have been pretty-printed on one branch, creating a lot of conflicts that would vanish, if pretty-printing had been applied on the other branch too, before merging.


Solution

  • Yes: use rerere, reuse recorded resolutions.

    If you do git config rerere.enabled true you turn on its ~auto~ mode, where it automatically runs when a merge stops with conflicts and again when you commit the result.

    This will be handy even in your case, where you're not going to commit, because you can get to where you're talking about, where you've done a lot of resolutions and realized you could avoid almost all the rest with a little preprocessing, and then do git rerere manually before aborting the merge.

    When rerere runs it (a) remembers new conflicts and new resolutions and (b) applies remembered resolutions to conflicts it's seen before. So in your situation, with auto mode turned on (I think that's almost universal among people who're mindful of it) it'd have noticed all the conflicts and reapplied any resolutions you'd shown it before, so when you get to the point where you've done all you can and are about to drop back and try again with preprocessed data you run git rerere manually, it sees any new resolutions you've come up with and ignores the remaining conflicts, because it's seen those before and it's got nothing for them. Then abort the merge, do the preprocessing, rerun the merge and the auto rerere will both resolve any conflicts it can recognize and notice any new ones.

    Be ready, though, Git can't recognize that two textually different hunks are semantically identical, so if your prettyprinter splits lines and such then even if you resolved the semantic conflict in the old text Git can't construct the analogous textual change in the new text.