100% found this document useful (4 votes)
904 views

Git Work Book

The document provides an introduction and guide to using Git. It covers installing and configuring Git, creating repositories, tracking changes, branching, merging, and resolving conflicts. The guide includes exercises for the reader to practice each concept as they learn.

Uploaded by

sandra marta
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (4 votes)
904 views

Git Work Book

The document provides an introduction and guide to using Git. It covers installing and configuring Git, creating repositories, tracking changes, branching, merging, and resolving conflicts. The guide includes exercises for the reader to practice each concept as they learn.

Uploaded by

sandra marta
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 74

Git Workbook

Self-Study Guide to Git

Lorna Mitchell
This book is for sale at http://leanpub.com/gitworkbook

This version was published on 2016-07-06

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean
Publishing process. Lean Publishing is the act of publishing an in-progress ebook
using lightweight tools and many iterations to get reader feedback, pivot until you
have the right book and build traction once you do.

2014 - 2016 Lorna Mitchell


Tweet This Book!
Please help Lorna Mitchell by spreading the word about this book on Twitter!
The suggested hashtag for this book is #gitworkbook.
Find out what other people are saying about the book by clicking on this link to
search for this hashtag on Twitter:
https://twitter.com/search?q=#gitworkbook
Also By Lorna Mitchell
N Ways To Be A Better Developer
Zend Certification Preparation Pack
Contents

About This Workbook 1


Get Some Tools 1
Configure Git 2
Make A Repository 5
Whats Happening? Ask Git Status 5
Keep Your Changes Under (Source) Control 8
Study Your History with Git Log 9
Inspect Your Changes with Git Diff 9
Unstage An Added File 12
Fix An Incorrect Commit Message 13
Branch To Create A Safe Working Area 14
Apply a Branch of Changes with Git Merge 17
Whats The Story? Ask Git Log 23
Gits Staging Area 24
Get The Right Commit On The Right Branch 26
Spot The Difference: More Git Diff 28
Power Diff with DiffTool 28
Handle Interruptions with Git Stash 31
Cherry-Pick Changes From Another Branch 32
Git Revert 33
UnMerge Changes 35
I Lost My Work: Git Reflog to the Rescue 38
Handling Conflicts - in Git and in Teams 40
Merge Types 43
Create A Fast-Forward Merge For A Clean History 45
Rewrite History With Rebase Interactive 48
Get Started With GitHub 52
CONTENTS

Understand Multiple Remotes 54


Use Tracking Branches for Easy Collaboration 57
Pull With Rebase When Your Branch Is Out Of Date 59
Git Hooks 60
Projects Within Projects: Git Submodule 61
Git Bisect for Fault Finding in a Forest of Commits 64
Quiz Answers 67
Credits 68
CONTENTS 1

About This Workbook


This workbook is designed to equip you with the skills you need to use the Git source
control tool. It assumes absolutely no pre-requisite knowledge at all. To get the best
out of this book you will need:

an attitude for participation


a GitHub.com account (sign up if you dont have one, its free)
some time. Little stolen moments of time is fine, this is all about the small
pieces

There is no easy way to pick up new skills, and git is no exception (and also theres
a lot of git skills!). In recognition of that, this workbook encapsulates everything you
will need for all the various areas, by explaining it, showing you, and then asking
you to try it yourself. There are some puzzles and quizzes along with the exercises -
these are there to hack your brain into remembering this stuff! Its tempting to skip
through or imagine what you would write, but please embrace the experience and
work through all the exercises and puzzles, I promise it will help to make everything
end up in your brain.
There are some supporting materials in git repositories on GitHub: you can find all of
them under the Git Workbook organisation here: https://github.com/gitworkbook.
If you have an comments or questions, then I would love to hear them: [email protected].
Have fun!

Each section has a box for you to tick so you can track your progress.

I have read the introduction and am ready to begin with git

Get Some Tools


First, if you dont have git already, youll need to get it. There are a very large number
of tools for working with git, but this workbook focuses on the command line, as it is
https://github.com/gitworkbook
CONTENTS 2

common to all platforms. I recommend that everyone gets to know a little bit on the
command line in git, and then goes on to choose a tool for everyday use - if you use
TextMate, Eclipse, or another IDE then there will be support for git in those tools.
To begin, theres some good documentation for each platform available at https://help.github.com
up-git/.

Assignment
Your first assignment is to install git.

1) Go to http://git-scm.com/downloads and follow the instructions for


your system
2) If you already have git installed check that it is relatively new (1.8 or
later) by typing git --version

I have installed git version 1.8 or later

Configure Git
The main point of version control is to have a safe place to keep the canonical version
of a project, and to have a record of all the changes that occurred in that project. Each
change is a commit (used as both a noun and a verb: you can commit a change
or create a commit), and will include a few key pieces of information:

Exactly what change was made


By whom
When
Why this change was made (this relies on all team members writing meaning-
ful commit messages at all times. You mileage will undoubtedly vary)

To record this information, git will need to know who you are to begin with, so
well configure it with that information. In any directory, run the following (edit the
commands to include your actual details):
https://help.github.com/articles/set-up-git/
CONTENTS 3

git config --global user.name "My Own Name Here"


git config --global user.email "[email protected]"

In typical commandline style, git doesnt give any feedback, but we can check our
config settings with git config --list (it can be a very long list, depending on
your setup)

$ git config --list


color.diff=auto
color.status=auto
color.branch=auto
user.name=Lorna Mitchell
[email protected]
diff.tool=vimdiff
push.default=simple

The username and email settings are the really important ones since this information
gets associated with each commit that you make. If you see issues such as your
avatar not appearing on GitHub, check these settings and make sure that your email
addresses are the same in your config and on GitHub.
Git can accept configuration at different levels. The three scopes for git config are:

System Global Local (default)


Applies to everyone using Applies to this Applies to the current
the
machine user repository

The more specific settings override the more general ones. The example above sets
the global configuration that will apply to all your respositories.

Quick Quiz: Config Settings


Jo needs to configure the email address that git uses.
CONTENTS 4

First, she types git config user.email "[email protected]"


Then realises that not only did she mistype her email address, but she also
wanted to set it for all repos
Next she types git config --global user.email "[email protected]"
Satisfied, she goes on to finish her work and commit it.

Which email address will be associated with her commit?


Why? .
How could Jo have checked her settings beforehand? .
Quiz answers can be found at the end of the workbook

Some other handy configuration options that you might like to set up at this point:

color.ui
Try setting this to true to get more colours in your terminal
core.editor
Sets which program youll be prompted to use to edit things in. Git will use
your system default editor or vi, but you can set whichever program youd
rather use.
core.autocrlf
Windows users, please set this to true. It will help you to avoid whitespace
issues in your projects.

Assignment
1) Configure your name, email address, and any of the other configura-
tion options that look useful.
2) Check your settings with git config --list

I have set my git config settings and am ready to begin


CONTENTS 5

Make A Repository
Git is a distributed version control system, which means that you do your local work
on an actual repository, and there are other remotes which are also repositories in
exactly the same way. Well start by making a standalone repository and then well
discuss how to link it to other repositories or repos as they are known.
The command to create a repo is git init [dir]. If you run git init on its own,
your new repo will be in the current directory. If you run git init myproject then
youll get a new directory called myproject with a git repo inside it. You can check
if a directory is a git repo by running git status (if it says not a git repository,
check you changed into the directory you created the repository in) or by checking
that there is a hidden .git/ directory inside there.

Assignment
1) Create a new git repository in the directory project1.
2) Check that this directory exists and is a git repository

I now have a repository to use for the next examples

Whats Happening? Ask Git Status


The most useful command in git doesnt actually do anything! It is git status which
tells you what is going on in your working directory. When you run git status, it
shows you three things:

which files have changes which will be included in your next


commit (more about commits in just a moment, were not ready
for that yet)
which files have been changed since the last commit, but wont be
included in the next commit unless you say so
which files exist but which git isnt tracking

Heres an example of what happens when you run git status:


CONTENTS 6

$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: example.txt
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working d\
irectory)
#
# modified: README.md
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# other.txt

Git helpfully includes instructions (in brackets/parentheses) on how to move some-


thing from one section to another; this can be very helpful when you cant quite
remember what to type! The files move between the states as we work on them.

The left hand state is a file that has changes, and also includes the new/untracked
files. Changes here are in our file system, weve done the work, but we havent yet
begun to feed that information to git.
The middle section is the staging area. We use git add to put files into this area (we
CONTENTS 7

can remove them from here too without losing our changes, as youll see shortly).
Once we are happy with the collection of changes that we have added to the staging
area, we commit, which is the area on the right hand side of the diagram. A commit
is formed from the contents of the staging area, the current timestamp, the user
information you added earlier and the message you supply when you create the
commit. At this point, your new commit is quite literally history: it forms part of the
history of the project.
In real, just-a-day-at-the-office terms, gits staging area means that we dont need
to conduct our entire working life around the special needs of git. We can sit at our
computers, hack together a masterpiece, and then curate some good commits from
the work that we have done, by only adding the files we want to include in each
commit. It gets our workflow into a place which makes sense for developers and
away from working around a tool.

