Use gitk to understand git – merge and rebase

This is the second part of my Use gitk to understand git post.

In my initial overview, I demonstrated creating a branch, making a couple commits to that branch, and then merging them back into master. In that scenario, there were no changes in my local master (and since it was contrived, I knew there were no changes in the remote origin/master), so the merge was really just a fast-forward. In the real world, my workflow would be slightly different, as I would have to account for other people making changes to our shared repository (my origin remote).

To demonstrate, I’ll rewind time and pretend we’re back at the moment where we switched to master as we prepared to merge in the changes from the issue123 branch. The gitk visualization of the repository looked like:

Just before merging issue123 into master

Before I merge my changes into master, I want to make sure my master branch is in synch with the central repository on github (which I refer to using the remote “origin”). We can see in the screenshot that my master branch refers to the same commit as origin/master, but that’s because I haven’t communicated with origin in a long time. All of my previous operations were done locally. In order to get the latest state from the remote repository, I need to perform a fetch.

d:codegitk-demo>git fetch origin
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (6/6), done.
   bf37c64..ec8d10f  master     -> origin/master

new changes from remote

I’ve downloaded new commits to my local repository and moved the remote branch pointer, but I haven’t changed anything in my local branches. If I were to look in my working folder, I would see that none of my files have changed. To get the latest changes to the master branch from Tony, I need to merge them into my master branch.

d:codegitk-demo>git merge origin/master
Updating bf37c64..ec8d10f
 dairy.txt |    3 +++
 1 files changed, 3 insertions(+), 0 deletions(-)
 create mode 100644 dairy.txt

After merging in remote x


Once again, since there was a straight line from my local master to origin/master, git was able to perform a fast-forward merge. The master branch has moved to point to Tony’s latest commit. My working directory has been updated accordingly to have the changes he made.

Note that none of the changes I made for issue123 have been included in master yet. We need to merge the issue123 branch back into master, and ultimately push them to the shared repository on github. However, there is no straight line between issue123 and master – neither is a direct descendent of the other – which means we cannot do a fast-forward merge. We have to do either a “real” merge, or rebase.


To perform a “real” merge, we just use the merge command as we have all along. Doing a fast-forward vs. a real merge is handled by git – not something you specify.

d:codegitk-demo>git merge issue123
Merge made by recursive.
 fruits.txt     |    1 +
 vegetables.txt |    3 ++-
 2 files changed, 3 insertions(+), 1 deletions(-)

After merge

Previously with our fast-forward merges, no new commits were created – git just moved branch pointers. In this case, since there is a new snapshot of the repository that never existed before (includes Tony’s new changes, as well as my changes from issue123), a new commit is required. The commit is automatically created with an auto-generated commit message indicating it was a merge. The merge commit has multiple ancestors (indicated by the red line going to the “Forgot the yogurt” commit” and the blue line going to the “Added another fruit” commit). We can safely delete the issue123 branch now, but unlike in the fast-forward example, when we push our changes to the central server, there will be evidence that the issue123 message existed (in the merge commit message, and the repository history shows the branched paths).

d:codegitk-demo>git branch -d issue123
Deleted branch issue123 (was cac3c72).

d:codegitk-demo>git push origin master
Counting objects: 12, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (8/8), 914 bytes, done.
Total 8 (delta 0), reused 0 (delta 0)
   ec8d10f..5835415  master –> master

After pushing merge to github

Commit History after merge


There are a few reasons not to like the merge approach:

  • Branching paths in the history can be unnecessarily complicated
  • The extra merge commit.
  • Your branch is now no longer a private, local concern. Everyone now knows that you worked in an issue123 branch. Why should they care?

Note: There are some scenarios where you want to preserve the fact that work was done in a separate branch. In those cases, the above “downsides” are not really downsides, but the desired behavior. However, in many cases, the merge is only necessary because of the timing of parallel work, and preserving that timeline is not important.

You can use git rebase to avoid these issues. If you have commits that have never been shared with anyone else, you can have git re-write them with a different starting point. If we go back in time to the point right after we merged in Tony’s changes, but before merging in issue123:

