Home git merging
Post
Cancel

git merging

Conflicts

1
2
3
4
5
6
7
<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

This means the version in HEAD (your master branch, because that was what you had checked out when you ran your merge command) is the top part of that block (everything above the =======), while the version in your iss53 branch looks like everything in the bottom part. In order to resolve the conflict, you have to either choose one side or the other or merge the contents yourself.

git mergetool

Aborting a merge

1
git merge --abort

The git merge --abort option tries to revert back to your state before you ran the merge. The only cases where it may not be able to do this perfectly would be if you had unstashed, uncommitted changes in your working directory when you ran it, otherwise it should work fine.

Ignoring whitespace

In this specific case, the conflicts are whitespace related. We know this because the case is simple, but it’s also pretty easy to tell in real cases when looking at the conflict because every line is removed on one side and added again on the other. By default, Git sees all of these lines as being changed, so it can’t merge the files.

-Xignore-all-space,-Xignore-space-change

1
git merge -Xignore-space-change br-whitespace

This is a lifesaver if you have someone on your team who likes to occasionally reformat everything from spaces to tabs or vice-versa.

Manual File Re-merging

First, we get into the merge conflict state. Then we want to get copies of my version of the file, their version (from the branch we’re merging in) and the common version (from where both sides branched off).

1
2
3
4
# The :1:hello.rb is just a shorthand for looking up that blob SHA-1.
git show :1:hello.rb > hello.common.rb
git show :2:hello.rb > hello.ours.rb
git show :3:hello.rb > hello.theirs.rb

If you want to get a little more hard core, you can also use the ls-files -u plumbing command to get the actual SHA-1s of the Git blobs for each of these files.

1
git ls-files -u
1
2
3
100755 ac51efdc3df4f4fd328d1a02ad05331d8e2c9111 1	hello.rb
100755 36c06c8752c78d2aff89571132f3bf7841a7b5c3 2	hello.rb
100755 e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd 3	hello.rb

Now that we have the content of all three stages in our working directory, we can manually fix up theirs to fix the whitespace issue and re-merge the file with the little-known git merge-file command which does just that.

1
2
git merge-file -p \
    hello.ours.rb hello.common.rb hello.theirs.rb > hello.rb

In fact, this actually works better than the ignore-space-change option because this actually fixes the whitespace changes before merge instead of simply ignoring them.

To compare your result to what you had in your branch before the merge, in other words, to see what the merge introduced, you can run git diff --ours:

1
git diff --ours

If we want to see how the result of the merge differed from what was on their side, you can run git diff --theirs.

1
git diff --theirs

Finally, you can see how the file has changed from both sides with git diff --base.

1
git diff --base -b

-b short for --ignore-space-change

-w shortfor --ignore-all-space

At this point we can use the git clean command to clear out the extra files we created to do the manual merge but no longer need.

1
git clean -f
1
2
3
Removing hello.common.rb
Removing hello.ours.rb
Removing hello.theirs.rb

Checking out Conflicts

git checkout –conflict

This will re-checkout the file again and replace the merge conflict markers. This can be useful if you want to reset the markers and try to resolve them again.

1
git checkout --conflict

You can pass –conflict either diff3 or merge (which is the default)

1
2
3
git checkout --conflict=diff3 hello.rb
# set diff3 as default
git config --global merge.conflictstyle diff3
1
2
3
4
5
6
7
8
9
10
11
12
13
#! /usr/bin/env ruby

def hello
<<<<<<< ours
  puts 'hola world'
||||||| base
  puts 'hello world'
=======
  puts 'hello mundo'
>>>>>>> theirs
end

hello()

The git checkout command can also take --ours and --theirs options, which can be a really fast way of just choosing either one side or the other without merging things at all.

This can be particularly useful for conflicts of binary files where you can simply choose one side, or where you only want to merge certain files in from another branch — you can do the merge and then checkout certain files from one side or the other before committing.

Merge Log

Another useful tool when resolving merge conflicts is git log. This can help you get context on what may have contributed to the conflicts. Reviewing a little bit of history to remember why two lines of development were touching the same area of code can be really helpful sometimes.

To get a full list of all of the unique commits that were included in either branch involved in this merge, we can use the “triple dot” syntax that we learned in Triple Dot.