Assignment
1) Create a new text file in your repo; you can use your normal tools
for this, dont feel tied to the command line as the repo is just a normal
directory with a few superpowers.
2) Run git status and check you can see it under untracked files. Now
run git add [file] with the filename of your file.
3) Run git status again and check that you can see the file listed under
Changes to be committed:.
4) Now were going to actually commit the change. Run git commit, and
you will be prompted to edit a file. Write a message about the change
you made at the top of this file, then save and quit the file.
note
If you didnt update your editor settings in the config section, you may
now be in vim. If that doesnt seem like a good idea, try this:

press Esc. Now type (it will appear in the bottom left):
:wq and press Enter
now set your core.editor config value to something
you would rather use
CONTENTS 8

I made my first git commit!

Keep Your Changes Under (Source) Control


Making commits in git means recording the changes we have made. Being a git
expert basically means adding and committing sanely. Even if you really did
rename all your template files, change the indentation level in your config directory,
fix a bug in the image uploader and then add a resize feature - and THEN realise
you should have committed - you still want to create a story about the changes you
made in a sane way.
Its tempting to create a masterpiece, and then just sort of checkpoint where you are
up to by committing everything you did at once, but we can do better than that.

Quick Quiz: Good Commit Practices


When is a good time to commit in git? (pick as many as you like)
A) Before you go for lunch
B) When youve finished one of the steps towards the feature youre
building
C) When youve finished a big feature
D) When youve finished a bug fix
E) For every small change
Quiz answers can be found at the end of the workbook

Weve discussed how amazing and powerful it is to have a staging area rather than
just snapshotting all our changes, but lets see how that could work in practice.

Assignment
1) Add multiple files to your project directory, and run git status
2) Do git add for just ONE of your files
3) Run git status, then commit your changes as before, and run git
status again (spotting a theme here?)
CONTENTS 9

I can create a commit with a subset of the changed files in it, leaving the other
changes intact in my working directory

Study Your History with Git Log


Now youve made those commits, youve created history. Its useful to be able to
look at what has already happened on a project to understand it, and to do that we
use the git log command (go on, try it!). This shows who made which changes in
the past. Well revisit this command a few times as we learn new things with git as
it can give us a lot of information about the commits that have been made.
Many git commands can produce output that is too long for the screen (git log will
do this when youve made more commits), so they launch their output into a pager.
When this happens, youll find that you are sort of stuck in a land of log after running
this command. If youve used programs called more or less (unix geeks love naming
things) then thats what you are seeing here. If you havent dont worry: press space
to see more content, and q to quit - thats pretty much all you need to know.

Assignment
1) Run git log to see the commits you have already made.
2) Try the command again with the --stat switch to get information
about which files were affected in each commit
3) For a compact view, use git log --oneline - this is useful when
looking at a bigger picture

I can check what has already happened on this repo

Inspect Your Changes with Git Diff


Another command I use a lot (not as mugh as git status though) is git diff. This
lets me examine the changes between two versions of my project. You can use git
diff to look at differences between past revisions, but mostly I use it to check whats
currently in my working directory. There are three ways I usually use this command:
CONTENTS 10

Command Shows
git diff unstaged changes
git diff --staged changes staged for commit
git diff HEAD all changes in this directory since the last
commit, both staged and not

Its useful to use git diff to check whats already been done, as well as to see what
is or is not included in your next commit. In git, its possible to stage part(s) of a file,
but not all of the changes in it, and so git diff is very handy there too!
For example if I change a file, and add it, then change it again, Ill see it listed as both
staged and unstaged in the output:

$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: README.md
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working d\
irectory)
#
# modified: README.md
#

We can check out whats going on here by running the three git diff commands
above on this very simple changeset.
CONTENTS 11

$ git diff
diff --git a/README.md b/README.md
index 67cda08..5cc0661 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,2 @@
Read this
+then this

$ git diff --staged


diff --git a/README.md b/README.md
index e69de29..67cda08 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1 @@
+Read this

$ git diff HEAD


diff --git a/README.md b/README.md
index e69de29..5cc0661 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1,2 @@
+Read this
+then this

In the diff output, additions have a + at the start of the line (and are in green if you
have default colours enabled) and removals have - at the start of the line (and are in
red if you have default colours enabled).

Quick Quiz: Staging Changes


Mia is working on a new feature. She made some changes to her local git repo, and
staged them. Next, she realises she forgot something, and edits the file to include
it.
CONTENTS 12

Git status shows the file as staged, so she commits her work.
Will Mias changes be included? Any of them? Why?
Quiz answers can be found at the end of the workbook

Another very handy trick is to diff and add at the same time; this is one I use a lot.
Running git add -p shows each chunk of output from git diff in turn, with the
option to add it! Press ? if youre not sure which option you want; this produces a
more detailed help output. The other advantage of this approach is that it really helps
to spot things which should not have been included, such as debug statements!

Assignment
1) Make sure you have no outstanding changes in your repository by
running git diff and adding/committing changes as necessary (well
come back to undoing things in a moment)
2) Create a file with at least 10 lines of content in it, add and commit the
file
3) Now edit this file at the beginning and at the end. Stage only one
of these changes by doing git add -p and choosing y for one of the
changes and n for the other
4) Use git diff to check that only one of the changes will be committed,
and then commit it
5) Check that the other change is still in your working directory, then
add and commit that too

I know how to add some, but not all, changes from a file

Unstage An Added File


While committing some changes, Anya has decided that it would be better to split
the changes she made in this file into two parts. She knows how to add part of a file
CONTENTS 13

with git add -p, but sadly she already added the whole thing
Enter the git reset command to rescue Anya and allow her to make well-curated
commits on her project. This command is destructive, so use with care! In this case
though, were only working on the staging area, to un-add either one or all files.

Command Outcome
git reset No changes to any project files, but any staged
changes are now unstaged
git reset [file] The named file will not be changed, but will no
longer be staged. No other staged changes are
affected

Warning
Theres a variation on this command, git reset --hard, which actually throws
away past commits as well. Only add the --hard if youre sure you know what
to do next! We will cover this thoroughly in later chapters, just be aware of the
danger until then

Assignment
1) Make changes to one or more files in your repo. Add the files and run
git status to check things look as you expect.
2) Pick a file and unstage it. Then run git status again.
3) Finally add and commit all your changes, or throw them away.

I can un-add as well as add changes

Fix An Incorrect Commit Message


Soraya has just committed a really excellent bug fix - but she forgot to include the
issue reference number which her team uses with all their commits! She is certainly
CONTENTS 14

not the only person to have made this mistake, and in fact git has a command for
exactly this situation: git commit --amend.
This command will remove and re-stage your changes, allow you to change the
commit message, and then create a new commit with the new commit message.
Because its not actually the same commit, the commit identifier will change, so
dont use this trick if you have already pushed your changes to another repository.
(We havent covered pushing yet, so if that was an unfamiliar term, dont worry, all
will become clear!)

Assignment
1) Run git log to see the history of all the commits in this repository.
We want to alter the most recent commit message.
2) Run git commit --amend and you will be prompted by your editor
which will allow you to edit the commit message. Then save and close
the file.
3) Run git log again and see that both the message and the commit
identifier has changed.

I know how to edit the message on a commit I just made

Branch To Create A Safe Working Area


Branches give us a safe space that we can commit into without disrupting what others
are working on. Commits themselves have no awareness of which branch they are
on. A commit knows which commit is its parent, and that is all. A branch is simply a
pointer or reference to a commit. When you make another commit on a branch, the
new commit points to the previous one as its parent, and the branch updates which
commit it is pointing to.
This might be easier with a diagram! The examples weve worked on so far have
been repositories with one branch (master is the default branch name), so lets start
there:
CONTENTS 15

We can create a new branch, feature, by doing git checkout -b feature. Running
git status shows us that were on the feature branch, but in fact our repo looks like
this:

Its only when we commit on the feature branch that the branches actually diverge:
CONTENTS 16

Each commit you make, by changing the files and then adding the changes, then
gets baked into something that becomes part of history. The branch pointer moves
along to point to the new commit, and the branch is made up by walking back up
the chain of commits, looking at the parent of each one.
Using branches is one of the most powerful features of git, and its useful in lots of
different situations. Usually we use branching to give us a safe space in which to
work. At its most basic level, using a branch means we can commit at logical points
throughout our work, rather than being unable to commit until the whole thing is
completely finished and tested.
Whether developing a new feature or fixing a bug, having a unit of work on a
separate branch means that we can very easily share our work with our team either
to collaborate with us or to review what we have done. The branch can be merged
and, if necessary, unmerged - but only if it was created separately in the first place.
Using branches gives us great flexibility and allows us to work quickly and commit
and share often; a vital ingredient into good development practices.
Now youve hopefully got a better idea of how the concepts fit together, lets have a
look at the commands youll need to work with branches:

