Five advanced Git merge techniques
January 18, 2010Have you ever performed a merge in Git and not have it quite turn out the way you wanted it to? For example, you accidentally converted all of your UNIX line endings to DOS line endings, and now the entire file reports a conflict? Maybe you see a conflict that you don’t really care about resolving, and want to resolve as theirs? Or perhaps the conflicted file is empty and you can’t figure out just what happened there?
Here are some advanced techniques you can apply to your conflicted merges to make things go a little easier. Many of them utilize Git plumbing; that is, the internal commands that interface directly with the bare metal Git abstractions: the index, the tree, the commit graph. Others are as simple as flipping a configuration switch.
Turn
diff3conflicts usinggit config --global merge.conflictstyle diff3. Thediff3conflict style adds an extra section between the new|||||||marker and=======markers, which indicates the original contents of the section, with your changes above and their (the branch that is being merged in’s) changes below.diff3is a powerful way of reestablishing context of a change you made several months ago (to see the changes you made, compare the middle section with the upper section; for the changes they made, compare the middle section with the lower section), and there is really no good reason not to have this on by default.If you’ve come in from Subversion, you may be familiar with the
FILE.mine,FILE.r2(the original you worked with) andFILE.r3(the latest version checked in) files, as well as the ability to runsvn resolve --accept theirs-fullormine-full, which says “I don’t care about the other changes, just use this version of the file.” Git offers similar facilities utilizing the merge parents, although they’re a little more hidden.You may be already familiar with the
git showcommand, which lets you view commits as well as arbitrary blobs inside the tree of any given commit. When you are inside a merge, you can use a special:N:syntax, whereNis a number, to automatically select one of the merge parents.1selects the common base commit (the lower revision),2selects your version (“mine”), and3selects their version (the higher revision). Sogit show :3:foobar.txtshows the upstream version offoobar.txt.To actually use one of these versions as the merge resolution, use
git checkout {--ours|--theirs} filename.txt.When you’re in a conflict,
git diffwill give you the deep and dirty of all the conflicts that occurred, sometimes this is too much information. In that case, you can rungit ls-files -uto view all of the unmerged files (this is also a lot faster thangit status, and will omit all of the files that were merged properly.)You may notice that there as many as three copies of a file inside the list; this tells you the state of the “common”, “ours” and “theirs” copies mentioned previously. If 1 (common) is missing, that means that the file appeared at the same time in our branch and their branch. If 2 (ours) is missing, it means we deleted the file, but it got a change upstream. If 3 (theirs) is missing, it means we made some changes, but upstream deleted the file. This is especially useful if a file is conflicted, but you can’t figure out why (since there are no conflict markers.)
Sometimes life gives you lemons. Many people suggest you make lemon juice. However, if Git gives you a really bad set of conflict markers, for example, you accidentally flipped the newline style for one of the files, so now the entire file conflicts, don’t settle for that: redo the merge for that file. You can do this with the handy
git merge-filecommand. This runs a three-way file merge, and takes three arguments: the current file, the common file, and the upstream file, and writes out the merge into the current file (first argument). Usegit showto dump out your file, the common file and upstream file, do whatever changes to those files you need (for example, rundos2unix), rungit merge-file mine common theirs, and then copy themineover the old conflicted file. Voila, instant new set of conflict markers.If you discover a global conflict relatively early in the merge process, and it was your fault, it might be easier to back out of the merge
git reset --hard, fix the mistake, and try merging again. However, if you’ve already made substantial progress merging a copy, re-merging just a single file can be a lifesaver.Don’t merge, rebase! Instead of running
git pull, rungit pull --rebase. Instead of runninggit merge master, rungit rebase master. Your history will be much cleaner as a result, and you want have to go on a massive rebase marathon later if you want to submit your patches upstream.
Now, go forth and merge to thy hearts content!
8 Comments