Merge vs Rebase

I’m a n00b when it comes to working with git. Sometimes I have absolutely no idea what the difference between merge and rebase, so I set out to answer that. I was learning this as I went along. Here’s what I started out with:

   / - A1 --- A4
 / 
O (master)
 \
   \ - B2 --- B5
     \
       \ - C3 --- C6

There’s a TL;DR section at the end of the article.

Basically I created 3 different branches off of master, A, B and C. I made two commits on each, in sequence: A1, B2, C3, A4, B5, C6. Commits are branch-sequence to aid for identifying what happened. Commit messages are along the lines of branch A commit 1 or similar. The idea is that commit 4 came before commit 6, but after 3. You get the idea. I then copied the folder to a number of other folders so I can have a pristine starting state when I’m done with experimenting.

I’ll do merges / rebases, and after each step I print out what git log --pretty=oneline is giving me. Let’s begin!

Merge

First up: what happens when I merge branches into master? What happens when I merge branches into other branches? What’s the order going to be? Do I need to resolve merge conflicts? Am I going to end up with extra commits?

Merge A onto master

What I’ve done:

on git:master
$ git merge A
Updating 5f6ad13..479d1b8
Fast-forward
 index.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

Result:

479d1b8e8572ab00f6087a94f016221fcea7f52a branch A commit 4
101020f4114fdc59998f31e1f1d543c7d59561a0 branch A commit 1
5f6ad13998a96f721048fad544aff8cbbc13f2c7 One source of truth

Merge B onto master (after having merged A)

on git:master
$ git merge B
Auto-merging index.js
CONFLICT (content): Merge conflict in index.js
Automatic merge failed; fix conflicts and then commit the result.

Contents of the file:

var original = 'this is the original thing, where everything began',
<<<<<<< HEAD
    a1 = 'this is on branch A, commit 1',
    a4 = 'branch A, commit 4';
=======
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5';
>>>>>>> B

Resolved to:

var original = 'this is the original thing, where everything began',
    a1 = 'this is on branch A, commit 1',
    a4 = 'branch A, commit 4',
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5';

Git log:

ef2c1eff445c01d4b85c58f9dc4d9cb0d167bc16 Merge branch 'B'
6ff1a89a1fe294ac9cc581cabea4e9e766299522 branch B commit 5
479d1b8e8572ab00f6087a94f016221fcea7f52a branch A commit 4
d071bd08bd6a7d8fb552b527380209404c8b2668 branch B commit 2
101020f4114fdc59998f31e1f1d543c7d59561a0 branch A commit 1
5f6ad13998a96f721048fad544aff8cbbc13f2c7 One source of truth

Merge C onto master (+A +B)

on git:master
* git merge C
Auto-merging index.js
CONFLICT (content): Merge conflict in index.js
Automatic merge failed; fix conflicts and then commit the result.

Contents of the file:

var original = 'this is the original thing, where everything began',
<<<<<<< HEAD
    a1 = 'this is on branch A, commit 1',
    a4 = 'branch A, commit 4',
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5';
=======
    c3 = 'branch C commit 3',
    c6 = 'branch C commit 6';
>>>>>>> C

Resolved to:

var original = 'this is the original thing, where everything began',
    a1 = 'this is on branch A, commit 1',
    a4 = 'branch A, commit 4',
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5',
    c3 = 'branch C commit 3',
    c6 = 'branch C commit 6';

Git log:

c0951e78b47e8cae192e98258020a7f7e469f7e8 Merge branch 'C'
ef2c1eff445c01d4b85c58f9dc4d9cb0d167bc16 Merge branch 'B'
9846031af1ee0fdfd187d20bd8095a171f118ced branch C commit 6
6ff1a89a1fe294ac9cc581cabea4e9e766299522 branch B commit 5
479d1b8e8572ab00f6087a94f016221fcea7f52a branch A commit 4
d1473d178232aaa48e5ef73b7bf4582932554376 branch C commit 3
d071bd08bd6a7d8fb552b527380209404c8b2668 branch B commit 2
101020f4114fdc59998f31e1f1d543c7d59561a0 branch A commit 1
5f6ad13998a96f721048fad544aff8cbbc13f2c7 One source of truth

Hm... So merge keeps the time data, the history is the same order as commits happened, this is why the two merge commits are at the end. Now... I’ll create a new commit on A, and merge that into master again.

on git:master
$ git merge A
Auto-merging index.js
CONFLICT (content): Merge conflict in index.js
Automatic merge failed; fix conflicts and then commit the result.

Contents:

var original = 'this is the original thing, where everything began',
    a1 = 'this is on branch A, commit 1',
    a4 = 'branch A, commit 4',
