Git Workshop

This is the content from a talk I gave at OMG!Code in August, 2013. I wrote it in the style of a workshop, but ended up
presenting it as a talk instead. It reads as a walkthrough and has an accompanying git repo.

@nicknisi | github.com/nicknisi/git-workshop

History #

The original git workshop
was presented by Stephen Haberman in 2010 and covered the basics of getting
started with git.

Prerequesites #

You should have a basic understanding of how to use git for this workshop. You should
also have a free account on Github and be sure you can push to it
either by generating an SSH key
or by using the https method.

Do not clone or fork this repository yet. We will get to that in the Introducing hub
section

Overview #

The following topics are going to be covered in this workshop:

  • Git Branching Overview
  • Teaching git about Github with hub
  • Finding bugs with bisect
  • Configuring git
  • Interactive rebase

Introducing hub #

hub is a wrapper around git that teaches git about Github. hub
provides a number of shortcuts and commands that make it easier to work with git projects
hosted on Github. For example, cloning this repository is a lot simpler with hub.
Instead of typing

git clone https://github.com/nicknisi/git-workshop.git

to clone this repo, you just need to run

git clone nicknisi/git-workshop

Installing hub #

# install on OS X
$ brew install hub

# other systems
$ curl http://hub.github.com/standalone -sLo ~/bin/hub
$ chmod +x ~/bin/hub

# alias it as git
$ alias git=hub

As you can see, hub wraps the git command to extend it with additional functionality. hub
makes working with github URLs a lot easier, but it does a lot more than that.

hub Commands #

  • git fork - Create a fork of the repository on your Github account
  • git pull-request - Issue a pull request on Github without ever leaving your terminal
  • git browse - Open a web browser straight to the repository page on Github
  • git create - Create a repository out on Github from your terminal
  • ... and many more!

Activity: Using hub #