Before rebase

Currently, the issue123 commits branch off from the “third commit”. The rest of the world doesn’t need to know that is where we started our work. We can re-write history so that it appears like we started our work from Tony’s latest changes. We want the issue123 commits to branch off from master, the “Forgot the yogurt” commit.

d:codegitk-demo>git checkout issue123
Switched to branch 'issue123'

d:codegitk-demo>git rebase master
First, rewinding head to replay your work on top of it...
Applying: My first commit
Applying: Added another fruit

After rebase

After a rebase, the “My first commit” now directly follows the “Forgot the yogurt”” commit, making the issue123 branch a direct descendent of the master branch. This means we can now do a fast-forward merge to bring issue123’s changes into master.

d:codegitk-demo>git checkout master
Switched to branch 'master'

d:codegitk-demo>git merge issue123
Updating ec8d10f..b5a86d6
 fruits.txt     |    1 +
 vegetables.txt |    3 ++-
 2 files changed, 3 insertions(+), 1 deletions(-)

No merge commit required after rebase

When we delete the issue123 branch and push these changes to the remote repository on github, there is no longer any evidence that the issue123 branch ever existed. Anyone that pulls down the repository will see a completely linear history, making it easier to understand.

d:codegitk-demo>git branch -d issue123
Deleted branch issue123 (was b5a86d6).

d:codegitk-demo>git push origin master
Counting objects: 9, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 626 bytes, done.
Total 6 (delta 1), reused 0 (delta 0)
   ec8d10f..b5a86d6  master –> master

Pushed to remote x


Commit History after rebase