<<<<<<< HEAD
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5',
    c3 = 'branch C commit 3',
    c6 = 'branch C commit 6';
=======
    a7 = 'branch A commit 7, after having merged A, B and C (until commit 6) into master';
>>>>>>> A

The reason why the first 3 lines are excluded from the conflict is because both branch A and master have those lines, they agree that that’s there. This is important! (and this is a note to self).

Resolved to:

var original = 'this is the original thing, where everything began',
    a1 = 'this is on branch A, commit 1',
    a4 = 'branch A, commit 4',
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5',
    c3 = 'branch C commit 3',
    c6 = 'branch C commit 6',
    a7 = 'branch A commit 7, after having merged A, B and C (until commit 6) into master';

Git log:

c5d17b20eb941d1bba8d2377bc2d9bb041ec14bb Merge branch 'A' (2nd)
af8e3b6a7990878e5ea55459b8f7825deb517961 branch A commit 7
c0951e78b47e8cae192e98258020a7f7e469f7e8 Merge branch 'C'
ef2c1eff445c01d4b85c58f9dc4d9cb0d167bc16 Merge branch 'B'
9846031af1ee0fdfd187d20bd8095a171f118ced branch C commit 6
6ff1a89a1fe294ac9cc581cabea4e9e766299522 branch B commit 5
479d1b8e8572ab00f6087a94f016221fcea7f52a branch A commit 4
d1473d178232aaa48e5ef73b7bf4582932554376 branch C commit 3
d071bd08bd6a7d8fb552b527380209404c8b2668 branch B commit 2
101020f4114fdc59998f31e1f1d543c7d59561a0 branch A commit 1
5f6ad13998a96f721048fad544aff8cbbc13f2c7 One source of truth

Merge C onto A

I’ve reset the repository to its original position (deleted the working directory I was playing with to obtain the above results, and copied the single source of truth to a different folder).

on git:A
$ git merge C
Auto-merging index.js
CONFLICT (content): Merge conflict in index.js
Automatic merge failed; fix conflicts and then commit the result.

Contents:

var original = 'this is the original thing, where everything began',
<<<<<<< HEAD
    a1 = 'this is on branch A, commit 1',
    a4 = 'branch A, commit 4';
=======
    c3 = 'branch C commit 3',
    c6 = 'branch C commit 6';
>>>>>>> C

Resolved to:

var original = 'this is the original thing, where everything began',
    a1 = 'this is on branch A, commit 1',
    a4 = 'branch A, commit 4',
    c3 = 'branch C commit 3',
    c6 = 'branch C commit 6';

Git log:

56b0bf763e41ff8a4958613e4edc860b1a7eccd5 Merge branch 'C' into A
9846031af1ee0fdfd187d20bd8095a171f118ced branch C commit 6
479d1b8e8572ab00f6087a94f016221fcea7f52a branch A commit 4
d1473d178232aaa48e5ef73b7bf4582932554376 branch C commit 3
101020f4114fdc59998f31e1f1d543c7d59561a0 branch A commit 1
5f6ad13998a96f721048fad544aff8cbbc13f2c7 One source of truth

Much the same as merging both A and B into master. Let’s merge B into master, and A (with C) into master again.

Merge B onto master and A+C on master after

on git:master
$ git merge B
Updating 5f6ad13..6ff1a89
Fast-forward
 index.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
$ git merge A
Auto-merging index.js
CONFLICT (content): Merge conflict in index.js
Automatic merge failed; fix conflicts and then commit the result.

Contents:

var original = 'this is the original thing, where everything began',
<<<<<<< HEAD
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5';
=======
    a1 = 'this is on branch A, commit 1',
    a4 = 'branch A, commit 4',
    c3 = 'branch C commit 3',
    c6 = 'branch C commit 6';
>>>>>>> A

Pay attention to the fact that the lines for branch B now come before line A. Functionally this doesn’t mess up javascript, but because they are logical blocks for the merges, it could trip you up.

Resolved to:

var original = 'this is the original thing, where everything began',
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5',
    a1 = 'this is on branch A, commit 1',
    a4 = 'branch A, commit 4',
    c3 = 'branch C commit 3',
    c6 = 'branch C commit 6';

Git log:

a8e27487a32ea1c85fd9c8344ce0bbdd92be9eef Merge branch 'A+c' onto master+B
56b0bf763e41ff8a4958613e4edc860b1a7eccd5 Merge branch 'C' into A
9846031af1ee0fdfd187d20bd8095a171f118ced branch C commit 6
6ff1a89a1fe294ac9cc581cabea4e9e766299522 branch B commit 5
479d1b8e8572ab00f6087a94f016221fcea7f52a branch A commit 4
d1473d178232aaa48e5ef73b7bf4582932554376 branch C commit 3
d071bd08bd6a7d8fb552b527380209404c8b2668 branch B commit 2
101020f4114fdc59998f31e1f1d543c7d59561a0 branch A commit 1
5f6ad13998a96f721048fad544aff8cbbc13f2c7 One source of truth

