Resolving Git Conflicts - Diff and Merge Tooling

You’re merging or rebasing your branch and get the dreaded:

CONFLICT (content): Merge conflict in critical_file.rb
Failed to merge in the changes.

Now what?

To be honest, I generally open the file and resolve the diff by hand. If your branches are small and short-lived this isn’t generally super difficult, but it’s certainly not efficient either. There are several tools that can assist, but unless you’re practiced in them, they can be intimidating.

What process do you follow to resolve conflicts? What are your tools?

We’ll be discussing diff and merge tooling, terminology, and process at the thoughtbot Boston developer discussion tomorrow afternoon and will post our notes and any follow-up here.

I’ve created a repository that can be used to practice conflict resolution. The master branch contains a copy of @pat’s GitHub watchme script. dp-octokit-port switches out the curl calls for the Octokit library. dp-auto-subscribe also introduces Octokit, but simply to automatically subscribe to unwatched repos. Attempting to rebase dp-octokit-port onto dp-auto-subscribe will give you a nice merge conflict to play with.

I almost always merge by hand. I was once told that if I’ve never used a 3-way-merge viewer that I’m not working on a mature enough codebase, but I’m pretty sure that guy was overcompensating for his ancient development practices (uphill, both ways, in the snow kind of comment).

Sure, this makes cross cutting concerns bad (like for the I18n change we just did), but I don’t know if there’s really a way to speed that part up. The slow part isn’t the viewing the merge, it’s knowing what parts of each branch can get used.

I always merge by hand. It never feels like there’s anything needed more than vim, fugitive, /<<<, /====, and />>>. Every time I’ve tried a visual merge tool, it always takes way too much mental processing to figure out what code appears where if I select “mine” vs. “theirs”.

1 Like

I’ve been trying this approach for complex merges: A better Vimdiff Git mergetool | Vim Tips Wiki | Fandom

I’ve also been using vimdiff for some of my non-conflict diffs.

I’ve also occasionally used opendiff, which comes with OS X.

Today’s development discussion was interesting. We had several difficulties actually demoing some of the tools, largely because complicated merge conflicts don’t happen enough for anyone to be practiced enough with them to fully understand their chosen tools.

Just about everyone in the room said they manually resolve conflicts by editing the file directly as desired. It was pointed out that this can be exceedingly difficult when, for instance, the conflicting change included re-indentation (say, a wrapping module was added to the file). It can be very difficult to see the actual changes and tools can be really helpful in those cases.

@jferris demoed the setup he’s been using which was the only of the three attempts at demoing vimdiff that actually worked correctly. It was also very handy to have three different tabs available with the different views of the conflict (two pane, three pane, conflicted file).

@gfontenot had some praise for kaleidoscope. While it does not (yet) support syntax highlighting, it does offer block, fluid, and unified layouts in both two pane and three-pane setups. While it previously only supported diffing, it has recently added support for merging. When merging it’s possible to directly modify the working copy, which isn’t the case with opendiff (the default git mergetool on OS X, at least). It also supports switching between several conflicted files easily, which is not the case with vimdiff in general.

We spent some time discussing some git configuration options that effect merging and diffing. It sounded like these were helpful options that it may be worth discussing/adding to dotfiles:

[mergetool]
  trustExitCode = true
  keepBackup = false

trustExitCode tells git that if the mergetool exits cleanly it should assume the conflict has been successfully resolved. By default, using git mergetool will save a backup file with the .orig extension. The keepBackup setting disables this.

If you find yourself in a situation where you need to have a long-running branch, you should rebase often in order to avoid merge conflicts. You’re still likely to get them, though. You may even get the same conflict several times. git rerere may be able to help with this by remembering how you resolved the previous versions of the same conflict and applying that in the future (reuse recoreded resolution). This might be worth enabling temporarily while working in this situation.

SInce I never used a Mac (yet) and am learning development on a Linux, I use git diff to check things out first. If I have a merge conflict that really threw things out of whack, which has only happened to me ONCE since I began learning computer programming this past April, I go in and start untangling it by hand. I learned how to do that during the Rails Girls Summer of Code.

What about conflicts in css or i18n files that you’ve reorganized but get new content? How to avoid keeping duplication?

@zamith, to be honest, I have not yet had to deal with that issue. I am a newbie dev, so maybe I’ve just been lucky so far.

The main problem is for css or i18n files that get too big, really. It makes it hard to spot duplication just by scrolling up and down the file. The obvious solution is not to have big files, but surely there are others.

@zamith: If you’re going to be making wholesale changes to an i18n or CSS file (say, alphabetizing keys in either), it makes some sense to do so when you’re reasonably certain you are unlikey to get a conflict. These modifications can be pretty quick - for instance the i18n_yaml_sorter gem can sort that yaml file for you quickly, allowing you to check in without much pain - conveniently, it will also eliminate duplication because the YAML file is loaded into a hash for sorting.

@derekprior Yes, that is a solution for that case. But sass files can have the same problem, even html if you use slim or haml. A good way of minimising this problem is by separating big files into small ones, but still in a big team a lot can happen. :wink:

Following the dev discussion last week I spent some time creating a Vim plugin, Conflicted, that wraps up the best features I could find for handling merge conflicts. It provides a command for loading the conflicted files into Vim and viewing them from a few different views (each in a tab), as well as mappings for resolving the conflicts.

Feel free to try it out and post any issues or suggestions to the repo.

https://github.com/christoomey/vim-conflicted

4 Likes

Thanks, @christoomey… I’m really liking your Conflicted tool! I’m just starting to scratch the surface, but it’s working well so far. I DEFINITELY need a good merge tool in my current environment: I’m writing Cucumber/Rspec code in a very large mobile/retail platform development project. There are at 30+ folks submitting changes, at least 5 committers and lots of activity.

What makes it really challenging is that we are also using the PageObject gem to ‘standardize’ access to the UI. Only problem is that every one of those 30+ can make changes to those central objects (and they do) resulting in very complex conflict/merge situations…

I started out using OpenDiff since that comes with the Mac (and it has a nice interface) but I kept running into ‘overlapping’ changes where I needed to accept part of the REMOTE’s changes and part of LOCAL’s changes and OpenDiff can’t handle that.

I finally settled on VimDiff because it can do all that, but it can get pretty complicated. Your conflicted plugin really simplified things for me – thanks very much!

@seangriffin I also resolve merge conflicts by hand but until you and I were pairing recently, I hadn’t thought to use Fugitive the way you do. Are you able to share some of the Fugitive commands you use during a conflict?

@seangriffin, interested to hear what you are using. @croaky I use :Gwrite from fugitive after resolving merge conflicts in a file to stage it. vim-conflicted wraps around fugitive’s :Gdiff which will do a three way diff in the case of a conflict.

In addition, if you have tpope’s unimpaired.vim, you can use ]n and [n to jump between conflict markers. It also provides a mapping, cod (for “change option diff”) to toggle diff view on and off. This is really useful for getting a view of the merged / middle file in a 3 way diff.

I just use :Gsta to see the conflicted files, press o, and do />>> to find the conflict. It’s lo-tech, but it works. :Gdiff can be helpful as well if the conflict is hard to parse mentally, since it’ll show a 3-way diff on a conflicted file.

+1 on unimpaired’s ]n commands, those are a lifesaver. Resolving conflicts is one of those things where I always feel like I should have a better vim technique, but I also end up doing it manually.