With hub installed on your machine, create a fork of this repository, Add your name to
the CONTRIBUTERS.md file, and issue a pull request without ever leaving your terminal.

  1. Clone this repository

    git clone nicknisi/git-workshop
  2. Fork this repository to your Github account: git fork

    # This will automatically set up a new remote named `USERNAME`
    # where USERNAME is your Github username
    git fork
  3. Open up the CONTRIBUTERS.md file in your favorite editor (hopefully vim) and add your name to the list

  4. Commit and push up the change to your fork

    # USERNAME is your Github username
    git commit -a -m "added NAME to the list"`
    git push -u USERNAME master
  5. Issue a pull request from the command line back to this repo

    # USERNAME is your Github username
    git pull-request -b nicknisi:master -h USERNAME:master
  6. Open up your repo in Github to confirm your commit has been pushed up and a pull
    request has been opened

    git browse

Finding bugs with git bisect #

bisect is a fantastic tool to help you discover the exact commit that introduced a bug into
the repository. This works by telling git bisect where a good state is: where your code
is working as expected, and where a bad state is: where the code is broken. git wil then
check out a commit in the middle, and you tell it whether this is in a good state or a
bad state. The bisect command works by performing a binary search between one commit and
another in order to find the exact commit that introduced the bug.

Performing a git bisect can be either a manual or an automatic process, and it depends on
the state of the repository and the bug that has been introduced. To perform a git
bisect, run

git bisect start

Then, tell bisect where a bad commit exists

git bisect bad HEAD

Finally, tell bisect where a good commit is

git bisect good 53cb134

Git will then check out a commit between these two commits and you can manually rerun
whatever steps or tests are necessary to reproduce the issue. If you can reproduce the
issue, run git bisect bad and git bisect good if you are unable to reproduce it. Git
will then pick another issue based on your response until there are no more commits left
to check. At this point, git will inform you of the commit that introduced the issue

Activity: Where did that bug come from? #

In this activity, we are presented with a pretty simple HTML file that is supposed to display a simple square and a slider.
When the slider is changed, the square should change colors, based on the input of from the slider. At the latest commit in
the bisect branch, we are getting no JavaScript errors, and we are not seeing a slider.

We know that the code is working as intended at 37d0821. Let's use git bisect to determine where things went wrong.

  1. Checkout the bisect branch

    git checkout bisect
  2. Look at the index.html file in a browser and test the file to how it is failing

  3. Look through the log to determine where the last known good state of the application is (I have marked this commit with
    the commit message "GOOD STATE")

    git log --graph --pretty=oneline --abbrev-commit --decorate
  4. Start up bisect

    git bisect start
  5. Alert bisect of the bad commit

    git bisect bad HEAD
  6. Alert bisect of the good commit

    git bisect good 37d0821
  7. git will now pick a commit between the two and check it out. Reload the page in the browser and determine if the slider
    is working properly (showing up).

    • If it is showing up, git bisect good
    • If it is not showing up, git bisect bad
  8. After each command, git will checkout a new commit until it has determined the exact commit that introduced the bug

  9. Once you have determined the bad commit, you should be presented with a message like this:

    git bisect bad                                                                                                       ✔
    9378fec...|bisect
    9378fec280242b4a02b6972c3ca9b61cdf14cad5 is the first bad commit
    commit 9378fec280242b4a02b6972c3ca9b61cdf14cad5
    Author: Nick Nisi <nick@nisi.org>
    Date: Tue Aug 13 00:05:23 2013 -0500

    adjust animation speed slowing down again

    :100644 100644 2bf5ba28648e5c388556b37701addac90d9daf20 63ec399ce0f457bd17e5e6933a0a140c1435223d M index.html
  10. To end bisect at any time reset it

    git bisect reset

Following these steps, we are able to quickly determine which is the bad
commit
.

Automating bisect #

The process of finding a bad commit with bisect can be automated if the problem you are trying to find can be tested with a
script. Using git bisect run, we can provide a command or a script to execute against each commit that bisect checks. If
ths script returns 0 then the commit is considered good. Otherwise, it is considered bad. This can be really useful if
you are trying to track down what commit introduced a failing unit test or build.

Configuring git #

The configuration of git can be customized by combining actions and creating shortcuts to simplify your git workflow.

Advanced aliases #

The ~/.gitconfig can include a number of custom
aliases. These can be anything from shortening simple git commands to running advanced shell scripts.

[alias]
co = checkout
s = status --short
br = branch -v

# slight variation of pretty log graph
l = log --graph --pretty=format:'%Cred%h%Creset %an: %s - %Creset %C(yellow)%d%Creset %Cgreen(%cr)%Creset' --abbrev-commit --date=relative

# show changed files for a commit
cf = show --pretty="format:" --name-only

# show what I did today
day = "!sh -c 'git log --reverse --no-merges --branches=* --date=local --after=\"yesterday 11:59PM\" --author=\"`git config --get user.name`\"'"

# show number of commits per contributer, sorted
count = shortlog -sn

# YOLO
yolo = !git commit -am "DEAL WITH IT" && git push -f origin master && shutdown -r now "DEAL WITH IT"

undo = reset --soft HEAD~1
amend = commit --amend -C HEAD

# rebase the current branch with changes from upstream remote
update = !git fetch upstream && git rebase upstream/`git rev-parse --abbrev-ref HEAD`

## grep commands

# 'diff grep'
dg = "!sh -c 'git ls-files -m | grep $1 | xargs git diff' -"
# 'checkout grep'
cg = "!sh -c 'git ls-files -m | grep $1 | xargs git checkout ' -"
# add grep
ag = "!sh -c 'git ls-files -m -o --exclude-standard | grep $1 | xargs git add' -"
# add all
aa = !git ls-files -d | xargs git rm && git ls-files -m -o --exclude-standard | xargs git add
# remove grep - Remove found files that are NOT under version control
rg = "!sh -c 'git ls-files --others --exclude-standard | grep $1 | xargs rm' -"

Reuse recorded resolution: rerere #

If you have a long-running branch, git can remember how you resolved merge conflicts and can reapply how you merged the files.

[rerere]
enabled = true

Git hooks #

Git hooks allow you to run scripts before or after events in git such as commit, push, and receive. Hooks are configured per repository and are located in the .git/hooks directory. Because they live in the git database directory, they are not part of your repository and can be set up independently. Here are just a few of the available commit hooks:

  • pre-commit - run before a commit to the repository
  • post-checkout - after a checkout which includes, cloning, switching branches, or resetting files
  • commit-msg - run after a commit message is entered

There are also server-side commit hooks. These can be utilized to kick off builds, run deployments, run
continuous integration, and many other things. Github has integration with third party services when
commits happen and you can also set up a route for Github to post to on certain actions.

Activity: Adding a commit-hook #

When a new repository is created or cloned, git will copy over example commit hooks into the repository's .git/hooks directory:

❯ ll .git/hooks                                                                                                                   ✔ master
total 80
-rwxr-xr-x 1 nicknisi staff 452B Jul 9 22:45 applypatch-msg.sample*
-rwxr-xr-x 1 nicknisi staff 896B Jul 9 22:45 commit-msg.sample*
-rwxr-xr-x 1 nicknisi staff 189B Jul 9 22:45 post-update.sample*
-rwxr-xr-x 1 nicknisi staff 398B Jul 9 22:45 pre-applypatch.sample*
-rwxr-xr-x 1 nicknisi staff 1.7K Jul 9 22:45 pre-commit.sample*
-rwxr-xr-x 1 nicknisi staff 1.3K Jul 9 22:45 pre-push.sample*
-rwxr-xr-x 1 nicknisi staff 4.8K Jul 9 22:45 pre-rebase.sample*
-rwxr-xr-x 1 nicknisi staff 1.2K Jul 9 22:45 prepare-commit-msg.sample*
-rwxr-xr-x 1 nicknisi staff 3.5K Jul 9 22:45 update.sample*

You can look at and use these files for inspriation when creating your git hook. Hooks can be written in any
language that can be called from the command line. If the script returns 0, the hook is considered successful.
Otherwise, it has failed. Let's add a git hook to show us info about our repository when we switch branches.

  1. Create a file called post-checkout in the .git/hooks directory and
    execute permissions
touch .git/hooks/post-checkout
chmod +x .git/hooks/post-checkout
  1. Set the contents of the file
#!/bin/bash

echo ""
echo ""
echo -e "\033[1m RECENT COMMITS \033[0m"
git --no-pager log -n5 --graph --pretty=format:'%Cred%h%Creset %an: %s - %Creset %C(yellow)%d%Creset %Cgreen(%cr)%Creset' --abbrev-commit --date=relative
echo "
echo "
"]]"
  1. Back in the repository, create a new branch and notice that you will get a
    summary of the last 5 commits to the repository.
git checkout -b new-branch

Managing git hooks #

Git hooks are excluded from the repository and this is both a blessing and a
curse. It allows developers to setup their own hooks without affecting anyone
else. However, that means that it is typically a manual process of storing the
commit hook in a safe place and then manually copying it over into the newly
created or cloned repo.

As previously pointed out, git copies a number of sample commit hooks into
each repository, and we can hijack this to add our own commit hooks! These
sample files live at /usr/share/git-core/templates in the hooks directory.
We can configure git to change where this directory exists and override it
with our own defaults, allowing us to manage this directory independently and
keep under source control in our dotfiles.

To change where git looks for this templates directory, add the following to
your ~/.gitconfig:

[init]
templatedir = ~/.dotfiles/git/templates

Now, whenever we clone or init a new repository, we will be set up with our
own, custom git hooks that have been copied from the above path.

Note that the hooks are copied, meaning that any revisions to them later will
not be copied over. However, running git init in an existing repository
will copy the updated hooks over to the repo.

Interactive rebase #

Remember that nasty bisect branch with its many commits changing the same thing
over and over? Wouldn't it be nice if we could clean up our log so it doesn't
look so nasty?

* fe321d9 Nick Nisi: adjust animation to 320 -   (HEAD, origin/bisect, bisect) (17 hours ago)
* 900b366 Nick Nisi: adjust animation to 319 - (17 hours ago)
* a5ba9ac Nick Nisi: adjust animation to 318 - (17 hours ago)
* f84b4cd Nick Nisi: adjust animation to 317 - (17 hours ago)
* 70f7912 Nick Nisi: adjust animation to 316 - (17 hours ago)
* 442c007 Nick Nisi: adjust animation to 314 - (17 hours ago)
* 7fd99d5 Nick Nisi: adjust animation to 313 - (17 hours ago)
* 507d90e Nick Nisi: adjust animation to 312 - (17 hours ago)
* 76162b0 Nick Nisi: adjust animation to 311 - (17 hours ago)
* 89390fa Nick Nisi: adjust animation to 310 - (17 hours ago)
* 2f3c3fc Nick Nisi: adjust animation to 309 - (17 hours ago)
* 18ce973 Nick Nisi: adjust animation to 308 - (17 hours ago)
* 448e9e2 Nick Nisi: adjust animation to 307 - (17 hours ago)
* f7a452d Nick Nisi: adjust animation to 306 - (17 hours ago)
* e23d9f8 Nick Nisi: adjust animation to 305 - (17 hours ago)
* 72eb0c2 Nick Nisi: adjust animation to 304 - (17 hours ago)
* 5e4cee6 Nick Nisi: adjust animation to 303 - (17 hours ago)
* 4f88c8d Nick Nisi: adjust animation to 302 - (17 hours ago)
* 9378fec Nick Nisi: adjust animation speed slowing down again - (17 hours ago)
* cc62c6f Nick Nisi: adjust animation speed slowing down - (17 hours ago)
* faa0412 Nick Nisi: adjust animation speed speed up again - (17 hours ago)
* 4e024b6 Nick Nisi: adjust animation speed speed up - (17 hours ago)
* 983eb1f Nick Nisi: adjust animation speed speed up - (17 hours ago)
* 00f4ca5 Nick Nisi: adjust animation speed speed up - (17 hours ago)
* 06badcb Nick Nisi: adjust animation speed - (17 hours ago)
* d1ebc8e Nick Nisi: adjust box size - (17 hours ago)
* d3d2d4d Nick Nisi: change box size - (17 hours ago)
* ecf34f6 Nick Nisi: change border color - (17 hours ago)
* de9bb1f Nick Nisi: add h2 tag contents - (17 hours ago)
* 2210aca Nick Nisi: add h2 tag - (17 hours ago)
* 37d0821 Nick Nisi: GOOD STATE - (17 hours ago)

The good news is that we can! The bad news is that it needs to be used with caution.
The Github Help page states it best:

Warning: It is considered bad practice to rebase commits which you have already pushed to a remote repository. Doing so may invoke the wrath of the git gods.

When you run an interactive rebase, you are changing your git history. Commits will be
assigned new SHAs. If you have already pushed up and someone else has pulled down the changes, it will mess up their repository if you change the history out from under them.

Running `git rebase -i HEAD 4f88c8d will pop us into our editor and list all of the commits within the provided range.
Each commit will have the word pick next to it, meaning that we are picking this commit (leaving it as it is).
The other options are

  • reword - use commit but edit the commit message
  • edit - use commit, but stop for amending
  • squash - use commit, but meld into previous commit
  • fixup - like "squash", but discard this commit's log message
  • exec - run command

