Strategies for difficult git merges

In simple cases, you can merge two branches in git very easily:

git checkout head
git merge my-feature-branch

This works well for feature branches, where you split the repository temporarily, to keep work separate. Note that it is worth naming the feature branches after ticket numbers, if you are going to make a lot of them, and if you use “/” in the name, some tools (e.g. SourceTree use this as a foldering scheme).

You can also have larger discrepancies between branches – for instance a “next product version” branch and a support branch. Moving a feature branch from one to the other can be a bit more painful.

One option is to cherry-pick a range of commits:

git checkout head
get cherry-pick abcdef...ffffff

This works pretty well, although it is applying diffs, so if that fails you need a better approach. In some cases, branches diverge too far to easily merge, and you have to manually copy changes (at this point you don’t have a “merge”, although it may feel like one). This is more commonly the case if the “merge” process involves fixing imports, library dependencies, or rewriting features to match how they are implemented in a new product release.

In order to make this, or any type of merge, successful, you ideally should identify some tests you can run – unit tests, making sure your application starts, and any useful utility commands in your build system (often these are easier starting points than “does the whole app work).

For a manual merge process, there are typically several steps:

  1. Determine which repositories must be merged (if more than one)
  2. Determine the commit range that needs to be moved, for each library
  3. Starting at the lower level dependencies, make a feature branch with all changes in one commit
  4. Squash this branch, so that it represents all changes combined (this helps avoid rework)
  5. From this commit, make a list of all files that require changing
  6. Apply each file manually (I find it helpful to start at the end of the list, as these changes are typically smaller)
  7. Once files are merged, make a commit. Subsequent commits should fix compilation errors, tests, and so on.
  8. Once the process is complete, you can delete the feature branch

To make the feature branch of one commit, first determine the commit range you care about.

If the end is tagged, it makes it easier:

git checkout old-release-2
git checkout -b AAA-12345-merge

Now, from this new branch you can rebase from where it starts:

git rebase -i d4635b46583903e24278abd1e19081f78278762a

On Windows, this opens a pop-up window listing all the commits. Here, change “pick” to “squash” on every row except the first (make no other changes). E.g.:

pick 81bea4a ABC-3342 Commit 0
squash 91aeec8 ABC-1243 Commit 1
squash 8a2b4e3 AAA-1234 Commit 2

You’ll then get a second pop-up to set the commit message (this isn’t something you’ll push, or keep long-term, so it doesn’t matter).

From here, you can now copy the names of all the files in the commit to a text file, and check out the branch you want to merge into, e.g.:

git checkout my-latest-version

From here you can attempt to cherry-pick the merge commit in if it is large. I’ve found that when I try this, certain files have major conflicts, so I have to reset them and apply by hand, but the simple changes can be kept. After cherry pick, you can see a list of files with conflicts with:

git diff --name-only --diff-filter=U

And that’s it – hopefully if you have this type of problem you have a long-term strategy to get out of it (e.g. smaller branches, or not making more breaking refactorings)