Version control with



So many topics, so little time

  • Version Control 101
  • Installation & Setup
  • Basic Workflow
  • Viewing Changes
  • Branching
  • Distributed Workflow
  • Best Practices
  • GUI Goodness
  • Dealing with SVN
  • Github
  • Advanced Topics
  • Resources
  • Version Control 101

    Why use Version Control

    Using bad or no version control will only lead to pain & suffering

    Why use Git

    Don't just take my word for it:

    History of Git

    It's come a long way since 2005

    Git Basic Workflow

    An essential - and rather unique - git concept

    Image from

    Git Architecture

    Learn this model, it will serve you well

    Image from “Version Control with Git, 1st Edition”, page 35. O’Reilly.

    Version Control Concepts

    I'm sure I forgot some important items here

    Install & Setup


    These should be mostly correct ...




    Environment Setup

    Just a few things to get us started ...

    bash completion

    Man pages

    git help <command>


    git config


    git config --global "Matt Kopala"
    git config --global ""

    Pager w/ color

    git config --global core.pager "less -R"


        name = Matt Kopala
        email =
        diff = auto
        status = auto
        branch = auto
        pager = less -R
        st = status
        gr = log --graph --oneline
        lt = ls-tree -r --name-only
        ru = remote update
        tool = kdiff3
        autosetuprebase = always

    Basic Workflow

    git status

    Learn to love it

    $ git status
    # On branch master
    # Changes to be committed:
    #   (use "git reset HEAD ..." to unstage)
    #   new file:   b
    # Changes not staged for commit:
    #   (use "git add ..." to update what will be committed)
    #   (use "git checkout -- ..." to discard changes in working directory)
    #   modified:   a
    # Untracked files:
    #   (use "git add ..." to include in what will be committed)
    #   c

    git init

    It doesn't get much easier than this

    Creates the .git directory

    mkopala@platypus:~/src/test/a$ git init
    Initialized empty Git repository in /home/mkopala/src/test/a/.git/

    git diff

    Because you're not going to remember what you changed. Trust me.

    View differences betwwen working directory and HEAD

    git diff

    View difference between index and HEAD

    git diff --cached

    View differences for a single file git diff myfile

    Only show how many lines have changed for each file

    git diff --stat

    git add

    Sorry, git can't read your mind

    Add a couple of files

    git add onefile somefile

    Interactively select which files to add

    git add -i

    Add specific sections of files

    git add -p

    git commit

    Let's checkpoint this bad boy!

    Generate a new tree and commit object, save them, and move HEAD

    Use EDITOR to enter commit message, display diffs in editor

    git commit -v

    Commit from the command line w/ the specified message

    git commit -m "Some really spiffy changes"

    Don't generate a new commit - replace the previous one

    git commit --amend

    git log

    It's all about the history

    commit 9db0d3e46f651ca0a357432f124f960a140d25e3
    Author: Matt Kopala 
    Date:   Tue Jan 24 02:14:05 2012 -0700
        Added a bunch more content




    Because not everything should be versioned

    What to ignore

    $ cat .gitignore 

    git reset

    Oops, I did it again! -Britney Spears

    Clean out the index

    git reset

    Remove a single file from the index

    git reset HEAD myfile

    Discard the last commit

    git reset --soft HEAD^

    Discard all changes in working directory since last commit

    git reset --hard

    git rm

    Get rid of files you don't want anymore

    Remove a file

    git rm

    Remove files that you deleted manually

    git add -u

    Remove added file from staging area

    git rm --cached myfile

    git stash

    Shelve unfinished changes for later

    Save changes for later

    git stash save "Description of changes"

    View list of stashed changes

    git stash list

    Remove last stashed change and apply it

    git stash pop

    Apply last stashed change, but leave on stack

    git stash apply

    Drop last stashed change

    git stash drop

    git checkout

    Jump to any version of your files/code

    Switch to mybranch

    git checkout mybranch

    Switch to remote branch origin/master (detached head)

    git checkout origin/master

    Switch to a specific commit

    git checkout c828fa9

    Switch to the grandparent of HEAD

    git checkout HEAD~2


    git branch

    View your local branches

    git branch

    View all of your branches (including remotes)

    git branch -a

    Show the last commit message & id for each branch

    git branch -v

    Delete a branch

    git branch -d oldbranch


    $ git branch
    * master
    $ git branch -a
    * master

    git checkout -b

    Blaze your own trail

    Create a branch off of your current HEAD

    git checkout -b mytest

    Create a branch of a specific commit, and switch to it

    git checkout -b other c38fa38

    git merge

    Switch to master branch and merge changes from mytest branch

    git checkout master
    git merge mytest

    Ensure a merge commit is create (Don't fast-forward)

    git merge --no-ff

    git cherry-pick

    Grab the changes for a single commit from another branch, and apply them

    git cherry-pick <commit_id>

    git tag

    Tag the current commit with v1.0 tag and label it

    git tag -a v1.0 -m "First release"


    Bringing some sanity to branch management

    Introduced in this post:

    Available on GitHub


    Resolving Merge Conflicts

    Can't we all just get along?

    You did this:

    git merge mybanch

    or this:

    git pull

    and got this:

    Auto-merged file.txt
    CONFLICT (content): Merge conflict in file.txt
    Automatic merge failed; fix conflicts and then commit the result.

    Look in the file for this:

    <<<<<<< HEAD:file.txt
    Hello world
    >>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt

    Clean it up, so you're left with what it should be:

    Hello world

    Viewing Changes

    View your history, your way

    The standard

    git log

    One line summaries

    git log --oneline
    git shortlog

    View files changed

    git log --stat

    One line summary with ASCII art for branches

    git log --graph --oneline

    The whole shebang, by commit

    git log -p

    git grep

    This is one hella useful tool

    Like grep -r *, but only for files committed to Git

    mkopala@platypus:~/src/slideshow$ git grep php
    samples/3rd/ruby19.textile:"Defect 17700":
    samples/facebook.textile:Source: "Facebook Statistics":
    samples/facebook.textile:Source: "Facebook Networks": (Apr/14/2008)
    samples/facebooker.textile:h3. "Facebook API Test Console": - Demo
    samples/mobile.textile:* "iUI Intro":  by Joe Hewitt  
    samples/tagging.textile:* "LibraryThing Book Tag Cloud":
    templates/s6/jquery.js: //

    git blame

    WTF? Who wrote this crap?

    mkopala@platypus:~/src/phpsh$ git blame
    ^9317b56 (Daniel Corson  2008-10-13 13:02:15 -0700  1) #!/usr/bin/env python
    646d4d57 (Daniel Corson  2010-02-23 00:31:18 -0800  2) from distutils.core import setup
    0166922b (Mark Marchukov 2008-11-24 14:43:44 -0800  3) from subprocess import Popen
    49481802 (Mark Marchukov 2008-11-23 21:33:02 -0800  4) import sys
    49481802 (Mark Marchukov 2008-11-23 21:33:02 -0800  5) import os
    49481802 (Mark Marchukov 2008-11-23 21:33:02 -0800  6) 
    1bc1c018 (Daniel Corson  2010-03-23 13:55:10 -0700  7) sys.path.insert(0, 'src')
    1bc1c018 (Daniel Corson  2010-03-23 13:55:10 -0700  8) from phpsh import __version__
    1bc1c018 (Daniel Corson  2010-03-23 13:55:10 -0700  9) 

    Distributed Workflow

    Distributed Workflow overview

    Image from

    SSH keys

    Because typing passwords every time sucks

    Generate keys:

    $ ssh-keygen 
    Generating public/private rsa key pair.
    Enter file in which to save the key (/home/mkopala/.ssh/id_rsa): 
    Enter passphrase (empty for no passphrase): 
    Enter same passphrase again: 
    Your identification has been saved in /home/mkopala/.ssh/id_rsa.
    Your public key has been saved in /home/mkopala/.ssh/
    The key fingerprint is:
    55:f2:06:d1:9c:7e:f8:f3:99:0b:e7:e6:81:53:42:13 mkopala@platypus

    Public key:

    $ cat ~/.ssh/id_rsa ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8FBFq1FZYoZtF9Sdr9/zwsU0xg/i54eq48uwd9RgyX91d2/qPJTQzsf8DKt/uTWKf1DyAUtclNz/MGQjCcHgip0XhuJLd1ChMmx0X/re81e3/WGNn/H/TcsxA2P36QVgB0kcPxnECWuEO62sQ0+2tz8l2S0io0dZntX/7oStM8E5aUA27mv2sUzdcD4p0ft5RJdT1dPdQhAB8QA8JcpCQ2OBSXjQ7gVYv3QdZDtGDJvlLr4KVUT4DAn3u6DLo3/sKkbL51srxpRMlNz/jFKA93pyGhdNDzDScJ0ucl7h+DZeF+IfoMYx8zXumismQDwTt6aHqLisPzylSeVo88q53 mkopala@home

    Private key:

    -----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY-----

    Put the contents of the file in to the ~/.ssh/authorized_keys file on the server.

    $ cat ~/.ssh/authorized_keys ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8FBFq1FZYoZtF9Sdr9/zwsU0xg/i54eq48uwd9RgyX91d2/qPJTQzsf8DKt/uTWKf1DyAUtclNz/MGQjCcHgip0XhuJLd1ChMmx0X/re81e3/WGNn/H/TcsxA2P36QVgB0kcPxnECWuEO62sQ0+2tz8l2S0io0dZntX/7oStM8E5aUA27mv2sUzdcD4p0ft5RJdT1dPdQhAB8QA8JcpCQ2OBSXjQ7gVYv3QdZDtGDJvlLr4KVUT4DAn3u6DLo3/sKkbL51srxpRMlNz/jFKA93pyGhdNDzDScJ0ucl7h+DZeF+IfoMYx8zXumismQDwTt6aHqLisPzylSeVo88q53 mkopala@home

    git clone

    Why let one person have all the fun?

    Make a local clone, pulling down the entire history for a repository

    mkopala@platypus:~/src$ git clone
    Cloning into progit...
    remote: Counting objects: 10042, done.
    remote: Compressing objects: 100% (3056/3056), done.
    remote: Total 10042 (delta 5453), reused 9733 (delta 5187)
    Receiving objects: 100% (10042/10042), 10.64 MiB | 2.43 MiB/s, done.
    Resolving deltas: 100% (5453/5453), done.

    git fetch

    Pull down changes for the remote branches

    git fetch
    remote: Counting objects: 92, done.
    remote: Compressing objects: 100% (29/29), done.
    remote: Total 39 (delta 32), reused 13 (delta 9)
    Unpacking objects: 100% (39/39), done.
    From git+ssh://
     + ea78579...9562048 mybranch   -> origin/mybranch  
       0a3d8ef..bd80cd6  master     -> origin/master

    git remote

    git remote update
    git remote add

    git pull

    Central Repository Setup

    There are several ways to do this ...

    On the server:

    Create an empty bare repository

    git init --bare --shared

    Back at home:

    Add "origin" remote repository

    git remote add origin _url_
    git config branch.master.remote origin
    git config branch.master.merge refs/heads/master

    Push up changes

    git push


    Configuration file for the repository

    $ cat .git/config 
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
    [remote "origin"]
        url = git+ssh://
        fetch = +refs/heads/*:refs/remotes/origin/*
    [branch "master"]
        remote = origin
        merge = refs/heads/master
        rebase = true
    [remote "bob"]
        url = git+ssh://
        fetch = +refs/heads/*:refs/remotes/bob/*
        url =

    Best Practices

    Before committing:

    git status

    When committing:

    git commit -v 

    Format your commit message properly

    Commit often - especially when things are working * you can always squash or amend commits later ...

    This is my commit message summary
    * changed some stuff in module A
    * cleaned up some heinous code duplication
    * fixed a nasty bug

    This makes it so git shortlog and git log --oneline are pretty and make sense

    20 minutes

    GUI Goodness

    Because pictures are pretty and useful

    Tcl/Tk based history view packaged with git


    View your diffs in a GUI

    git difftool --tool=kdiff3
    * see the `difftool` page for more tool options

    Stage and commit changes

    git gui

    Code Reviews

    They're not optional any more

    git remote add
    git remote update
    git log -p
    git log --stat
    git diffall

    Git vs. Subversion

    git clone _url_
    git pull
    git init
    git add .
    git commit
    git diff
    git diff rev path
    git status
    git checkout
    git add _file_
    git rm _file_
    git mv _file_
    git commit -a
    git log 
    git blame
    svn checkout _url_
    svn update
    svn admin create _repo_
    svn import file:///repo
    svn diff
    svn diff -rrev path
    svn status
    svn revert
    svn add _file_
    svn rm _file_
    svn mv _file_
    svn commit
    svn log | less
    svn blame

    Dealing with SVN

    If you have to ...

    git svn clone _url_
    git svn rebase
    git svn dcommit
    git svn log


    (More) Advanced Git

    My choice of 'advanced' is rather arbitrary

    git am
    git bisect
    git diffall
    git rebase
    git reflog
    git gc
    git patch
    git revert
    git archive
    git submodule
    git filter-branch

    I've only got slides for some of these. I've used all of them at some point though except am and patch.


    Create an alias for any git command

    git config --global "status"

    git diffall

    Clone from github & copy in to main directory:

    git clone
    sudo cp git-diffall /usr/lib/git-core

    View diffs:

    git diffall <ref1> <ref2>

    git bisect

    Track down bugs in log n time!

    Start the binary search

    git bisect start

    Mark commit as "good"

    git bisect good

    Mark commit as "bad"

    git bisect good

    git rebase

    With great power, comes great responsibility

    Find out how far to go back

    git log --oneline

    Interactive rebase

    git rebase -i _commitid_

    If you have a merge conflict, fix the file, then:

    git add 
    git rebase continue
    # Commands:
    #  p, pick = use commit
    #  r, reword = use commit, but edit the commit message
    #  e, edit = use commit, but stop for amending
    #  s, squash = use commit, but meld into previous commit
    #  f, fixup = like "squash", but discard this commit's log message

    git reflog

    Never fear losing a commit again ...

    9db0d3e HEAD@{0}: commit: Added a bunch more content
    ac4e09d HEAD@{1}: commit: Lots of updates to the slide
    9e6c0c0 HEAD@{2}: commit (initial): Initial version of

    git gc

    Because smaller is better

    Clean up old objects, refs, and compress pack files

    git gc
    $ git gc
    Counting objects: 3487, done.
    Delta compression using up to 2 threads.
    Compressing objects: 100% (1665/1665), done.
    Writing objects: 100% (3487/3487), done.
    Total 3487 (delta 2254), reused 2853 (delta 1775)

    git revert

    So that change you made last week wasn't a good idea afterall

    Revert the changes made by a commit, and create a new commit object

    git revert <ref>

    git archive

    Because tar cvfz ... and zip are sooo 2005

    Get the tree SHA1 for the mydir directory from the last commit

    COMMIT=$(git ls-tree HEAD mydir | awk '{print $3}')

    Create an archive of the directory

    git archive --prefix mydir/ -o $COMMIT


    Ok, not actually very advanced

    git log > CHANGELOG


    Lots more Git goodness

    Git Main Site



    Stack Overflow

    Git Man Pages

    git help