The end result with regards to history is exactly the same as the previous version.

Anyway, let’s rebase!

Rebase

I can’t rebase any of the branches on top of master, because they’re already up to date. Rebase basically moves the origin point of the changes to somewhere else. Let’s begin!

Rebase A on top of B

on git:A
$ git rebase B
First, rewinding head to replay your work on top of it...
Applying: branch A commit 1
Using index info to reconstruct a base tree...
M   index.js
Falling back to patching base and 3-way merge...
Auto-merging index.js
CONFLICT (content): Merge conflict in index.js
Failed to merge in the changes.
Patch failed at 0001 branch A commit 1
The copy of the patch that failed is found in:
   /Users/javorszky/Sites/mergeathon-rebase/.git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

Yay! \o/

Contents:

var original = 'this is the original thing, where everything began',
<<<<<<< HEAD
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5';
=======
    a1 = 'this is on branch A, commit 1';
>>>>>>> branch A commit 1

Hm, this is only one commit. Let’s resolve that. Note that right now you can’t commit (or shouldn’t). You still need to add the conflicting files with git add <file>. I just did git add ..

Resolved to:

var original = 'this is the original thing, where everything began',
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5',
    a1 = 'this is on branch A, commit 1';

Continuing...

$ git rebase --continue
Applying: branch A commit 1
Applying: branch A commit 4
Using index info to reconstruct a base tree...
M   index.js
Falling back to patching base and 3-way merge...
Auto-merging index.js
CONFLICT (content): Merge conflict in index.js
Failed to merge in the changes.
Patch failed at 0002 branch A commit 4
The copy of the patch that failed is found in:
   /Users/javorszky/Sites/mergeathon-rebase/.git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

Yay! \o/ #2

Contents:

var original = 'this is the original thing, where everything began',
<<<<<<< HEAD
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5',
    a1 = 'this is on branch A, commit 1';
=======
    a1 = 'this is on branch A, commit 1',
    a4 = 'branch A, commit 4';
>>>>>>> branch A commit 4