We can use the squash, pick, and fixup commands to simplify the repository.

Activity: Clean the history #

Let's use our new knowledge of interactive rebase to clean up the bisect branch.

  1. Let's branch off of bisect and do this in a new branch
git checkout bisect
git checkout -b rebase
  1. Enter into an interactive rebase, down to the "GOOD STATE" commit
git rebase -i 37d0821
  1. For each of the commits that have a similar message, select the top one to "pick". For the rest, select "squash"
pick 2210aca add h2 tag
pick de9bb1f add h2 tag contents
pick ecf34f6 change border color
pick d3d2d4d change box size
pick d1ebc8e adjust box size
pick 06badcb adjust animation speed
pick 00f4ca5 adjust animation speed speed up
pick 983eb1f adjust animation speed speed up
pick 4e024b6 adjust animation speed speed up
pick faa0412 adjust animation speed speed up again
pick cc62c6f adjust animation speed slowing down
pick 9378fec adjust animation speed slowing down again
pick 4f88c8d adjust animation to 302
squash 5e4cee6 adjust animation to 303
squash 72eb0c2 adjust animation to 304
squash e23d9f8 adjust animation to 305
squash f7a452d adjust animation to 306
squash 448e9e2 adjust animation to 307
squash 18ce973 adjust animation to 308
squash 2f3c3fc adjust animation to 309
squash 89390fa adjust animation to 310
squash 76162b0 adjust animation to 311
squash 507d90e adjust animation to 312
squash 7fd99d5 adjust animation to 313
squash 442c007 adjust animation to 314
squash 70f7912 adjust animation to 316
squash f84b4cd adjust animation to 317
squash a5ba9ac adjust animation to 318
squash 900b366 adjust animation to 319
squash fe321d9 adjust animation to 320
  1. Complete by saving and closing your editor
  2. Look at the new history in the log