This entry was posted in git. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Kevin Kuebler

    Thanks for this series – this is really helpful. I’ve seen these types of gitk screenshots before but didn’t really understand what was going on. Your explanations are very clear. I feel like I actually have a basic understanding of git now. Thanks!


  • George

    Great clear explanation, thanks, but there’s still points that confuse the hell out me. Particularly the confusion between “master” in the local repository and “master” on origin. For example:
    d:\code\gitk-demo>git merge origin/master

    If you had renamed your local master to “mymaster”, would that line become:
    d:\code\gitk-demo>git merge origin/mymaster

    … or is origin/master the name of the remote thing, and it merges with your current branch which is not specified in the command?

  • @George Your second understanding is correct. If I had renamed my local master, the line would still be:

    d:\code\gitk-demo>git merge origin/master

    Because the name of the remote reference is still “origin/master”. origin/master is a single argument in the merge command, indicating the source reference to merge into the current branch.

    However, if you want to push your local mymaster changes to the remote origin/master, you would need to specify the source and destination explicitly, since they do not match:

    git push origin mymaster:master

  • Krishnan Mahadevan

    I must admit, this was like an eye opener for me. I have been feeling like a dog trying to catch its own tail, with respect to my effort in understanding GIT. The one best thing I liked about your post, was you helped me clear out this confusion of visualizing the GIT branches as actual branches. The concept of branches being mere labels solved a lot of puzzles ! Awesome Post Joshua…

    One quick question. Which of the following would you advocate, if I were to ensure that my developer branch is in sync with origin/master

    1. git fetch
    2. git checkout master
    3. git rebase origin/master
    4. git checkout mydeveloperbranch
    5. git rebase master

    1. git fetch
    2. git checkout mydeveloperbranch
    3. git rebase origin/master

    My assumptions here are:
    I will only work on developer branch, which means my master branch is basically dummy, and I deliver code as branches to the remote and an admin group takes care of merging the contents of my dev branch to the origin/master.

  • @Krishnan – Thinking of branches as labels was a big breakthrough for me too – I’m glad I was able to convey the idea to help you.

    If you really have no need for your local master branch (and I can’t think of any off the top of my head), I say skip it and use the second workflow you proposed.

  • Thanks for the post! Really helped me get a few things straight. Shine on.

  • Hey. If anyone has any clues to the following, I’d much appreciated it. I’m trying this out in my own (real work) repo:

    Instead of using ‘fetch’ then ‘merge’ as shown above, at work we’re in the habit of just using a single ‘git pull origin master’.

    My approximate understanding was that this is equivalent, but I think I must be wrong about that.

    I have a local clone of our shared repo. I created a couple of local feature branches. Then I want to get updates from the shared repo. So I ‘checkout master’. Instead of doing a ‘fetch origin’ followed by a merge, I did a ‘pull origin master’, which I understood to be a simple combination of the fetch & merge.

    I ended up with gitk showing me:

    o [master]
    o recent commits made by my workmates
    | o [my local feature branch 1]
    | |
    | /
    | o [my local feature branch 2]
    | |
    | /
    o [remotes/origin/master]

    So I have successfully fetched the recent changes from my workmates, and applied them to my local master, which is good. But I don’t understand why my remote origin/master branch has not been moved up to the same commit as my local master.

    If I do as Joshua instructs, and now perform a fetch, then the remote origin master branch catches up to my local master. Can anyone explain why the fetch does that, but the pull does not?

  • @Jonathan – Yes, this is a known behavior. I don’t know if its a bug or intentional, but we have stubbed our toe on it a number of times. The key is to NOT specify the branch when doing a pull.

    If you just do ‘git pull origin’, everything will work as you expect – your remote pointer be updated.

    If you find out a good explanation, let us know.

  • Alright, thanks for the reassurance that I’m not *totally* bungling it in some other mysterious way.

  • Suresh

    Hey Joshua,
    This is very helpful and eye opener for me, I was using CVS earlier and wanted to switch to GIT, branches in GIT were so very confusing for me. After reading your post, (Visualizing branches as labels) many things have become clear to me and now I started appreciating GIT. Thanks for this awesome post.

  • Chouyatingde

    Hej, Joshua.
    This is the best guide for beginners Ive ever seen. You bring me to git. Thanks:-)

  • Ditto.. really nice work. I’ve been using git on a project for a month, googling when issues came up, and this post is a breath of fresh air.

  • blancNoir

    this is exactly what i needed. thank you for sharing your experience, it’s very helpful to me with great clarity. please don’t hesitate to write more git tutorials =) thanks again.

  • Tristan Boyer

    Thanks a lot for this short and well explained tutorial, clear, simple…
    Waiting the next one :)

  • Riz Pro

    Now i know….Thanks a lot!

  • paranoia25

    Great!!! Clear and precise ! Thanks a lot!!

  • Giuliano

    Helpful series, thanks!


  • Fred

    Great demonstration, hope you can continue the gitk topic with a bit more complex cases such as dealing with conflicts when doing the merge or rebase.

  • I read a lot about rebase a don’t understard a word :|

    With this post I understood, cristal clear

    Thanks a lot

  • Mga555

    GREAT JOB!  I’ve been struggling trying to learn Git as I go.  I’ve accumulated a ton of bookmarked information, but I think I can throw most of it away now.  Not only did you do a great job explaining Gitk, but provided a tremendous boost to my understanding of Git; just what you said you’d do!

    One minor thing I did not understand.  When you did the “git merge issue123″, the commits shown in the “top pane” (does that pane have a name?) moved from being in line under master to being in line under issue123. 

    What is the significance of that shift ?

    Are you thinking about a series on Git Gui?

    thanks so much!

  • Anonymous

    Thanks for the great post!

  • Navk24

    Good explanation

  • Karsten

    Hi Joshua,

    I often read tutorials on the web, but this is the first time I add a comment just to say thank you – thank you for the great explanation of git using gitk !

  • Akhil Sikri

    really good one, do you have some other tutorials

  • JIM

    Thanks for the sharing. It is a good walkthrough

  • pdcurtis

    A very good introduction – has made many things clear and gives a route to future investigation and understanding.

  • Txt Lu

    Very excellent illustration. I get a lot from reading your essays. And I am a little confused. when using rebase command, is it processing a merge? And when merge, there is a conflict, do we solve the conflict as usual?

  • Lorenzo

    I agree with Chouyatingde: really the best guide for beginners: clear, simple and with good examples!

    thank you!

  • randomPerson

    Really thankful for this, Joshua!

  • kaavik

    Really great post. Came across your posts during a search about gitk. Fantastic tutorial and great for helping understand gitk as well as the git workflow in general. Thanks!!

  • Martin Pecka

    Really nice tutorial. However, I have a comment regarding the usage of rebase. If you write “Anyone that pulls down the repository will see a completely linear history”, that’s not true regarding the time of the commits.

    That might be confusing, since when I e.g. rebased a month old branch on top of our master, people could get confused why the newest commit is a month old when they committed and pushed yesterday.

    From this reason, I think using rebase is also not so good as you show it here.

    • Traumflug

      It’s even better. Rebasing makes sure a merge actually applies cleanly to the other branch.

      Think of more complex projects where branches have diverged quite a bit. Merges wouldn’t be successful, a lot of conflicts would have to be solved at once. If you rebase an older topic branch one commit by another, you can resolve conflicts in small chunks and test each intermediate result.

      Later you’ll recognize cherry-pick, which allows to copy commit by commit from one branch to another. This way reviewing becomes more powerful: the reviewer picks commits and can test each intermediate result. Or pick only the better parts of a branch. If you pick over a bunch of commits and rebase the topic branch without merging, the topic branch will shrink accordingly, showing much better what’s left.

      These are techniques which simply weren’t possible with Subversion & Co.. They open new horizons, merging (error prone, difficult to bisect later) is entirely gone in my workflow.

      A project of mine is . Linear history for 3 years and several hundred commits now. And a lot of topic branches spinning off, one for each bug report, one for each contribution, one for each experimental implementation. These topic branches get rebased and rebased again to follow development of master. Eventually they become empty and disappear, because all of it was picked to master.

      Having the opportunity to unleash such powers should make it easy to get used to non-linear date stamps. Code development isn’t linear either, after all, people work in parallel, delay things and pick them up later.

      • Peter Tran

        Thanks, for sharing this strategy.

  • ChalaD

    These two posts should be a required reading for git newbies. Thanks Joshua!

  • imed

    very interesting guide.

  • Johnny Oshika

    Excellent article! Thank you.

  • spagbol

    I rarely comment on blogs, but this article is FANTASTIC!!!

    Please understand, I don’t say that lightly; you really have written an excellent article for noobs, especially clarifying the confusing green label thingies as branches – I now realise they are just pointers to timestamps, so can dance around vertically depending on the commit time.

    I realised my project needed source control as I always copied releases into new folders on my hard-drive like v1.0 and then copying the whole thing into v1.1, etc…messy.

    I then tried tortoise SVN and thought it was great, but it was annoying having to right-click all the time, so I found TortoiseTG (Mercurial)…I though wow, that’s exactly what I wanted, it has a nice GUI app where I can see development and clone repos.

    But as time passed, I realised branches are permanent and the lines were all over the place. I felt like I was going backwards :(

    I then forced myself to try GIT GUI but was initially reticent as it’s GUI was rubbish compared to TortoiseHG. When I tried branches, it had the same problem where lines were all over the place when I created branches and then merged.

    I refuse to use the terminal and the GUI doesn’t allow you to rebase. I found an SO answer by using Tools->Add->git rebase master.

    Boom, branches disappeared and it unleashed the power of encapsulated branch development, i.e. you branch, do stuff, do some more stuff and then rebase it back into master without any messy branch lines.

    GIT is far superior to any other SVC because you can delete a branch and do this rebase stuff as Traumflug mentioned in the comments.

    Thank you again Joshua, you have given me the power of version control.

    • cst1992

      Not a pointer to a timestamp; but to a commit(this pointer is known in git terms as a ‘ref’). Your branch points to a commit, and your HEAD points to a branch you’re on.

  • fcq

    say hello from china,you are helpful

  • No1guy

    This should be on the front page of github this actually explained it in two pages vs days of googling things and trying to figure it out. Thanks !

  • Star Wars

    Wonderful!!! Thanks for this straightforward and detailed explanation.
    Thank you very much!

  • Fabricio Suarte

    Perfect! Best explanation ever! Congratulations :)

  • raober

    Thank you, you made my day

  • Saqib

    Now I got it!!!