List all branches: git branch


Create a branch:
CONTENTS 17

either git branch newbranchname


or git checkout -b newbranchname - this option also switches you to
the new branch
Change branches: git checkout desiredbranch

With all these tools in the proverbial box, its time to try them out

Assignment
1) Make sure you have no changes in your working area, either commit
or undo them before you begin.
2) Create a branch called excellent-feature.
3) Make two commits to the branch excellent-feature. Run git log
to check that you see the commits as you expected.
4) Go back to the master branch. Run git status to double check
which branch you are on, and git log to check that you cannot see
the commits that you made to the other branch.
5) . do nothing. Well merge in the next section!

I can create a branch in git and commit to it

Apply a Branch of Changes with Git Merge


Branching is the easy part. It is when we try to merge that things can get a little
messy. It really helps to know what is actually happening, so lets start out with
some concepts.
Merging is how we collaborate. First the changes take place in a safe space on a
branch, then we merge them into the main project. Fear of merging can lead to
avoidance of branching and therefore missing out on some of the best features of git!
This section will give you everything you need to be able to merge those branches
back in without tears.
CONTENTS 18

When we merge, were combining two sets of changes. Sometimes this results in a
conflict, well deal with those in a moment. Lets look at a very simple merge in terms
of what happens to the files at play (which is what we really care about). Here is a
repository with three files in it:

Now lets imagine we made a feature branch and made some changes to those files
- were working on a cool new feature. We finish, and by then there have been other
changes to the original master branch.
CONTENTS 19

At this point, we want to take our new feature, and put it into the codebase without
losing anything that anyone else has done. So we checkout the master branch (since
this is the one we want to work on), and type git merge feature.
What happens looks something like this:
CONTENTS 20
CONTENTS 21

The merge has taken all the changes from both branches, even where the same files
are affected, and combines them safely. Never again will you hear the question Who
last edited/uploaded the template files for site X? source control will handle this
for you. Especially if youre new to tools like this, its almost indistinguishable from
magic!
(we still didnt talk about conflicts yet, they get their own chapter later on in the
workbook)
In terms of commits, weve made this:

The master branch has a new commit that has two parents and no changeset since
this is a merge commit. You can see this also when you run git log as it shows both
commit identifiers rather than one parent:

$ git log
commit e3a7b5591cab5ca792c4b2a00cf5b2060dc2962e
Merge: b5bb230 3a39f06
Author: Lorna Mitchell <[email protected]>
Date: Tue Sep 9 17:39:27 2014 +0100

Merge branch 'feature'

This shows that the merge occurred in this commit, and that there were 2 parent
commits. The result is that your project now contains the changes from both
branches.
CONTENTS 22

Quick Quiz: Branches


Use the words and phrases from the table to complete the sentences below
safe two parents combining
merge easy new features
bug fixes different to

Branches are .. to make.


Git takes care of the changesets on the branches when we .
Merge commits are .. normal commits because they have ..
Branching gives a way of working on or
Quiz answers can be found at the end of the workbook

Assignment
This assignment follows on directly from the previous one about creating and
committing to a branch. If you didnt do that already, go there first and then come
back!

1) Add a commit to the master branch, to give the impression that


the project was still moving forward while we were working on that
excellent feature.
2) Check out the excellent-feature branch and examine its history
3) Look at what changed on this branch against the master branch by
doing git diff master - this shows you which changes you will bring
in when you merge. Youll also see the changes you just made on the
master branch being removed, but dont worry about that; they wont
be!
4) If it looks good, well go ahead and merge your changes. Since its
the master branch we want to apply changes to, go there: git checkout
master
CONTENTS 23

5) Merge! git merge excellent-feature.


6) Look at git log and observe the commits from the branch are now
present on the master branch.

I can merge one branch to another in git, correctly and with confidence

Whats The Story? Ask Git Log


git log is a real power tool. Its abilities could justify several book chapters and still
have some tricks left over for you to find.
This command lets you view your history in any way you like. Whether you need
to find commits on a particular branch, see the diff or which files were changed, or
filter by who made the commits - its all here.
The easiest thing is to play with git log yourself and see which of the options seem
the most useful for your use case. When youve finished playing, write yourself a
note to do this exercise again every quarter - its one of those commands that youll
pick up and use more and more of over time.

Assignment
1) Try each of the following and write in the table what the switch does.
Use git help log if you want more information.
--all
--graph
--raw
--author
--grep
--oneline
--decorate
2) When you see an interesting commit in the list, try git show [com-
mittish] with the commit identifier to see more information about just
that commit.
CONTENTS 24

3) Now try some of them in combination. My favourite is git log --


oneline --all --decorate --graph. Whats yours?

I know how to examine the history of a repository

Gits Staging Area


At this point you know that you need to add your changes before you commit them,
but what is actually going on when you do that?
In order to ask git to track changes, we first need to stage the changes by adding them
to the staging area. In all, there are three stages that a changeset will go through as
we create, add and commit it:

We move changes, deletions and new files into the staging area with git add. Your
local working copy and the staging area are not associated with a branch - they will
come with you if you switch branches, so it doesnt matter which branch you are on
until you commit.
The staging area shows file names, but it doesnt deal in whole files; it actually deals
in changesets. It is perfectly possible to show a file in both the staged and unstaged
sections of git status output. Try this:

edit the README.md file


git add README.md
git status
CONTENTS 25

edit the README.md file again


git status

How can a file be both staged and unstaged? What happened was that when you
staged the file, the current changes in it were staged. We can check exactly what will
be included in the next commit with git diff --staged.
When you edit the file again, the new changes arent staged. View unstaged changes
with git diff - and for avoiding committing debug messages or having to type lots
of long file paths, try staging your changes with git add -p, which shows you the
output of diff with the option of staging each chunk.
You can also unstage changes, using the git reset command - and in fact you can
selectively unstage parts of a file using git reset -p, which offers each changeset
for you to examine and reset or not.

The git reset command takes some switches that indicated how severe youd like
your reset to be. The default is --mixed, which will reset your staging area but not
affect your working directory at all. This book already mentioned --hard which
resets your staging area and your working directory, and is usually used when forcing
your branch to go back in history. The third option is --soft which doesnt change
either your staging or working directory areas.

Quick Quiz: Staging Area Commands


What are the commands to achieve each of the following?
CONTENTS 26

Add all changes from a file to the staging area?


Add some of the changes in your working directory to the staging area?
Unstage some of the currently-staged changes?
Unstage all changes from a single file, without losing the changes?

Quiz answers can be found at the end of the workbook

The assignment will give you the change to try all of these commands, and feel free
to improvise as well to give yourself some practice.

Assignment
1) Edit any three existing files in the project. Add all your changes, but
dont commit
2) Now change your mind about which files should be staged. Use git
reset to unstage everything or git reset [filename] to just unstage
one file
3) Oh no, we shouldnt have changed that last file at all! Use git
checkout -- [filename] to restore it to how it was at the last commit.
4) Boss has changed his mind, none of those changes are needed. Go
back to a clean working directory using git reset --hard.

Health Warning Beware that git reset --hard is the best way I know to totally
lose work without any chance of recovery in git. You have been warned - use with
spades of caution!

I can stage some changes, and unstage some changes, and undo my changes
completely, either selectively or wholesale

Get The Right Commit On The Right Branch


Its easy to work away and then suddenly realise that youre on the wrong branch
but dont panic!
CONTENTS 27

What is important here is that your working directory and staging area are not
associated with any particular branch. You can make changes, and even stage them,
but they only get applied to a branch when you actually commit - it is fine to switch
branches with local and/or staged changes in order to get onto the correct branch
you want to commit to!

Assignment
Getting the correct change onto the right branch is a key skill, so this is a great time
to get some practice! Try this:

1) Get out of your current repository by changing into the parent


directory of it - you should get an error when you run git status
2) Create a new repo and inside there, create a README file with some
content
3) Commit your README to the master branch (actually weird things
happen before the first commit in git so at this point we can start to
do some real work)
4) Create a new branch called experiment but then make sure youre
still on master
5) Edit the README file and add it - oh, but now switch to experiment
before you commit
6) To check which commits are on which branches, run git log --
oneline --decorate --all --graph which should show master point-
ing to your first commit, and experiment pointing to the second one.

I can commit to the correct branch in git (this makes you in the top 32% of git
users *)
* statistic entirely invented
CONTENTS 28

Spot The Difference: More Git Diff


In the previous assignment, we used git diff to check what changes wed made
and which were staged. We can also diff between a whole host of other possible
combinations of reality, including limiting by path, by branch . for lots more detail
try git diff help.
Rather than lots of reading, try the assignment to learn by doing - this covers my
personal favourite switches to this command.

Assignment
1) Make a change to a tracked file. Inspect it with git diff.
2) Add the change to the staging area, now git diff shows no output;
use git diff --staged to inspect exactly what will be in your next
commit.
3) Make the commit.
4) Run git log and pick a commit from the past. Run git diff
[committish] to see the differences between that and your current state.
5) Inspect the differences between the master branch and your experi-
ment branch from the previous example, with git diff experiment.

I can inspect the differences between versions, including branches, history and
my working directory

Power Diff with DiffTool


Gits difftool command is basically indistinguishable from magic once you have it
all set up properly. It allows you to run different (better) diff tools, and to vary the
diff tool that git will pick according to what is appropriate for this type of file.

You can replace git diff with git difftool in any circumstance.

Using the git difftool command uses all the tricks we learned when using the git
diff command, but with a little bit more sparkle? force? meaning?
CONTENTS 29

Diffing Text or Code


Usually, we see that git diff outputs a simple combined diff format, showing the
lines that were added and removed. There are so many better tools for viewing
changes between versions of files, if you use an IDE or a GUI tool for git, youll
have already seen these better alternatives. The combined diff is quick and very easy
to read, and most of the time I just use it as it is. But for a more knotty problem, or
a complicated series of changes, its helpful to see the two versions side-by-side.
First, lets configure git with your favourite diff tool. Some possible options are: *
vimdiff (not the prettiest but does show files side by side and Im a vim user) * meld
(a cross-platform GUI) * Github Desktop * something else (tell me your favourite and
I can add it to the list, this is the joy of ebooks)
To configure your platform to use meld, first install it and then do:

git config diff.tool meld

Now in any situation where you would run git diff you can run git difftool
instead - magic!
Pro-tip: if you do choose meld, try git difftool -d to get meld to open all the files
and folders at once and let you browse the changes rather than opening pairs of files
in turn. Most other GUIs will do this by default but I had to look up how to get meld
to do this.

Diffing Other File Types


Sometimes, well have items in our git repositories that are not made of text. Usually
these are documents of other formats, they might be specifications, documentation,
or something else, but we might still want to understand what changed in a given
commit. Git is pretty smart about not trying to show you a diff of files that are not in
text format, and if necessary you can tell it that a file is binary and tell it not to diff it
(this happens if you have a file format with an extension that git thinks is something
else).
As well as marking types of files as binary, you can also nominate a tool to translate
a file type into a textual representation of itself. For example: there is a utility called
CONTENTS 30

odt2txt that will render a LibreOffice Document as text so I can configure git to
use this tool whenever it would like to show me the difference between two .odt
documents.
To configure git, changes are needed to both the .gitattributes and .git/config
files. First: .gitattributes. This file is local to your repo and doesnt get shared with
other repositories when you push and pull. We will use this file to add various file
extensions to a specific file type. For the LibreOffice documents, Ill add the following:

*.odt diff=odt

Now well tell .git/config how to handle odt files by adding some configuration
there:

[diff "odt"]
binary = true
textconv = odt2txt

This says that when diffing .odt files, they are binary and therefore git should not
attempt to show me diffs of the actual file contents. The textconv setting tells git
how to get a textual representation of this document and git will use this to diff.
When I diff two LibreOffice files, Ill see the text-based changes without any of the
formatting or other features not understood by odt2txt - very neat!

MergeTool
Git can also use an alternative program to help understand and resolve merge
conflicts. The git mergetool command is to git merge what git difftool is
to git diff. You configure your chosen diff program by setting the merge.tool
configuration and then when you use git mergetool you will see three screens, the
two at the sides are one from each branch, and the one in the middle is the version
that will be used after this merge. Its a great way of seeing the original context of
each change when merging is complicated!
https://github.com/dstosberg/odt2txt/
CONTENTS 31

Assignment
1) Find or clone an active repository to your local machine - any project
that has some git history that you can play with is fine.
2) Decide which diff tool you want to use/try, perhaps starting with what
you already have installed, or my recommendations from above.
3) Run git log and choose a commit from the history, just 2 or 3
commits ago is fine. Now run git diff [rev] to see the diff since that
change.
4) Replace git diff [rev] with git difftool [rev] and see your
other program launch. What is different when you use this approach?

I know how to view diffs in better-than-standard ways

Handle Interruptions with Git Stash


Do you ever get interrupted at work? Me too!
When something comes up and you are right in the middle of a task, you have two
options, neither of them are good. You can either commit half a thought, creating
a strange, unfinished story, or you can try to fix another thing with a half-chewed
changeset in your working area. Actually the third option is to throw away all your
changes so you have a clean repo, but lets stay away from that one.
Enter the stash command, which lets you safely put your work aside for a moment
while you deal with something else. In particular its useful when you cant
switch branches because you have something in your working area that would be
overwritten by a change in the other branch.
You can stash multiple stashes, they are stored in a stack so by default youll always
get the most recent thing you stashed - but you can also manipulate that list. Run
git help stash for more instructions.
Final note: its possible to lose work from your stash, so unless you really are putting
it aside for just a few minutes, Id recommend creating a branch and committing
instead - you can make your history logical again later (see the section on interactive
rebasing).
CONTENTS 32

Assignment
This is a pretty simple feature so the easiest way to show you is to let you have a go.

1) Make a change to whatever branch you are on, but dont commit it.
Instead type git stash
2) Run git status and marvel at the absence of the work you just did
3) Make more changes and type git stash again
4) Look at the stashes by running git stash list
5) Apply the newest one by running git stash apply, then run git
status and then git stash list again. What happened?

I can safely put my work aside for a moment, and still get it back

Cherry-Pick Changes From Another Branch


The git cherry-pick command is for the situation where a commit exists, but
should be on that other branch over there. If were only dealing with the tips of
branches you have options (mostly involving git merge which Ill cover in a bit),
but if this commit is in the middle of a branch, weve got a problem
which we can solve with a command called git cherry-pick. This takes a commit
from the middle of a branchs history:

And applies it to the tip of your current branch:


CONTENTS 33

Note that since the new commit has a different parent, its identifier changes despite
it being exactly the same changeset.
This is super-useful for when you fix something small in the process of working
on one thing, and then it becomes apparent that your small fix was actually quite
important and should be used immediately on another branch.

Assignment
1) Create a new branch from master called fixes. Make three commits
to this branch
2) Identify the revision number (or copy it into your clipboard) of the
first commit you made to fixes
3) Now checkout master (in git we usually checkout the branch that
were going to make changes to)
4) Apply the first change from the fixes branch to master by typing git
cherry-pick [committish]
5) Check that you have master branch plus that one commit using git
log

I can cherry pick changes from one branch to another.

Git Revert
The revert command is the opposite of cherry-pick, because instead of applying
the changeset, it applies the inverse changeset.
CONTENTS 34

If you have a repository that looks like this, with a commit that shouldnt be there
somewhere along the line:

Sometimes this might be the newest commit, if you havent pushed your changes, you
can reset your branch to before the commit, but if you have pushed your changes, or
if there are more changes after the mistake, then you should use the revert technique.
The command is git revert 49c9 - and what actually happens is that a new commit
is generated, with the exact opposite diff to the named commit, so you get something
that looks like this:

This is a safe way to undo changes without erasing history, the revert will always
show what was done, and then what was undone, so you can look back and
understand what happened.

Quick Quiz: Git Undo


Which command will undo your changes safely if:

you didnt commit yet?


its your most recent commit (and you didnt push)?
the commit is not the most recent, but you didnt push yet?
you already pushed your changes?

Quiz answers can be found at the end of the workbook


CONTENTS 35

Assignment
1) Run git log and pick a commit from 3 or 4 commits in the past. The
situation is that we need to undo these changes, but we have already
pushed our changes.
2) Now use git revert to make a commit with exactly the opposite
changeset to the one in the commit you chose.
3) Check that the files changed in the way you expected.

I used git revert to make an undo commit

UnMerge Changes
Merging feature branches in is a regular feature in git, but sometimes we need to
undo a merge. This chapter will show you how to proceed - but if youve already
pushed your changes, you probably want to read the previous chapter about git
revert instead!

The reset command with the --hard switch simply moves a branch pointer from one
commit to another, and updates both your staging area and your working directory
to contain what was in that specific commit. Weve seen some examples in earlier
chapters that made use of this technique, but lets examine it in detail now.
If we start with a master branch with a feature branch merged into it:
CONTENTS 36

We can set the master branch back to before the merge again (use git log to find
which commit was before the merge):

git reset --hard 40ba

Afterwards, our diagram looks something like this:

Since the branch is simply a pointer to the commit, the reset command can move
that pointer around in any way. Powerful!
CONTENTS 37

This chapters assigment will walk you through a bit more merging practice, followed
by use of the git unmerge feature.

Assignment
1) Make sure you have no uncommitted changes in your local repo (this
is a good place to start with almost everything)
2) Now we need something to merge. Create and switch to a new branch
called masterpiece, and add and commit some new feature here.
3) Go back to your master branch and add at least one more commit
here.
4) Merge your masterpiece into the master branch. Check that git log
shows what youd expect (use the --graph and --all options to see how
the commits relate to one another), and also that your filesystem now
has both sets of changes in the same place.
5) Oh no! We merged in too soon, we need to undo the merge. Dont
panic .
6) Run git log and look at the merge commit, then the one before it
(still on the master branch), and note its commit identifier.
7) Now well just move our master branch back in time to point to the
commit before the merge by running git reset --hard b5bb2 (use the
committish you got from the previous step)
8) Run git log again to check that the master and masterpiece branches
look as you would expect.

Health Warning This was already mentioned but git reset --hard is a really good
way to lose work in git. The next section covers what to do if it happens.

I can correctly merge and undo a merge in git


CONTENTS 38

I Lost My Work: Git Reflog to the Rescue


Imagine the scenario. Charlie is working late at the end of the week, then realises
she made a mistake. She knows how to undo things so she runs git reset --hard
. but it wasnt the right thing and now all her work is gone :( Charlie needs rescue!
Heres the most important thing to know: if you lose work in git, so long as it was
committed to a branch at some point recently, then its recoverable (this is why its
better to create a branch and commit than just throw things away, you can always
clean it up later if you really dont need it). Less important but also relevant: you
have 60 days in which to recover it. So Charlie should go home, enjoy her weekend,
and well clean up the mess next week!
Where is this secret archive of lost works? In git this is called the reflog, basically
an audit trail of every single thing that happens on your local repo, whether you
are committing, adding, switching branches, whatever. All your actions stay in there
until they are garbage collected, usually 60-90 days after they happened.
To see your reflog, run git reflog (it also accepts switches to look for a particular
change or time period, see git help reflog for more info). Take a look at my reflog
after I did the previous exercise of creating a branch, merging it, and then resetting
back to before the merge:

$ git reflog
b5bb230 HEAD@{0}: reset: moving to b5bb2
e3a7b55 HEAD@{1}: merge feature: Merge made by the 'recursive' strat\
egy.
b5bb230 HEAD@{2}: commit: bugfixes
083a068 HEAD@{3}: commit: refactoring
c08074a HEAD@{4}: checkout: moving from feature to master
3a39f06 HEAD@{5}: commit: great feature
c08074a HEAD@{6}: checkout: moving from master to feature
c08074a HEAD@{7}: commit (initial): this is the repo we'll work on

This shows all the commits that Ive visited during my work - importantly this
includes the merge commit that I went on to undo later on. It wont be possible
CONTENTS 39

to view this commit via git log because it no longer has a branch or tag pointing to
it, but the reflog lets us see it.
Once we see the commit we want, we have a few options:

Run git reset --hard e3a7 to get the current branch back to that
lost commit
Checkout that commit to see if it has what you need by doing
git checkout e3a7. This puts you in a detached head state but
dont worry! If you decide this is a useful piece of work that youd
like to keep then you can either use the reset command above,
or simply create a new branch at this point with git checkout
-b newbranch. If you are finished looking, you can git checkout
master at any time to back to reality.

Its all very well knowing that this exists, but lets practice using it before you really,
really need to know how.

Assignment
This follows on from the previous assignment; you had merged a branch and then
reset to undo the merge.

1) Look at the commits you have with git log --oneline and observe
that you cant see the merge commit you did before resetting
2) Run git reflog and identify that merge commit that you undid
before
3) Now reset back to that commit so that master has the merge again.

I can recover previously lost changes in git


CONTENTS 40

Handling Conflicts - in Git and in Teams


Merge conflicts happen. Rebase conflicts happen as well. The strategies for dealing
with both are very similar, and theres a simple process to follow which I will show
you. First though, what is a conflict and why does it occur?
A conflict is a situation where git cant figure out what to do without some help from
a human. Its name sounds quite scary but its actually a child raising her hand for
some guidance from you. Conflicts occur when two branches affect the same lines
in the same file - or more accurately, when the same changeset has incompatible
changes in two places. Git is smarter than many other source control systems because
it identifies code by similarity, not by line number, so in general I see fewer conflicts
when using git than with older systems such as Subversion.
This assignment will show you how to resolve the conflict at a technical level, but if
youre experiencing a lot of conflicts then your team needs to discuss that together.
Did changes get made in two branches because one person or team wasnt aware
what another person or team was working on? Better communication usually helps
to keep conflicts down, but they arent always avoidable so youll need to know what
to do.
When a merge conflict occurs, youll see a message like this:

$ git merge improvement


Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.

I tried to merge my improvement branch into the master branch but there were
overlapping changes and git needs me to go and work out what to do. At this point,
I am now in a merging state; you can check this by looking at the output of git
status, which will say something like:
CONTENTS 41

$ git status
# On branch master
# You have unmerged paths.
# (fix conflicts and run "git commit")
#
# Unmerged paths:
# (use "git add <file>..." to mark resolution)
#
# both modified: README.md
#
no changes added to commit (use "git add" and/or "git commit -a")

At this point you can pretend this never happened: run git merge --abort and your
repo will return to its pre-merge state. You can do this at any point during a merge
and its handy for getting your repository back to a clean state while you wait for
someone to come out of a meeting and explain the merge, or help you, or whatever.
Or while you wait for someone else on your team to sort out the mess?
Sometimes, we do need to handle the conflict. In this case, I have just one file with
conflicts (any other files that were changed in the merge will be added for me, so
youll see them listed but you dont need to do anything with them. If we look inside
that file to see what happened, we see both sets of changes with a bit of ascii art to
show which is which:

My Project
##########

This is my readme file


<<<<<<< HEAD
for a really awesome project.
=======
for an excellent project.
>>>>>>> improvement

You should download


CONTENTS 42

and install it.

There is a bug tracker.

Oh, look. The same line has been changed in two different ways in the two branches.
We can help git out by editing this file to make it look correct. Make sure that you
remove the lines starting with <<<<<, ====== and >>>>, fix the rest of the file so the
conflicted line(s) look correct, and save the file.
Running git status will still show the file as conflicted until you add it, then it will
suggest that you run git commit to complete the merge - so do that, and were done.
The basic process:

Oh no! A conflict.
Run git status to see which files are conflicted.
Fix the files.
Add the files.
Type git commit; the commit message will already be stored from when the
commit was intially attempted.

I also have two top tips to share with you about merge conflicts:

1) If theres a lot of conflicting change (usually a result of coding


standards fixes) and you really just want to use the whole file from
one branch or the other, you can use git checkout --ours README.md
to use the one from the current branch, or git checkout --theirs
README.md to use the version from the incoming branch.
2) In longer files, its less easy to spot the conflicts so I use a search for
"====" to spot them. Once youve resolved one though, BEWARE that
there could be others and git will not warn you if youve only fixed one
(or none!) of them. So once youve found and fixed a conflict, always be
sure to search for another before you save the file!

The main thing with a merge is to keep calm, and know what to do. Work through
the assignment and then youll be all set for this eventuality.
CONTENTS 43

Assignment
The hard bit is always contriving a conflict on a simple example, here goes.

1) Switch to the master branch and check you have no pending changes.
Create a file called trouble.txt, add a few lines, then add and commit
it.
2) Create a branch called awkward but do not switch to it, stay on (or
return to) the master branch.
3) Edit trouble.txt, and add/commit your changes.
4) Checkout the awkward branch, and edit trouble.txt again, changing
the same lines of the file in different ways
5) Checkout master, then try to merge the awkward branch in - this will
result in a conflict
6) Resolve the conflict, and complete the process by adding the affected
file (after you have fixed it), and committing.

I can safely handle merge conflicts in git

Merge Types
In the previous examples, youve seen that a merge will take place and git reports
merge made by the recursive strategy. Sometimes, youll see instead Fast-for-
ward, which means that there were no changes on the target branch while the
incoming branch was in progress. So if a recursive merge, with changes on both
branches, looks like this:
CONTENTS 44

Then a fast-forward merge looks more like this:

The feature branch is created and changes are committed to it, but there are no
changes in the meantime on the master branch, so there isnt anything to combine
when we come to do the merge. By default, git will do a fast-forward merge, just
moving the master branch pointer to the tip of the feature branch.
If this isnt what you want, and you want to create a merge commit as we have done
in the previous examples, then you can force git to do that by adding --no-ff to your
merge command. This can be useful if, for example, you want it to be clear which
branch work was done on, and where that branched off master and was merged
back in. Forcing git to make the merge commit gives a clearer paper trail of what
happened.
CONTENTS 45

Assignment
1) Checkout the master branch and check you have no pending changes
(this should seem familiar by this point!)
2) Checkout a new hotfix branch and apply a commit or two to it
3) Merge hotfix into master. Oh no! It did the fast-forward and we
didnt want it to
4) Undo the merge (look back at a previous exercise, you did do this
already)
5) Now repeat the merge but without allowing git to fast-forward.

I know what a fast-forward merge is and how to prevent it if thats not what
I wanted

Create A Fast-Forward Merge For A Clean


History
Some projects try to remove all those messy merge commits from their history and
show only the commits that added something in feature terms. To achieve this, we use
a feature called rebase, which transplants a branch from where it really originated
and makes it look like we started this branch from somewhere else - usually the place
were about to merge it back into.
The procedure for this is to checkout the branch we want to transplant, lets call ours
featureX, and tell it where to rebase onto. This can be any commit reference, but is
usually the tip of a branch, e.g. git rebase master.
In handwavy terms, were doing something like this:
CONTENTS 46

Git narrates its progress pretty well, with the comment rewinding head to replay
your work giving a good clue. It will then read out each commit as it succesfully
applies it, or notifies you if theres a problem. The result is output that looks like this:

$ git rebase master


First, rewinding head to replay your work on top of it...
Applying: fixing a problem with the API endpoint to create things
Applying: updating the documentation to fix the new API data format

Once this is done, you can see (from the diagrams or from git log --all --graph
if you have tried this yourself) that weve set ourselves up to achieve a fast-forward
merge that we looked at in the previous section. Whether you choose to rebase
regularly or not is a feature of your chosen work process and branching strategy,
but its very useful to know HOW to - you can use this technique for example when
someone has branched off the wrong branch by mistake when creating a new feature.
CONTENTS 47

One thing to look out for is that the commit identifiers of all the commits on your
branch will change, because they depend on the parent and the original parent
commit has changed. As a result, it isnt advisable to push a branch after youve
rebased it if it was already shared. Instead a common pattern is to rename a featureX
branch to featureX.rebased or similar.
(Rebasing can also be used to rewrite history on a single branch, that bit is in the
next section).
Rebasing feels a bit like performing surgery on your repo, but there are a limited
number of things that can go wrong. They are:

You panic (valid response). Try the git rebase --abort command
if this eventuality occurs.
There is a conflict. See earlier sections on handling merge conflicts,
its the same for rebasing. Once your conflict is sorted, you tell git
to carry on with git rebase --continue

The key with rebase is to really understand whats happening, so in the practical
section, well do lots of checking on what is going on.

Assignment
1) Start with git status to check for being on the master branch with
no changes. This is a good habit to get into whenever you return to your
git project after a break.
2) Create a new branch called issue-42, but stay on the master branch.
3) Add some commits to the master branch.
4) Add some commits to the issue-42 branch. Its up to you whether you
edit the same line of the same file on both branches and try handling a
rebase conflict while youre here.
5) See that you have two branches with commits since the most recent
commit they have in common (I use git log for this)
CONTENTS 48

6) Run git status, check that you have no local changes and check that
you are on the issue-42 branch - because this is the one we want to alter
by rebasing it.
7) Now rebase! We want the commits on the issue-42 branch to remain
intact, but look as if the branch was created now from the master branch
and your amazing feature was created very quickly indeed.
8) Assuming everything is still okay, use git status to check that the
rebase is complete (if not, it will tell you, so its worth checking if
anything happened that youre not sure about) and git log to check
that the commits on all the branches look as you would expect, with the
master branch commits before the issue-42 ones.
9) Now checkout master, do a git merge issue-42 and admire your
fast-forward merge.

I can rebase a branch onto another branch in git, and I understand why you
might want to do that

Rewrite History With Rebase Interactive


Another use of the rebase command is to quite literally rewrite history - the history
of your branch.
Before we go any further, there are some caveats. It is not recommended to rewrite
shared history; this means that you dont rebase commits that you have already
pushed, or that instead you should create a new branch name as we did when we
transplated a branch with the rebase command in the previous section.
Now Ive got the warnings out of the way, lets get on. This feature is called
interactive rebasing and it allows you to take a range of recent commits, and change
them in any way you like. Your choices are:

Where you have already pushed really means someone has already pulled. If youve pushed a branch to your own
repo and you werent expecting anyone to pull or collaborate on it, then you can do as you please!
CONTENTS 49

Action Outcome
pick keep the commit (this is the default)
reword get the chance to change the commit message
edit edit the commit
fixup combine these changes into the previous commit. Useful for
the fixed the bug, followed by no really fixed it this time
situations
squash still combines commits but prompts you with an editor with
all the commit messages present in order for you to make
the correct commit
remove to pretend a commit never happened: simply delete the line
for it

Pretty powerful stuff. One thing that always confuses me is that the order of the
commits is in the order that they happened, with the oldest first. Which is exactly the
opposite way round from the way I usually see my history, via the git log command!
First of all: git log

$ git log
commit e11e694f62832dc84473350d838040aa94977280
Author: Lorna Mitchell <[email protected]>
Date: Mon Oct 20 10:49:47 2014 +0100

Bug definitely fixed by this commit

commit c8c923986d5df6e9281a77884af4f031e5355c15
Author: Lorna Mitchell <[email protected]>
Date: Mon Oct 20 10:49:29 2014 +0100

Fixed bug 123 that's been causing all the issues

Oops! I think you can see the problem here. The interactive rebase is exactly the
right thing to iron out this history. Since I know I only want to operate on those last
2 commits, I can use this command rather than looking up which revision I want to
go back to: git rebase -i HEAD2.
When I do so, my editor opens a file that looks like this (the gotcha is that these
commits show in time order, oldest first, unlike git log. This confuses me every time):
CONTENTS 50

pick c8c9239 Fixed bug 123 that's been causing all the issues
pick e11e694 Bug definitely fixed by this commit

There are also some comments in the file, which is basically the documentation for
the various options we showed above. Each commit is listed with its revision number
and commit message, and starting with the word pick. Next, I will edit the file to
describe what should happen, and then git works through line by line to actually do
it. At each point where my input is needed, it will pause and let me either edit the
message or actually use git. git status will warn me that I am
still mid-rebase while thats the case, but I can type git rebase --abort to undo it
all at any time during the rebase.
If I dont change anything and just save and close the file that git prompted me with,
then git will simply take and reapply all the changes, one at a time. This is the default
behaviour and the edits that I make to the file tell git what to do differently this time
around.
For the situation above, Im simply going to squash that commit into the first one, so
it looks like I can actually fix a bug properly:

pick c8c9239 Fixed bug 123 that's been causing all the issues
fixup e11e694 Bug definitely fixed by this commit

When I save and close the file, git will make a new commit (with a new identifier)
containing all the changes needed to fix this bug. Since I chose the fixup option, I
dont need to supply any more information or write a new commit message.
Now my git log looks much tidier:

$ git log
commit 83347acaf9e9efeaf948fea8718cfa40af0fb069
Author: Lorna Mitchell <[email protected]>
Date: Mon Oct 20 10:49:29 2014 +0100

Fixed bug 123 that's been causing all the issues


CONTENTS 51

Exactly like when we merge or rebase a branch, conflicts can occur here too. Git will
stop, and give you the space you need to resolve the conflict exactly like you do for a
merge conflict: identify the conflict, edit the files, then add and commit. Theres no
difference in what you need to do and when you are finished, git will continue with
its rebase.

Quick Quiz: Rebasics


For each of these situations, would you use rebase, or rebase interactive?

To fix a branch that branched from the wrong place?


To make a branch be based off the current tip of master and therefore
include all the changes in master?
To edit the history on a single branch?

Quiz answers can be found at the end of the workbook

Assignment
1) Get onto the master branch and create three commits here.
2) Look at the history with git log, then use git rev-parse HEAD3
to check which revision well be rebasing back to.
3) Rebase all three: delete the first commit you made and combine the
other two using squash
4) Look at the history again to check that you now have one commit as
you expected

I can rewrite (unpushed) history in git


CONTENTS 52

Get Started With GitHub


Weve come a very long way in our git skills already in this book, but working entirely
with our local repositories. This approach is designed to make sure that you master
all the key skills and become confident with those, before we add more repositories
into the equation. Working with remotes is straightforward in comparison to the
rebasing and other skills that weve already covered, especially now youve seen so
many other git concepts and understand how the moving parts work.
Working locally is great, being able to branch and commit and manage various work
streams is a fundamental cornerstone of good development practice. This chapter
shows you how to start to collaborate with other users, and take advantage of the
most powerful features of git.
In the modern era of social coding, well probably also deal with accounts on GitHub,
Bitbucket, or any one of a number of other providers, so this section will cover that
too. To begin, make sure you have an account at GitHub, its free to sign up.
First a bit of github-rather-than-git terminology: we need to fork a repo. To do this,
visit the repository you are interested in via your web browser (you need to be logged
in to GitHub) and click the fork button you find on the top right hand side of the
screen. This creates a clone of the repo on your own github account, which means
you can write/push to it.
Once you have your own fork of a repo (if you have write access to a repo, you may
choose not to fork it, but in this case you dont so our examples use forks), you can
clone it to your local machine. To do this, look on the right hand side for a clone
URL. Copy it, and use it in this command on your local command line:
git clone [url]

Git chatters away and then tells you that you have a new directory with a repository
in it. What just happened? Well, something like this:
http://github.com
CONTENTS 53

At this point you have a local repo and all the git skills you have learned until now
can be used!

Assignment
1) Log in to GitHub and then visit https://github.com/gitworkbook/accompaniments
2) Fork the repo and make sure that there is now a repo of this project
at a URL which has your username instead of gitworkbook in it.
3) On the page of your copy of the repo, look for the clone URL on
the right hand side bar. It comes in different flavours, the recommended
default is HTTPS but if you would rather set up SSH keys than confirm
your password regularly, then read https://help.github.com/articles/
generating-ssh-keys and then use the SSH URL. Whichever way you
choose, you should now copy that URL.
4) Put the URL into the git clone command. You will get new directory
with the same name as the default repo (you can override this by
supplying an additional argument for the directory name).
CONTENTS 54

5) Change into that directory and observe that there is a git repository
there.
6) Make some changes, and commit them.
7) Push your changes, and then visit your fork on the github website and
you should be able to see them there, too.

I can both fork and clone a repository from GitHub

Understand Multiple Remotes


When you clone a repo, rather than doing git init to make a new one, git adds a
reference to the repo that you cloned from; it appears as a remote named origin.
Check out what happens in your new repo that you cloned from your GitHub
fork when you type git remote. This is a list of the other repositories that this git
repository knows about, and you can add to the list (and see where they point to by
adding a -v to the command).
Although they are called remotes, they dont have to be very far away! Its
entirely possible to have a remote which is just another directory on the same
disk - something I use frequently when teaching a class with an unreliable internet
connection.
We can add any remotes we like to our repo, those might be the remotes of other
people we are collaborating with. Usually we also add the main project repository,
often referred to as upstream. By adding the upstream repository we give ourselves
a way to keep up with changes that happen in the main project and bring them into
our own repository. The process will look something like this:
CONTENTS 55

This is very important when collaborating because it means that our work can be
based (or rebased) onto the current version of the project, rather than sliding further
and further out of date as the clone gets older.

Assignment
Its difficult to arrange upstream changes on a github repo, so well do this exercise
with some local repositories. This also works to show you how multiple repositories
on the same machine could work, without GitHub or forks in the mix.

1) Get out of any existing git repositories, were going to create a few
at the same directory level as one another (you might like to create a
directory to put them all in).
2) Create the upstream repository by running git init upstream. Make
three commits of anything you like here, on the master branch.
3) Go up a level so youre outside the repo again. Clone this upstream
repo like this: git clone --bare upstream origin.git. The --bare-
- switch means that this repo doesnt have a working directory, if you
CONTENTS 56

cd into it you will see contents that would normally be found in the
.git directory of a repository. Since it is just a repo and not a working
directory, we use the .git suffix as a convention.
4) Make sure you are not inside a repo, then clone the origin.git repo
you just made into a directory called local
5) Confused yet?? You have three repositories, upstream, origin and
local - to reflect how things look when we fork an upstream repo. So
now were set up and ready to begin the exercise!
6) Next well make some changes in the upstream repo, there is a new
feature in our project. Make sure you are using the upstream repo and
add a couple of commits to it.
7) Switch back to your local repo and run git remote -v to check
which remotes it knows about. Add the path to the upstream repo as
a remote called upstream, then run git remote -v again and check
that this reflects what you expected (what did you expect?).
8) There are new changes upstream of our repo. Run git fetch up-
stream to get the changes from upstream into the current repo. This
brings in all the meta data and allows us to checkout those branches,
inspect the changes, and so on, but it does not affect any of the branches
on this repository. To apply the changes from the incoming master
branch to the local one, you need to merge: checkout the master branch
and run git merge upstream/master.
9) Its more common to use pull than fetch for master branches. pull
does both the fetch and the merge command in a single swoop. For
a master branch, then the changes made upstream arent negotiable,
but for most other branches that we collaborate on, you might like to
examine the changes first, in which case you would fetch and refer to
those branches by their repo/branchname qualified name.
10) So the changes are in our local repo, but what about our fork? This
is the final stage in the process and it goes like this: git push origin
master, run that command and see your changes go into the origin repo.
11) Since the master branch on your local repo is already set up to track
the master branch on the origin repo (check that this is true by running
CONTENTS 57

git branch -vv), you could achieve the same thing as the previous step
with the shorter command git push - git will assume you meant the
branch that is being tracked.

I survived an incredibly convoluted example, and learned to add remotes, push,


pull, and stay in sync with changes to an upstream repo

Use Tracking Branches for Easy Collaboration


The previous assignment showed that we can use git push rather than git push
[repo] [branch] and allow git to infer that while we didnt specify the details, it
should just use the tracking branch for this branch. Lets look at this feature in more
detail.
There are two ways to view the tracking branches. One is to get the list of your
branches with git branch but append the double verbose switch -vv to get the
tracking branches (if any) shown against each branch. The other way is to look in
.git/config at all the branch sections. Heres an example from one of my config
files:

[branch "master"]
remote = origin
merge = refs/heads/master
[branch "cool-feature"]
remote = upstream
merge = refs/heads/cool-feature

If you want to stop a branch from tracking another, or change what it tracks, you
can edit this config file manually.
To set up a branch to track another branch in the first place, you have two main
options:

for example if someone creates a feature4 branch on the upstream


repo (and you dont already have a feature4 branch locally), you
CONTENTS 58

can fetch the upstream changes with git fetch upstream and
then do git checkout feature4. Git will then create the branch
and notify you that it is tracking the upstream one of the same
name.
if you created a branch locally, you can create the link when you
push it to a remote by using the -u switch. So if your new branch is
called fix-failures then you can do git push -u origin fix-
failures to link your branch with the one on the origin repo.

Once your tracking branches are set up, you can just use git push or git pull to
push and pull changes between the local and remote pairs of branches - and you can
still push and pull changes to other branches or remotes too just by specifying those
as you usually would.

Assignment
This assignment continues from the previous one and assumes you have a local and
remote origin repository

1) Make one commit to the master branch on the local repository.


Then run git log with the decorate switch to see the difference
between your local master branch and the origin/master branch on
your GitHub fork.
2) Create a new branch called fix-it-all and commit to it. Run git
branch -vv and observe that there is no tracking information for this
branch.
3) Run git push and see git prompt you to set the tracking branch. The
-u option I showed earlier is a shorthand for the --set-upstream option
git shows in its message.
4) Run git push -u origin fix-it-all. Now do git branch -vv and
check that the tracking info is visible.
5) Commit to the branch again, then try git push. It should know where
to push to this time, because you configured the tracking branches.
CONTENTS 59

I understand and can configure tracking branches to allow me to collaborate


more easily and quickly

Pull With Rebase When Your Branch Is Out Of


Date
Picture the scene: the developer arrives early at work, ready to work on a feature
she and her team have been making great progress on. She builds a feature, piece by
piece, committing as she goes. Then the git push command fails she forgot to git
pull before her commits.

What happened? The local and remote versions of the branches have diverged. We
know that feature and origin/feature are different branches; usually we set them
up as tracking branches but they are just as different as two different-named branches
on a single repo.
From this point, there are two options:

1) Allow the git pull command to add a merge commit to the local
feature branch and then add an additional merge commit when adding
those changes back onto origin/feature.
2) Ask git to rebase the feature branch onto the tip of master. We can
do this very easily with the command git pull --rebase - this updates
your local branch with changes from upstream, then adds any local
commits on top of that. At this point we can git push our changes and
everything is as it should be.

The first solution is simpler, and git will try to create the merge commit in this
situation (remember that git pull is just git fetch with git merge attached) so
to avoid this you will need to empty the file when you are prompted to write the
commit message. Saving an empty file will abort the commit. Using git pull --
rebase however rewrites the history on the local branch to make it look as if it had
never got out of sync from the upstream one, making it a very useful trick in this
(common) situation.
CONTENTS 60

Assignment
You will need two repositories for this, to simulate the local/remote pattern.

1) Create one repository, and add some commits.


2) Clone this first repository to create a second repository with the first
repository as its origin.
3) Add some commits to the first repository. Now checkout a different
branch (were about to push to this repo and you cant push to a currently
checked out branch).
4) Add some totally unrelated commits to the second repository. Now
try git push git tells you that Updates were rejected because the tip
of your current branch is behind its remote counterpart.
5) Use git pull --rebase and check that git log now shows the
commits from the origin repo, followed by the commits you added here
on the second repo.
6) Finally, git push and see it complete - you can check on the first repo
that things look as you epect.

I can put my new commits onto a branch that got out of date

Git Hooks
Git hooks are scripts that we can set up to occur in response to given events. They are
stored in the .git/hooks directory inside your repository. By default it will contain
sample placeholders for all the various kinds of hooks you can use.
Im sure you can think of places in your own process where it would be useful to have
an automated script run rather than someone having to remember to do something.
I mostly see this in a couple of key places:

A pre-commit hook to do a syntax check (well use this as our


example since I have a script for it)
CONTENTS 61

A post-commit hook to do deployment, used when pushing to a


repo on a remote server

One important thing to note is that the scripts are set on a per-repository basis, theyre
not shared when you push and pull to other repositories. So if you change something
in your script, or you want your colleagues to use the same ones, you need to agree
that via another channel because git wont pick it up by itself. On the plus side, it
means nobody needs to know that I need a syntax check to catch my silly mistakes!
To use or create a hook in your own repo, create a file with the name of the event
that will cause it to run - if you need a hook to do mulitple things then you will still
have one script that does them all. Put your script into the .git/hooks/ directory in
your repository, and check that it is executable. Thats it - the script will run next
time the event that it hooks into occurs.

Assignment
1) Theres a sample hook in the examples/ directory of the accompani-
ments repo called pre-commit. Copy it into the .git/hooks directory of
your repo
2) Edit the test.php file, then add and commit it.
3) Now edit the file again and break the PHP syntax (no need to be a PHP
expert, feel free to let your cat walk on your keyboard at this point). Now
go ahead and add and commit again - and see what happens

I used a git hook to spot a problem in my project

Projects Within Projects: Git Submodule


This section deals with a feature called Submodules. I think the WHY is more
important than the HOW (although the HOW is pretty damn tricky too!) so excuse
my diversion to explain what problem this feature is trying to solve.
https://github.com/gitworkbook/accompaniments
CONTENTS 62

Submodules are where one project has another project as its dependency, either
because the two projects are maintained separately or sometimes because the
submodule is a dependency of many other projects too; a pattern we usually refer
to as Common Code. Often, the tools that we use already have ways of managing
dependencies and even tracking bleeding edges of dependencies - I work in web
scripting languages so npm, bundler, composer, pip - all those tools are one way
to solve this problem. Some of those tools play nicely with using another github repo
as a dependency also, and you many not find you need submodules at all. As a team,
youll need to decide what works for your situation.
Assuming you go with submodules, keep reading :)
The best way to describe submodules is one thing inside another. Imagine a big shed,
with a small shed inside it. From the outside of the big shed, it looks like a shed. When
you get inside there, you can see that there is a smaller shed as well as whatever else
is stored here. But when you get inside that small shed, it just looks like youre in a
small shed with nothing particularly special about it.
What have sheds got to do with it? Well, the repositories work more or less the same
way: the main repo just looks normal from the outside but has some special features
from the inside. The submodule repo just looks like a repo once you get inside it; it
doesnt care its part of something bigger.
The tricky part about working with submodules is keeping everyones changes in
sync. There are so many moving parts here:

the main repo contains a submodule, which knows which directory holds the
submodule and what revision that submodule should be on
the submodule directory contains a repo that behaves like a normal repo
when you push changes to your main repo, you must also push changes to
your submodule repo, otherwise other users will get a pointer to a revision
that doesnt exist in their submodules
https://www.npmjs.org/
http://bundler.io/
https://getcomposer.org/
https://github.com/pypa/pip
CONTENTS 63

when you make a change to your submodule, you must also commit and push
on your main repo otherwise your collaborators repos wont realise they need
to use the newer repository in their own submodules

If this all sounds very confusing, dont worry. Most things make more sense when
you actually see them in action, so heres the assignment for you.

Assignment
1) Make sure youre not already inside a git repo, and create a fresh one
called project. Add some commits here.
2) Now come back out of your repo and create another new repo, called
library - this will be our dependency. Make some commits here too.
3) Go back to your project repo and well add in our submodule de-
pendency: git submodule add /path/to/library library. This adds
a submodule in the library directory pointing to the repository you
specified (which might often be a remote URL rather than a local path).
4) Run git status and check that you see a new library file and also
a .gitmodules file. Look what is in the .gitmodules file.
5) Add both files and commit. Git records the submodule as just which
commit it should be on.
6) Go back to the library repository (the original copy, not the one
inside your project repo) and add another commit.
7) How can you get that change to show up in your project repo?
Go into the project repo and make a change to a file unrelated to the
submodule, but dont commit. Run git status and look at the output
showing what you changed.
8) Enter the library subdirectory and run git status again this
repository has no idea that it is inside another repository. Bring in the
changes that you made earlier by running git pull and check that you
can see them as you expect. Run git status (yes, this is becoming a
habit, but a useful one).
CONTENTS 64

9) Come back up a level into the project directory and run git status.
The submodule shows as changed. This is a really key point. If you
change the submodule (it is completely fine to work on it in place and
commit and push from there), then you ALSO need to change the parent
project, otherwise it just continues to point to the old version of the
submodule.
10) Add and commit the library submodule in the parent project to
move it on to point to the current version of the submodule. When you
share your changes, everyones repositories will know to use this new
revision
11) Now make a change directly inside the submodule inside your parent
project. It is important to note that if you change the submodule, then
commit and push the parent project, you MUST also push the submodule
otherwise everyone else will have a repo that points to a commit that
doesnt exist.

I can use submodules in my project, and safely make, track and share changes
in both my own project and the submodule

Git Bisect for Fault Finding in a Forest of


Commits
Do you know how to play guess the number? This game is known by other names
too, but basically it works like this:

You think of a number in an agreed range


I try to guess it. If I am wrong, you tell me whether your number
is higher or lower than my guess
I guess again

To do well at the game, or indeed in searching for anything, it helps to reduce the
amount of space you need to look in - so you guess somewhere in the middle and
thereby halve the amount of space you need to look in.
CONTENTS 65

Git has a command called bisect which helps us to quickly narrow down where in
the history of a branch a particular problem was introduced. To do this, it looks at
the last good commit, the current bad commit, and checks out something in between
the two to let us test if the problem is present in this version. This is a very handy
feature to know about so that you can quickly identify when something went wrong,
and in fact you can also ask git to use a script to test each revision so it does all the
checking out and testing itself.
First of all, we use git bisect bad [rev] and git bisect good [rev] to indicate
where we have known good and bad states. Then git picks a revision from the middle
of the range for us to test.
In these diagrams, grey commits are outside of the range under consideration, red
ones are known to be broken, green ones are known to be working, and the blue one
is the commit were testing

We test the code at that point, and say whether this is good (working) or bad (broken),
and then bisect will give us another revision to try.

Then we repeat this over and over, eliminating part of the search range each time.
CONTENTS 66

Finally, git tells us which commit broke things!

Assignment
1) Clone the bisect example repository from this location: https://github.com/gitworkbook/
example
2) Start by telling git that the last good commit was e2acddb by running
git bisect start followed by git bisect good e2acddb. This is a
commit where we are sure everything was working correctly at that
point.
3) Tell git that the repo is currently broken by running git bisect bad
(the omitted committish causes git to assume you mean HEAD).
4) At this point, git has checked out a revision for you which is in the
middle of the range of commits that we need to search. The bug is that
the file important.txt got deleted - so for each revision it checks out,
you need to check whether the file exists. If it does, you can say git
https://github.com/gitworkbook/bisect-example
CONTENTS 67

bisect good (again, itll assume you mean the current HEAD revision),
but if it is missing, run git bisect bad instead.
5) Repeat the previous step until git tells you which revision introduced
the problem
6) Finally run git bisect reset to go back to the tip of the master
branch (and at this point you can go on and fix the problem, perhaps
using git revert!)

I can find a fault, anywhere in a repositorys history, using git bisect

Quiz Answers

Config Settings
Jo will end up with [email protected] because she set this as her local setting, and it is
more specific. Check settings with git config --list - and if you want to remove
the local one, you can just remove the line from the config file that lives inside the
.git/ directory at the top level of your project.

Good Commit Practices


Every team will have their own process, but Id advocate for B) and D) as good
options.

Staging Changes
The first set of changes that Mia made will be included since they were present when
she added the file. The edits she made after adding wont be included in the commit.
CONTENTS 68

Branches
Branches are easy to make.
Git takes care of combining the changesets on the branches when we merge.
Merge commits are different to normal commits because they have two parents.
Branching gives a safe way of working on new features or bug fixes.

Staging Area Commands


Add all changes from a file to the staging area? git add [file]
Add some of the changes in your working directory to the staging area? git
add -p
Unstage some of the currently-staged changes? git reset -p
Unstage all changes from a single file, without losing the changes? git reset
[file]

Git Undo
you didnt commit yet? git reset
its your most recent commit (and you didnt push)? git reset --hard
the commit is not the most recent, but you didnt push yet? git revert or git
rebase -i (which we havent covered yet, but it would be correct)
you already pushed your changes? git revert

Rebasics
To fix a branch that branched from the wrong place? git rebase
To make a branch be based off the current tip of master and therefore include
all the changes in master? git rebase
To edit the history on a single branch? git rebase -i

Credits
Checkboxes designed by Arthur Shlain from the thenounproject.com

You might also like