* db86962 Nick Nisi: adjust animation to 302 -   (HEAD, rebase) (4 seconds ago)
* 9378fec Nick Nisi: adjust animation speed slowing down again - (18 hours ago)
* cc62c6f Nick Nisi: adjust animation speed slowing down - (18 hours ago)
* faa0412 Nick Nisi: adjust animation speed speed up again - (18 hours ago)
* 4e024b6 Nick Nisi: adjust animation speed speed up - (18 hours ago)
* 983eb1f Nick Nisi: adjust animation speed speed up - (18 hours ago)
* 00f4ca5 Nick Nisi: adjust animation speed speed up - (18 hours ago)
* 06badcb Nick Nisi: adjust animation speed - (18 hours ago)
* d1ebc8e Nick Nisi: adjust box size - (18 hours ago)
* d3d2d4d Nick Nisi: change box size - (18 hours ago)
* ecf34f6 Nick Nisi: change border color - (18 hours ago)
* de9bb1f Nick Nisi: add h2 tag contents - (18 hours ago)
* 2210aca Nick Nisi: add h2 tag - (18 hours ago)
* 37d0821 Nick Nisi: GOOD STATE - (18 hours ago)
* a36751a Nick Nisi: add border to color box - (18 hours ago)
* c287e19 Nick Nisi: fixing slider values - (18 hours ago)
* 6f4edca Nick Nisi: messing with slider values - (18 hours ago)
* 225c4ef Nick Nisi: adding width style - (18 hours ago)
* 09ac64d Nick Nisi: adding height style - (18 hours ago)
* 6148238 Nick Nisi: removed height/width - (18 hours ago)
* d1e1701 Nick Nisi: removing border - (18 hours ago)
* 2c9df68 Nick Nisi: adding cdn scripts - (18 hours ago)
* 0763e15 Nick Nisi: initial commit of index - (18 hours ago)
* 9f0ec0c Nick Nisi: Add editorconfig - (20 hours ago)
* 39aad97 Nick Nisi: updating hub instructions - (20 hours ago)
* 2922ce8 Nick Nisi: Added README and hub walkthrough - (21 hours ago)
* 5fb2d99 Nick Nisi: Rename LICENSE.md to LICENSE - (21 hours ago)
* 53cb134 Nick Nisi: Create LICENSE.md - (21 hours ago)

Learning more #

We've only briefly covered a few of the really cool tricks you can do with git. Want to learn more? Check out these great resources!