1
git log --oneline --left-right HEAD...MERGE_HEAD
1
2
3
4
5
6
< f1270f7 Update README
< 9af9d3b Create README
< 694971d Update phrase to 'hola world'
> e3eb223 Add more tests
> 7cff591 Create initial testing script
> c3ffff1 Change text to 'hello mundo'
1
2
3
4
5
# --merge: it will only show the commits in either side of the merge that touch a file that’s currently conflicted.
git log --oneline --left-right --merge

#. If you run that with the -p option instead, you get just the diffs to the file that ended up in conflict. This can be really helpful in quickly giving you the context you need to help understand why something conflicts and how to more intelligently resolve it.
git log --oneline --left-right -p

Combined Diff Format

When you run git diff directly after a merge conflict, it will give you information in a rather unique diff output format.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ git diff
diff --cc hello.rb
index 0399cd5,59727f0..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,11 @@@
  #! /usr/bin/env ruby

  def hello
++<<<<<<< HEAD
 +  puts 'hola world'
++=======
+   puts 'hello mundo'
++>>>>>>> mundo
  end

  hello()

The format is called “Combined Diff” and gives you two columns of data next to each line. The first column shows you if that line is different (added or removed) between the “ours” branch and the file in your working directory and the second column does the same between the “theirs” branch and your working directory copy.

So in that example you can see that the <<<<<<< and >>>>>>> lines are in the working copy but were not in either side of the merge. This makes sense because the merge tool stuck them in there for our context, but we’re expected to remove them.

If we resolve the conflict and run git diff again, we’ll see the same thing, but it’s a little more useful.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
diff --cc hello.rb
index 0399cd5,59727f0..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
  #! /usr/bin/env ruby

  def hello
-   puts 'hola world'
 -  puts 'hello mundo'
++  puts 'hola mundo'
  end

  hello()

This shows us that “hola world” was in our side but not in the working copy, that “hello mundo” was in their side but not in the working copy and finally that “hola mundo” was not in either side but is now in the working copy. This can be useful to review before committing the resolution.

You can also get this from the git log for any merge to see how something was resolved after the fact. Git will output this format if you run git show on a merge commit, or if you add a --cc option to a git log -p (which by default only shows patches for non-merge commits).

1
2
git show ...
git log --cc -p -1

Undoing Merges

hard reset

1
git reset --hard HEAD~

The downside of this approach is that it’s rewriting history, which can be problematic with a shared repository.

if other people have the commits you’re rewriting, you should probably avoid reset.

This approach also won’t work if any other commits have been created since the merge; moving the refs would effectively lose those changes.

revert

If moving the branch pointers around isn’t going to work for you, Git gives you the option of making a new commit which undoes all the changes from an existing one. Git calls this operation a “revert”

1
2
3
# The -m 1 flag indicates which parent is the “mainline” and should be kept
# When you invoke a merge into HEAD (git merge topic), the new commit has two parents  `#1` and  `#2` 
git revert -m 1 HEAD

Our or Theirs Preference

By default, when Git sees a conflict between two branches being merged, it will add merge conflict markers into your code and mark the file as conflicted and let you resolve it. If you would prefer for Git to simply choose a specific side and ignore the other side instead of letting you manually resolve the conflict, you can pass the merge command either a -Xours or -Xtheirs.

1
2
3
git merge -Xours xxx
# for individual file merge
git merge-file --ours xxx

If you want to do something like this but not have Git even try to merge changes from the other side in, there is a more draconian option, which is the “ours” merge strategy. This is different from the “ours” recursive merge option.

This will basically do a fake merge. It will record a new merge commit with both branches as parents, but it will not even look at the branch you’re merging in. It will simply record as the result of the merge the exact code in your current branch.

1
git merge -s ours xxx

This can often be useful to basically trick Git into thinking that a branch is already merged when doing a merge later on. For example, say you branched off a release branch and have done some work on it that you will want to merge back into your master branch at some point. In the meantime some bugfix on master needs to be backported into your release branch. You can merge the bugfix branch into the release branch and also merge -s ours the same branch into your master branch (even though the fix is already there) so when you later merge the release branch again, there are no conflicts from the bugfix.

mergetool

1
git mergetool --tool-help

References

Basic Merging

Advanced Merging

This post is licensed under CC BY 4.0 by the author.