Skip to content

Instantly share code, notes, and snippets.

@portante
Created September 11, 2018 10:53
Show Gist options
  • Save portante/05042696e4a5be2006c58c73a41de335 to your computer and use it in GitHub Desktop.
Save portante/05042696e4a5be2006c58c73a41de335 to your computer and use it in GitHub Desktop.
Git merge vs Git rebase

What to do about so many empty merge commits?

I think it has to do with a branch "merging" up to master by using git merge/pull instead of using git rebase.

If you make a change in a branch from a particular commit ID on master, and then master moves ahead, and you use git merge to update that branch, git gratuitously creates "merge commits" which cover the changes made to automatically merge the new changes since your change was made on master.

In contrast, a git rebase (assuming you have properly setup remote tracking with git branch --set-upstream-to=...) takes your commits (really the diffs those commits represent) and re-applies it to the latest on the tracking branch adjusting your commits to work on top of that rebase.

For example, let's say we have master with commits A, B, and C:

$ git branch --set-upstream-to=upstream/master master
$ git remote update
$ git pull
$ git log --oneline --decorate
     1  C (HEAD, master, upstream/master) Change 3
     2  B Change 2
     3  A Change 1

And you create a branch for a new change:

$ git checkout -b work upstream/master
$ git commit -m "Change 4"
$ git log --oneline --decorate
     1  D (HEAD, work) Change 4
     2  C (master, upstream/master) Change 3
     3  B Change 2
     4  A Change 1

But now somebody else makes a commit to master before you get a change to push yours, and you use git remote update to reflect those changes locally:

$ git remote update
$ git log --oneline --decorate
     1  D (HEAD, work) Change 4
     2  C Change 3
     3  B Change 2
     4  A Change 1

Your changes are still intact, and notice that your work is still commit D on top of commit C, but that commit C no longer has the upstream/master label. If you have a local master branch which has its upstream tracking set to upstream/master, then after you use git pull to bring it up-to-date, you'd likely see:

$ git checkout master
$ git pull
$ git log --oneline --decorate
     1  E (HEAD, master, upstream/master) Change 5
     2  C Change 3
     3  B Change 2
     4  A Change 1

If you were to then use git merge to incorporate those changes into your work branch, you'd likely see:

$ git checkout work
$ git merge upstream/master
$ git log --oneline --decorate
     1  F (HEAD, work) Merge remote-tracking branch 'upstream/master'
     2  E (master, upstream/master) Change 5
     3  D Change 4
     4  C Change 3
     5  B Change 2
     6  A Change 1

Because D ("Change 4") was your work based on C, but F contains the changes made by the merge performed on top of E.

If you were to perform a git rebase instead of git merge, then you'd likely see:

$ git checkout work
$ git rebase
$ git log --oneline --decorate
     1  D (HEAD, work) Change 4
     2  E (master, upstream/master) Change 5
     3  C Change 3
     4  B Change 2
     5  A Change 1

Does that make sense?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment