Version control for fun and profit: the tool you didn't know you needed. From personal workflows to open collaboration

Note: this tutorial is based (mostely blantently copied), and therefore owes a lot, to the excellent materials offered in:

That notbook owed a lot to the following (including these notes for completeness):


There is also J.R. Johansson's tutorial on version control that is also written as a fully reproducible notebook and is also aimed at a scientific audience. It has a similar spirit to this one, and is part of his excellent series Lectures on Scientific Computing with Python that is entirely available as IPython Notebooks.


If we're running short on time, we'll do the simple guide

Why should I care about Version Control?

From What Is Version Control? Why Is It Important For Due Diligence?

Version control systems are essential for any form of distributed, collaborative development. Whether it is the history of a wiki page or large software development project, the ability to track each change as it was made, and to reverse changes when necessary can make all the difference between a well managed and controlled process and an uncontrolled ‘first come, first served’ system. It can also serve as a mechanism for due diligence for software projects.

From Wikipedia

“Revision control, also known as version control, source control or software configuration management (SCM), is the management of changes to documents, programs, and other information stored as computer files.

Reproducibility?

  • Tracking and recreating every step of your work
  • In the software world: it's called Version Control!

What do (good) version control tools give you?

  • Peace of mind (backups)
  • Freedom (exploratory branching)
  • Collaboration (synchronization)

Git is an enabling technology: Use version control for everything

  • Paper writing (never get paper_v5_john_jane_final_oct22_really_final.tex by email again!)
  • Everyday research
  • Teaching (never accept an emailed homework assignment again!)

Paper Writing With Git

Annotated history of each authors's worfklow (and backup!)

Why are we using git?

Because that's what folks were using when I came into programming... ¯\(ツ)

I don't know, really. Here are some reasons though:

  • It's decentralized (ie. everyone has a copy of the repository).
  • It has multiple, secure connection methods.
  • You can make commits offline.
  • It encourages a 'commit-often' mentality.
  • Easy to make branches and tags.
  • Any more?

I am not going to be upset if you use something else, but using a system that is decentralized is important. If you need to pick, check out wikipedia.

The plan for this tutorial

This tutorial is structured in the following way: we will begin with a brief overview of key concepts you need to understand in order for git to really make sense. We will then dive into hands-on work: after a brief interlude into necessary configuration we will discuss 5 "stages of git" with scenarios of increasing sophistication and complexity, introducing the necessary commands for each stage:

  1. Local, single-user, linear workflow
  2. Single local user, branching
  3. Using remotes as a single user
  4. Remotes for collaborating in a small team
  5. Full-contact github: distributed collaboration with large teams

In reality, this tutorial only covers stages 1-4, since for #5 there are many software develoment-oriented tutorials and documents of very high quality online. But most scientists start working alone with a few files or with a small team, so I feel it's important to build first the key concepts and practices based on problems scientists encounter in their everyday life and without the jargon of the software world. Once you've become familiar with 1-4, the excellent tutorials that exist about collaborating on github on open-source projects should make sense.

Very high level picture: an overview of key concepts

The commit: a snapshot of work at a point in time

Credit: ProGit book, by Scott Chacon, CC License.


In [5]:
ls


PBC2016_Version_Control_Git.pdf  VersionControl.ipynb.txt
VersionControl.ipynb             files/

A repository: a group of linked commits

And this is pretty much the essence of Git!

First things first: git must be configured before first use

The minimal amount of configuration for git to work without pestering you is to tell it who you are:


In [6]:
%%bash
git config --global user.name "Jeremy S. Perkins"
git config --global user.email "jeremyshane@gmail.com"

And how you will edit text files (it will often ask you to edit messages and other information, and thus wants to know how you like to edit your files):


In [7]:
%%bash
# Put here your preferred editor. If this is not set, git will honor
# the $EDITOR environment variable
git config --global core.editor /usr/bin/nano  # Yes, I still use nano

# On Windows Notepad will do in a pinch, I recommend Notepad++ as a free alternative
# On the mac, you can set nano or emacs as a basic option

# And while we're at it, we also turn on the use of color, which is very useful
git config --global color.ui "auto"

Set git to use the credential memory cache so we don't have to retype passwords too frequently. On OSX, you should run the following (note that this requires git version 1.7.10 or newer):


In [8]:
%%bash 
git config --global credential.helper osxkeychain
# Set the cache to timeout after 2 hours (setting is in seconds)
git config --global credential.helper 'cache --timeout=7200'

Github offers in its help pages instructions on how to configure the credentials helper for Linux and Windows.

Stage 1: Local, single-user, linear workflow

Simply type git to see a full list of all the 'core' commands. We'll now go through most of these via small practical exercises:


In [9]:
!git


usage: git [--version] [--help] [-C <path>] [-c name=value]
           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
           [-p | --paginate | --no-pager] [--no-replace-objects] [--bare]
           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
           <command> [<args>]

These are common Git commands used in various situations:

start a working area (see also: git help tutorial)
   clone      Clone a repository into a new directory
   init       Create an empty Git repository or reinitialize an existing one

work on the current change (see also: git help everyday)
   add        Add file contents to the index
   mv         Move or rename a file, a directory, or a symlink
   reset      Reset current HEAD to the specified state
   rm         Remove files from the working tree and from the index

examine the history and state (see also: git help revisions)
   bisect     Use binary search to find the commit that introduced a bug
   grep       Print lines matching a pattern
   log        Show commit logs
   show       Show various types of objects
   status     Show the working tree status

grow, mark and tweak your common history
   branch     List, create, or delete branches
   checkout   Switch branches or restore working tree files
   commit     Record changes to the repository
   diff       Show changes between commits, commit and working tree, etc
   merge      Join two or more development histories together
   rebase     Forward-port local commits to the updated upstream head
   tag        Create, list, delete or verify a tag object signed with GPG

collaborate (see also: git help workflows)
   fetch      Download objects and refs from another repository
   pull       Fetch from and integrate with another repository or a local branch
   push       Update remote refs along with associated objects

'git help -a' and 'git help -g' list available subcommands and some
concept guides. See 'git help <command>' or 'git help <concept>'
to read about a specific subcommand or concept.

git init: create an empty repository


In [10]:
%%bash
rm -rf test
git init test


Initialized empty Git repository in /Users/n01063296/Desktop/unfpyboot/Day_02/02_GitDevelopment/test/.git/

Note: all these cells below are meant to be run by you in a terminal where you change once to the test directory and continue working there.

Since we are putting all of them here in a single notebook for the purposes of the tutorial, they will all be prepended with the first two lines:

%%bash
cd test

that tell IPython to do that each time. But you should ignore those two lines and type the rest of each cell yourself in your terminal.

Let's look at what git did:


In [11]:
%%bash
cd test

ls

In [12]:
%%bash
cd test

ls -la


total 0
drwxr-xr-x   3 n01063296  UNFCSD\Domain Users  102 Jun 17 10:47 .
drwxr-xr-x   9 n01063296  UNFCSD\Domain Users  306 Jun 17 10:47 ..
drwxr-xr-x  10 n01063296  UNFCSD\Domain Users  340 Jun 17 10:47 .git

In [13]:
%%bash
cd test

ls -l .git


total 24
-rw-r--r--   1 n01063296  UNFCSD\Domain Users   23 Jun 17 10:47 HEAD
drwxr-xr-x   2 n01063296  UNFCSD\Domain Users   68 Jun 17 10:47 branches
-rw-r--r--   1 n01063296  UNFCSD\Domain Users  137 Jun 17 10:47 config
-rw-r--r--   1 n01063296  UNFCSD\Domain Users   73 Jun 17 10:47 description
drwxr-xr-x  11 n01063296  UNFCSD\Domain Users  374 Jun 17 10:47 hooks
drwxr-xr-x   3 n01063296  UNFCSD\Domain Users  102 Jun 17 10:47 info
drwxr-xr-x   4 n01063296  UNFCSD\Domain Users  136 Jun 17 10:47 objects
drwxr-xr-x   4 n01063296  UNFCSD\Domain Users  136 Jun 17 10:47 refs

Now let's edit our first file in the test directory with a text editor... I'm doing it programatically here for automation purposes, but you'd normally be editing by hand


In [14]:
%%bash
cd test

echo "My first bit of text" > file1.txt

git add: tell git about this new file


In [15]:
%%bash
cd test

git add file1.txt

We can now ask git about what happened with status:


In [16]:
%%bash
cd test

git status


On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

	new file:   file1.txt

git commit: permanently record our changes in git's database

For now, we are always going to call git commit either with the -a option or with specific filenames (git commit file1 file2...). This delays the discussion of an aspect of git called the index (often referred to also as the 'staging area') that we will cover later. Most everyday work in regular scientific practice doesn't require understanding the extra moving parts that the index involves, so on a first round we'll bypass it. Later on we will discuss how to use it to achieve more fine-grained control of what and how git records our actions.


In [17]:
%%bash
cd test

git commit -a -m"This is our first commit"


[master (root-commit) 0113667] This is our first commit
 1 file changed, 1 insertion(+)
 create mode 100644 file1.txt

In the commit above, we used the -m flag to specify a message at the command line. If we don't do that, git will open the editor we specified in our configuration above and require that we enter a message. By default, git refuses to record changes that don't have a message to go along with them (though you can obviously 'cheat' by using an empty or meaningless string: git only tries to facilitate best practices, it's not your nanny).

git log: what has been committed so far


In [18]:
%%bash
cd test

git log


commit 0113667d42a3fb2d24293b7713101b9903fba6d2
Author: Jeremy S. Perkins <jeremyshane@gmail.com>
Date:   Fri Jun 17 10:48:10 2016 -0400

    This is our first commit

git diff: what have I changed?

Let's do a little bit more work... Again, in practice you'll be editing the files by hand, here we do it via shell commands for the sake of automation (and therefore the reproducibility of this tutorial!)


In [19]:
%%bash
cd test

echo "And now some more text..." >> file1.txt

And now we can ask git what is different:


In [20]:
%%bash
cd test

git diff


diff --git a/file1.txt b/file1.txt
index ce645c7..4baa979 100644
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1,2 @@
 My first bit of text
+And now some more text...

The cycle of git virtue: work, commit, work, commit, ...


In [21]:
%%bash
cd test

git commit -a -m"I have made great progress on this critical matter."


[master c84a808] I have made great progress on this critical matter.
 1 file changed, 1 insertion(+)

git log revisited

First, let's see what the log shows us now:


In [22]:
%%bash
cd test

git log


commit c84a808473879bd41d2da5e6a471e14e29c6f1a0
Author: Jeremy S. Perkins <jeremyshane@gmail.com>
Date:   Fri Jun 17 10:48:19 2016 -0400

    I have made great progress on this critical matter.

commit 0113667d42a3fb2d24293b7713101b9903fba6d2
Author: Jeremy S. Perkins <jeremyshane@gmail.com>
Date:   Fri Jun 17 10:48:10 2016 -0400

    This is our first commit

Sometimes it's handy to see a very summarized version of the log:


In [23]:
%%bash
cd test

git log --oneline --topo-order --graph


* c84a808 I have made great progress on this critical matter.
* 0113667 This is our first commit

Git supports aliases: new names given to command combinations. Let's make this handy shortlog an alias, so we only have to type git slog and see this compact log:


In [24]:
%%bash
cd test

# We create our alias (this saves it in git's permanent configuration file):
git config --global alias.slog "log --oneline --topo-order --graph"

# And now we can use it
git slog


* c84a808 I have made great progress on this critical matter.
* 0113667 This is our first commit

git mv and rm: moving and removing files

While git add is used to add fils to the list git tracks, we must also tell it if we want their names to change or for it to stop tracking them. In familiar Unix fashion, the mv and rm git commands do precisely this:


In [25]:
%%bash
cd test

git mv file1.txt file-newname.txt
git status


On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	renamed:    file1.txt -> file-newname.txt

Note that these changes must be committed too, to become permanent! In git's world, until something hasn't been committed, it isn't permanently recorded anywhere.


In [26]:
%%bash
cd test

git commit -a -m"I like this new name better"
echo "Let's look at the log again:"
git slog


[master 652c8f4] I like this new name better
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename file1.txt => file-newname.txt (100%)
Let's look at the log again:
* 652c8f4 I like this new name better
* c84a808 I have made great progress on this critical matter.
* 0113667 This is our first commit

And git rm works in a similar fashion.

Local user, branching

What is a branch? Simply a label for the 'current' commit in a sequence of ongoing commits:

There can be multiple branches alive at any point in time; the working directory is the state of a special pointer called HEAD. In this example there are two branches, master and testing, and testing is the currently active branch since it's what HEAD points to:

Once new commits are made on a branch, HEAD and the branch label move with the new commits:

This allows the history of both branches to diverge:

But based on this graph structure, git can compute the necessary information to merge the divergent branches back and continue with a unified line of development:

Let's now illustrate all of this with a concrete example. Let's get our bearings first:


In [27]:
%%bash
cd test

git status
ls


On branch master
nothing to commit, working directory clean
file-newname.txt

We are now going to try two different routes of development: on the master branch we will add one file and on the experiment branch, which we will create, we will add a different one. We will then merge the experimental branch into master.


In [28]:
%%bash
cd test

git branch experiment
git checkout experiment


Switched to branch 'experiment'

In [29]:
%%bash
cd test

echo "Some crazy idea" > experiment.txt
git add experiment.txt
git commit -a -m"Trying something new"
git slog


[experiment 3c87788] Trying something new
 1 file changed, 1 insertion(+)
 create mode 100644 experiment.txt
* 3c87788 Trying something new
* 652c8f4 I like this new name better
* c84a808 I have made great progress on this critical matter.
* 0113667 This is our first commit

In [30]:
%%bash
cd test

git checkout master
git slog


* 652c8f4 I like this new name better
* c84a808 I have made great progress on this critical matter.
* 0113667 This is our first commit
Switched to branch 'master'

In [31]:
%%bash
cd test

git status


On branch master
nothing to commit, working directory clean

In [32]:
%%bash
cd test

echo "fixed this little bug" >> file-newname.txt
git commit -a -m"The mainline keeps moving"
git slog


[master 3821364] The mainline keeps moving
 1 file changed, 1 insertion(+)
* 3821364 The mainline keeps moving
* 652c8f4 I like this new name better
* c84a808 I have made great progress on this critical matter.
* 0113667 This is our first commit

In [33]:
%%bash
cd test

ls


file-newname.txt

In [34]:
%%bash
cd test

git merge experiment
git slog


Merge made by the 'recursive' strategy.
 experiment.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 experiment.txt
*   5b64343 Merge branch 'experiment'
|\  
| * 3c87788 Trying something new
* | 3821364 The mainline keeps moving
|/  
* 652c8f4 I like this new name better
* c84a808 I have made great progress on this critical matter.
* 0113667 This is our first commit

Using remotes as a single user

We are now going to introduce the concept of a remote repository: a pointer to another copy of the repository that lives on a different location. This can be simply a different path on the filesystem or a server on the internet.

For this discussion, we'll be using remotes hosted on the GitHub.com service, but you can equally use other services like BitBucket or Gitorious as well as host your own.


In [35]:
%%bash
cd test

ls
echo "Let's see if we have any remote repositories here:"
git remote -v


experiment.txt
file-newname.txt
Let's see if we have any remote repositories here:

Since the above cell didn't produce any output after the git remote -v call, it means we have no remote repositories configured. We will now proceed to do so. Once logged into GitHub, go to the new repository page and make a repository called test. Do not check the box that says Initialize this repository with a README, since we already have an existing repository here. That option is useful when you're starting first at Github and don't have a repo made already on a local computer.

We can now follow the instructions from the next page:


In [36]:
%%bash
cd test

git remote add origin https://github.com/kialio/test.git

In [37]:
%%bash
cd test

git push -u origin master


remote: Permission to kialio/test.git denied to saudijack.
fatal: unable to access 'https://github.com/kialio/test.git/': The requested URL returned error: 403

Let's see the remote situation again:


In [45]:
%%bash
cd test

git remote -v


origin	https://github.com/kialio/test.git (fetch)
origin	https://github.com/kialio/test.git (push)

We can now see this repository publicly on github.

Let's see how this can be useful for backup and syncing work between two different computers. I'll simulate a 2nd computer by working in a different directory...


In [47]:
%%bash

# Here I clone my 'test' repo but with a different name, test2, to simulate a 2nd computer
git clone https://github.com/kialio/test.git test2
cd test2
pwd
git remote -v


/Users/jsperki1/src/python-bootcamp/Lectures/10_GitDevelopment/test2
origin	https://github.com/kialio/test.git (fetch)
origin	https://github.com/kialio/test.git (push)
fatal: destination path 'test2' already exists and is not an empty directory.

Let's now make some changes in one 'computer' and synchronize them on the second.


In [48]:
%%bash
cd test2  # working on computer #2

echo "More new content on my experiment" >> experiment.txt
git commit -a -m"More work, on machine #2"


[master ad31fea] More work, on machine #2
 1 file changed, 1 insertion(+)

Now we put this new work up on the github server so it's available from the internet


In [49]:
%%bash
cd test2

git push


To https://github.com/kialio/test.git
   4e22d04..ad31fea  master -> master

Now let's fetch that work from machine #1:


In [50]:
%%bash
cd test

git pull


Updating 4e22d04..ad31fea
Fast-forward
 experiment.txt | 1 +
 1 file changed, 1 insertion(+)
From https://github.com/kialio/test
   4e22d04..ad31fea  master     -> origin/master

An important aside: conflict management

While git is very good at merging, if two different branches modify the same file in the same location, it simply can't decide which change should prevail. At that point, human intervention is necessary to make the decision. Git will help you by marking the location in the file that has a problem, but it's up to you to resolve the conflict. Let's see how that works by intentionally creating a conflict.

We start by creating a branch and making a change to our experiment file:


In [51]:
%%bash
cd test

git branch trouble
git checkout trouble
echo "This is going to be a problem..." >> experiment.txt
git commit -a -m"Changes in the trouble branch"


[trouble a3733d3] Changes in the trouble branch
 1 file changed, 1 insertion(+)
Switched to branch 'trouble'

And now we go back to the master branch, where we change the same file:


In [52]:
%%bash
cd test

git checkout master
echo "More work on the master branch..." >> experiment.txt
git commit -a -m"Mainline work"


Your branch is up-to-date with 'origin/master'.
[master 2e0207f] Mainline work
 1 file changed, 1 insertion(+)
Switched to branch 'master'

So now let's see what happens if we try to merge the trouble branch into master:


In [53]:
%%bash
cd test

git merge trouble


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

Let's see what git has put into our file:


In [54]:
%%bash
cd test

cat experiment.txt


Some crazy idea
More new content on my experiment
<<<<<<< HEAD
More work on the master branch...
=======
This is going to be a problem...
>>>>>>> trouble

At this point, we go into the file with a text editor, decide which changes to keep, and make a new commit that records our decision. I've now made the edits, in this case I decided that both pieces of text were useful, but integrated them with some changes:


In [55]:
%%bash
cd test

cat experiment.txt


Some crazy idea
More new content on my experiment
More work on the master branch...
This is going to be a problem...

Let's then make our new commit:


In [56]:
%%bash
cd test

git commit -a -m"Completed merge of trouble, fixing conflicts along the way"
git slog


On branch master
Your branch is ahead of 'origin/master' by 3 commits.
  (use "git push" to publish your local commits)

Untracked files:
	experiment.txt~
	file-newname.txt~

nothing added to commit but untracked files present
*   3c1745f Merge branch 'trouble'
|\  
| * a3733d3 Changes in the trouble branch
* | 2e0207f Mainline work
|/  
* ad31fea More work, on machine #2
*   4e22d04 Merge branch 'experiment'
|\  
| * 10c28e9 The mainline keeps moving
| * 7ef8b27 Trying something new
* | 8378542 The mainline keeps moving
|/  
* 0320e82 Deleted file.
* da076dc Adding this new file like you told me.
* c10f036 I like this new name better
* dceceed I have made great progress on this critical matter.
* b4a8d18 This is our first commit

Note: While it's a good idea to understand the basics of fixing merge conflicts by hand, in some cases you may find the use of an automated tool useful. Git supports multiple merge tools: a merge tool is a piece of software that conforms to a basic interface and knows how to merge two files into a new one. Since these are typically graphical tools, there are various to choose from for the different operating systems, and as long as they obey a basic command structure, git can work with any of them.

Collaborating on github with a small team - Breakout

Single remote with shared access: we are going to set up a shared collaboration with one partner (the person sitting next to you). This will show the basic workflow of collaborating on a project with a small team where everyone has write privileges to the same repository.

Note for SVN users: this is similar to the classic SVN workflow, with the distinction that commit and push are separate steps. SVN, having no local repository, commits directly to the shared central resource, so to a first approximation you can think of svn commit as being synonymous with git commit; git push.

We will have two people, let's call them Alice and Bob, sharing a repository. Alice will be the owner of the repo and she will give Bob write privileges.

We begin with a simple synchronization example, much like we just did above, but now between two people instead of one person. Otherwise it's the same:

  1. Both Alice and Bob need to register on github.
  2. Alice creates a reposity on github.
  3. Alice adds something to that repository (whatever you want).
  4. Bob clones Alice's repository.
  5. Bob makes changes to a file and commits them locally.
  6. Bob pushes his changes to github.
  7. Alice pulls Bob's changes into her own repository.

Next, we will have both parties make non-conflicting changes each, and commit them locally. Then both try to push their changes:

  1. Alice adds a new file, alice.txt to the repo and commits.
  2. Bob adds bob.txt and commits.
  3. Alice pushes to github.
  4. Bob tries to push to github. What happens here?

The problem is that Bob's changes create a commit that conflicts with Alice's, so git refuses to apply them. It forces Bob to first do the merge on his machine, so that if there is a conflict in the merge, Bob deals with the conflict manually (git could try to do the merge on the server, but in that case if there's a conflict, the server repo would be left in a conflicted state without a human to fix things up). The solution is for Bob to first pull the changes (pull in git is really fetch+merge), and then push again.

Git resources (Some notes from F. Perez)

Introductory materials

There are lots of good tutorials and introductions for Git, which you can easily find yourself; this is just a short list of things I've found useful. For a beginner, I would recommend the following 'core' reading list, and below I mention a few extra resources:

  1. The smallest, and in the style of this tuorial: git - the simple guide contains 'just the basics'. Very quick read.

  2. The concise Git Reference: compact but with all the key ideas. If you only read one document, make it this one.

  3. In my own experience, the most useful resource was Understanding Git Conceptually. Git has a reputation for being hard to use, but I have found that with a clear view of what is actually a very simple internal design, its behavior is remarkably consistent, simple and comprehensible.

  4. For more detail, see the start of the excellent Pro Git online book, or similarly the early parts of the Git community book. Pro Git's chapters are very short and well illustrated; the community book tends to have more detail and has nice screencasts at the end of some sections.

If you are really impatient and just want a quick start, this visual git tutorial may be sufficient. It is nicely illustrated with diagrams that show what happens on the filesystem.

For windows users, an Illustrated Guide to Git on Windows is useful in that it contains also some information about handling SSH (necessary to interface with git hosted on remote servers when collaborating) as well as screenshots of the Windows interface.

Cheat sheets : Two different cheat sheets in PDF format that can be printed for frequent reference.

Beyond the basics

At some point, it will pay off to understand how git itself is built. These two documents, written in a similar spirit, are probably the most useful descriptions of the Git architecture short of diving into the actual implementation. They walk you through how you would go about building a version control system with a little story. By the end you realize that Git's model is almost an inevitable outcome of the proposed constraints:

Git ready : A great website of posts on specific git-related topics, organized by difficulty.

QGit: an excellent Git GUI : Git ships by default with gitk and git-gui, a pair of Tk graphical clients to browse a repo and to operate in it. I personally have found qgit to be nicer and easier to use. It is available on modern linux distros, and since it is based on Qt, it should run on OSX and Windows.

Git Magic : Another book-size guide that has useful snippets.

The learning center at Github : Guides on a number of topics, some specific to github hosting but much of it of general value.

A port of the Hg book's beginning : The Mercurial book has a reputation for clarity, so Carl Worth decided to port its introductory chapter to Git. It's a nicely written intro, which is possible in good measure because of how similar the underlying models of Hg and Git ultimately are.

Intermediate tips : A set of tips that contains some very valuable nuggets, once you're past the basics.