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.
From github.com:joshuaflanagan/gitk-demo
   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
Fast-forward
 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.

Merge

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)
To git@github.com:joshuaflanagan/gitk-demo.git
   ec8d10f..5835415  master –> master

After pushing merge to github

Commit History after merge

Rebase

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
Fast-forward
 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)
To git@github.com:joshuaflanagan/gitk-demo.git
   ec8d10f..b5a86d6  master –> master

Pushed to remote x

 

Commit History after rebase

Related Articles:

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!

    Kevin

  • 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?

  • http://www.lostechies.com/members/jflanagan/default.aspx Joshua Flanagan

    @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

    (or)
    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.

  • http://www.lostechies.com/members/jflanagan/default.aspx Joshua Flanagan

    @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.

  • http://tartley.com Jonathan Hartley

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

  • http://tartley.com Jonathan Hartley

    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?

  • http://www.lostechies.com/members/jflanagan/default.aspx Joshua Flanagan

    @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.

  • http://tartley.com Jonathan Hartley

    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:-)

  • http://www.facebook.com/rbucks Ryan Buckley

    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!

    Giuliano

  • 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.

  • http://www.facebook.com/profile.php?id=100000628198194 Cristian Umaña Jiménez

    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!