Whoa, whoa, whoa! Why is a1 in there twice? Have I done a bad thing?! :( (By the way, because one ends with a ,, the other with a ;, so there’s no agreement between them that they match.)

Resolved to:

var original = 'this is the original thing, where everything began',
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5',
    a1 = 'this is on branch A, commit 1',
    a4 = 'branch A, commit 4';

Continuing...

$ git rebase --continue
Applying: branch A commit 4
$ _

Git log:

52b840cb1fb0cc644a0d33938749ba1136e6f7a1 branch A commit 4
89e7b2de0ac66752e8165fac7dbebdf8c4fef317 branch A commit 1
6ff1a89a1fe294ac9cc581cabea4e9e766299522 branch B commit 5
d071bd08bd6a7d8fb552b527380209404c8b2668 branch B commit 2
5f6ad13998a96f721048fad544aff8cbbc13f2c7 One source of truth

Okay, so the order is totally out of whack, however because what we wanted to do is rebase A on top of B, that is exactly what we did. All the changes we did on A (commits 1 and 4) are now on top of what we did on branch B (2 and 5).

For added fun, let’s rebase all of this on top of C.

Rebase A (+B) on top of C

Git log will give you the last git log. That’s where we start. Pay attention how many times I’ll need to resolve conflicts, and in what order, and for what commits!

Step 1

on git:A
$ git rebase C
First, rewinding head to replay your work on top of it...
Applying: branch B commit 2
Using index info to reconstruct a base tree...
M   index.js
Falling back to patching base and 3-way merge...
Auto-merging index.js
CONFLICT (content): Merge conflict in index.js
Failed to merge in the changes.
Patch failed at 0001 branch B commit 2
The copy of the patch that failed is found in:
   /Users/javorszky/Sites/mergeathon-rebase/.git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

Contents:

var original = 'this is the original thing, where everything began',
<<<<<<< HEAD
    c3 = 'branch C commit 3',
    c6 = 'branch C commit 6';
=======
    b2 = 'branch B, commit 2';
>>>>>>> branch B commit 2

Resolved to:

var original = 'this is the original thing, where everything began',
    c3 = 'branch C commit 3',
    c6 = 'branch C commit 6',
    b2 = 'branch B, commit 2';

Step 2: Continuing...

$ git rebase --continue
Applying: branch B commit 2
Applying: branch B commit 5
Using index info to reconstruct a base tree...
M   index.js
Falling back to patching base and 3-way merge...
Auto-merging index.js
CONFLICT (content): Merge conflict in index.js
Failed to merge in the changes.
Patch failed at 0002 branch B commit 5
The copy of the patch that failed is found in:
   /Users/javorszky/Sites/mergeathon-rebase/.git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

Contents:

var original = 'this is the original thing, where everything began',
<<<<<<< HEAD
    c3 = 'branch C commit 3',
    c6 = 'branch C commit 6',
    b2 = 'branch B, commit 2';
=======
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5';
>>>>>>> branch B commit 5

Resolved to:

var original = 'this is the original thing, where everything began',
    c3 = 'branch C commit 3',
    c6 = 'branch C commit 6',
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5';

Step 3: Continuing...

$ git rebase --continue
Applying: branch B commit 5
Applying: branch A commit 1
Using index info to reconstruct a base tree...
M   index.js
Falling back to patching base and 3-way merge...
Auto-merging index.js
CONFLICT (content): Merge conflict in index.js
Failed to merge in the changes.
Patch failed at 0003 branch A commit 1
The copy of the patch that failed is found in:
   /Users/javorszky/Sites/mergeathon-rebase/.git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

Contents:

var original = 'this is the original thing, where everything began',
    c3 = 'branch C commit 3',
    c6 = 'branch C commit 6',
    b2 = 'branch B, commit 2',
<<<<<<< HEAD
    b5 = 'branch B, commit 5';
=======
    b5 = 'branch B, commit 5',
    a1 = 'this is on branch A, commit 1';
>>>>>>> branch A commit 1

Resolved to:

var original = 'this is the original thing, where everything began',
    c3 = 'branch C commit 3',
    c6 = 'branch C commit 6',
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5',
    a1 = 'this is on branch A, commit 1';

Step 4: Continuing...

$ git rebase --continue
Applying: branch A commit 1
Applying: branch A commit 4
Using index info to reconstruct a base tree...
M   index.js
Falling back to patching base and 3-way merge...
Auto-merging index.js

Contents:

var original = 'this is the original thing, where everything began',
    c3 = 'branch C commit 3',
    c6 = 'branch C commit 6',
    b2 = 'branch B, commit 2',
    b5 = 'branch B, commit 5',
    a1 = 'this is on branch A, commit 1',
    a4 = 'branch A, commit 4';

Okay, our file seems to have everything that we need. Note that the order of lines is c - b - a, because we rebased A on top of B, hence B comes first, and then we rebased these two on top of C, hence C comes before both of them.

What we’ve done is “apply the changes of C first on master, and then B, and then A”.

Git log:

670ceef3ea46d9b53e2297456785f38efd8a2091 branch A commit 4
971f05ade92b1a9c99133ae248efee1ca266dc66 branch A commit 1
8807c25a48416a58b911d0793669cf8f96a2fcc0 branch B commit 5
889ab840214b442a406bbf1b5d8d7df4a5b0a1f1 branch B commit 2
9846031af1ee0fdfd187d20bd8095a171f118ced branch C commit 6
d1473d178232aaa48e5ef73b7bf4582932554376 branch C commit 3
5f6ad13998a96f721048fad544aff8cbbc13f2c7 One source of truth

One thing to note: there are no merge commits. We’ve changed history, we’ve changed what each commit did here. This is potentially destructive, and you can lose changes if you’re not careful.

tl;dr;

Given this origin scenario:

   / - A1 -- A4
 / 
O (master)
 \
   \ - B2 -- B5
     \
       \ - C3 -- C6

Merge A, B and C, in that order, into master will yield this history:

   / - A1 --- A4
 / 
O -- A1 -- B2 -- C3 -- A4 -- B5 -- C6 -- merge B -- merge C
 \
   \ - B2 -- B5
     \
       \ - C3 -- C6

Rebase A onto B, and then both on top of C will yield:*

   / - C3 -- C6 -- B2 -- B5 -- A1 -- A4
 / 
O (master)
 \
   \ - B2 -- B5
     \
       \ - C3 -- C6

*Basically the individual commits had to be changed slightly...

When to use which?

Use rebase if you’re working on a feature off of a branch, and the branch is moving independently from what you’re doing. If you’re not touching the same files as others on the origin branch. Or if your changes are local to your computer, and they aren’t made public / other peeps do not have a copy of the work.

Use merge if you want the main branch’s changes to be incorporated and available to you, or if what you’re working on is already available locally to your team.

On Ghost we’re using the constant rebasing method, and at Prospress, we’re using merges. Depends. Your mileage may vary. And I might be monumentally wrong, so please please tell me where I got stuff wrong, and why :).