Dreaming in Git

Understanding Git well enough to anticipate its every move.

Marcel M. Cary

Dreaming?

Learning a foreign language:

  • At first you decide what to say in your native language and translate it
  • Eventually, your thoughts begin to surface directly in the new language, avoiding the need for translation
  • Someday, it becomes second-nature; you notice you are dreaming in the new language

Goals

  • Develop a visual model for thinking about Git
  • Learn how some commands map to this model
  • See tools for discovering how additional commands manipulate that model
  • Begin to understand why Git is fast

About Me

  • At O'Reilly Media (this campus)
  • On a team of 5 developers, collaborating via Git every day
  • Using Git as primary version control for 7 years
  • Have trained people on using Git
  • Contributor to git.git, the Git codebase

Level

I will assume you:

  • Already use Git
  • Have a basic familiarity with branching and merging

This is not an intro-level presentation, but you can still learn something if you have not used Git before.

Git Commit IDs

  • Git uses a sha1 hash to name a commit
  • Like an id identifies a row in a database
  • Like pointer points at an object in C/C++

Example Commit IDs

~/Projects/git|master$ git log --format=fuller 2c3c395e
commit 2c3c395e84409c278bd7b050877c36d04b952056
Author:     Marcel M. Cary <marcel@oak.homeunix.org>
AuthorDate: Fri Feb 6 19:24:28 2009 -0800
Commit:     Junio C Hamano <gitster@pobox.com>
CommitDate: Sat Feb 7 00:45:29 2009 -0800


    git-sh-setup: Use "cd" option, not /bin/pwd, for symlinked work tree

    In cd_to_toplevel, instead of 'cd $(unset PWD; /bin/pwd)/$path'
    use 'cd -P $path'.  The "-P" option yields a desirable similarity to
    ...

More Commit IDs

~/Projects/git|master$ git log --oneline 7d233dea
7d233de gitweb: Hyperlink multiple git hashes on the same commit message line
024aa7d system_path(): simplify using strip_path_suffix(), and add suffix "git"
4fcc86b Introduce the function strip_path_suffix()
88e3880 filter-branch -d: Export GIT_DIR earlier
51b2ead disallow providing multiple upstream branches to rebase, pull --rebase
d61027b Skip timestamp differences for diff --no-index
b94ead7 git-svn: fix parsing of timestamp obtained from svn
df5d10a gitweb: Fix warnings with override permitted but no repo override
bed5122 Documentation/git-push: --all, --mirror, --tags can not be combined
...

Commit ID, AKA:

  • Hash
  • SHA1
  • Commit ID

Immutability

Git Commit IDs are a digest (checksum) of the commit attributes, which cryptographically guarantees that the commit can't be altered.

Committing

~$ mkdir example-repo
~$ cd example-repo
~/example-repo$ git init
~/example-repo|master$ touch README.md
~/example-repo|master$ git add README.md
~/example-repo|master$ git commit -m 'Initial commit'
[master c714722] Initial commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 README.md

Another Commit

~/example-repo|master$ echo hi > README.md
~/example-repo|master$ git commit -am 'Say hi'
[master 016e54b] Say hi
 1 file changed, 1 insertion(+)
~/example-repo|master$ git log -p
commit 016e54bea7d5803346ed1b57b963fefe3ea22d9d
Author: Marcel M. Cary <marcel@oreilly.com>
Date:   Tue Feb 9 20:16:48 2016 -0800

    Say hi

diff --git a/README.md b/README.md
index e69de29..3b18e51 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1 @@
+hi

commit c714722b9a633a523a982ad5b88374a37683ce1d
Author: Marcel M. Cary <marcel@oreilly.com>
Date:   Tue Feb 9 20:05:58 2016 -0800

    Initial commit
...

A Better Commit

~/example-repo|master$ echo 'Hello World!' > README.md
~/example-repo|master$ git commit --amend -am 'Say Hello!'
[master d2b3e97] Say Hello!
 Date: Tue Feb 9 20:16:48 2016 -0800
 1 file changed, 1 insertion(+)
~/example-repo|master$ git log -p
commit d2b3e97df640b0d2a863630af42d35ecb13573cd
Author: Marcel M. Cary <marcel@oreilly.com>
Date:   Tue Feb 9 20:16:48 2016 -0800

    Say Hello!

diff --git a/README.md b/README.md
index e69de29..980a0d5 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1 @@
+Hello World!

commit c714722b9a633a523a982ad5b88374a37683ce1d
Author: Marcel M. Cary <marcel@oreilly.com>
Date:   Tue Feb 9 20:05:58 2016 -0800

    Initial commit
...

Which Just Happened?

  1. We created a new commit and destroyed the old one
  2. We just changed our commit to sound better
  3. We just created a new commit

?

How can you tell?

  • Can I still see the old commit?
  • How does Git know which commit I'm on?
  • What exactly does Git do when I commit??

Model for Commits

Let's create a visual model for commits.

Initial commit

git log --format=fuller c714722b

Commit ID:
c714722b9a633a523a982ad5b88374a37683ce1d
Author:
Marcel M. Cary <marcel@oreilly.com>
AuthorDate:
Tue Feb 9 20:05:58 2016 -0800
Committer:
Marcel M. Cary <marcel@oreilly.com>
CommitDate:
Tue Feb 9 20:05:58 2016 -0800
Message:
Initial commit

Second Commit

Commit ID:
016e54be
Message:
Say hi
Parent:
c714722b
→
Commit ID:
c714722b
Message:
Initial commit

Revised Commit

016e54be
Say hi
↓
d2b3e97d
Say Hello!
↓
c714722b
Initial commit

Reflog

~/example-repo|master$ git reflog master
d2b3e97 master@{0}: commit (amend): Say Hello!
016e54b master@{1}: commit: Say hi
c714722 master@{2}: commit (initial): Initial commit

Superseded Commit

~/example-repo|master$ git show 016e54b
commit 016e54bea7d5803346ed1b57b963fefe3ea22d9d
Author: Marcel M. Cary <marcel@oreilly.com>
Date:   Tue Feb 9 20:16:48 2016 -0800

    Say hi

diff --git a/README.md b/README.md
index e69de29..3b18e51 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1 @@
+hi

How does Git know which commit is "current"?

Branches

master
↓
016e54be
Say hi
↓
d2b3e97d
Say Hello!
↓
c714722b
Initial commit

Remember the Reflog?

~/example-repo|master$ git reflog master
d2b3e97 master@{0}: commit (amend): Say Hello!
016e54b master@{1}: commit: Say hi
c714722 master@{2}: commit (initial): Initial commit

What's the difference between the reflog and the log?

  • A branch's reflog is the history of changes to a branch, like an undo history
  • A branch's log is the list of commits reachable from that branch
  • If all you ever do is add commits to that branch, they show the same entries

Branch Log

master
↓
016e54be
Say hi
↓
d2b3e97d
Say Hello!
↓
c714722b
Initial commit

Warning

When collaborating, amending risks introducing redundant versions of a commit into your commit log if your collaborators don't also abandon the superseded commit.

(See "RECOVERING FROM UPSTREAM REBASE" in the git-rebase manual.)

Branching

~/example-repo|master$ git checkout -b fill-in-readme
Switched to a new branch 'fill-in-readme'
~/Projects/example-repo|fill-in-readme$ git branch
* fill-in-readme
  master

Add a Pointer

master
↓
fill-in-readme
↓
d2b3e97d
Say Hello!
↓
...

You are Here

HEAD
↓
master
↓
fill-in-readme
↓
d2b3e97d
Say Hello!

Branching is Fast

No matter how many files or commits in your repo,
branching just creates a pointer and updates HEAD.

Symbolic Refs

HEAD is a symbolic ref — a pointer to a branch

~/example-repo|fill-in-readme$ git symbolic-ref HEAD
refs/heads/fill-in-readme

Looks like a File!

refs/heads/fill-in-readme

~/example-repo$ find . | grep refs/heads/fill-in-readme
./.git/logs/refs/heads/fill-in-readme
./.git/refs/heads/fill-in-readme
~/example-repo$ cat .git/refs/heads/fill-in-readme d2b3e97df640b0d2a863630af42d35ecb13573cd

Symbolic ref / Symlink

A Symoblic Ref used to be a Symolic Link, until Git supported Windows.

~/example-repo$ grep -r . refs/heads/fill-in-readme
./.git/HEAD:ref: refs/heads/fill-in-readme

Do some work...

~/example-repo|fill-in-readme$ cat > README.md <<EOF
> Example Repo
> ============
>
> Hello World!
> EOF
~/example-repo|fill-in-readme$ git commit -am 'Fill
 in the README'
[fill-in-readme 30842db] Fill in the README
 1 file changed, 3 insertions(+)

Branch Follows

master
↓
HEAD
↓
fill-in-readme
↓
30842dbd
Fill in the README
↓
d2b3e97d
Say Hello!

Merging

Makes master have all changes from both branches

~/example-repo|fill-in-readme$ git checkout master
Switched to branch 'master'
~/example-repo|master$ git merge fill-in-readme Updating d2b3e97..30842db Fast-forward README.md | 3 +++ 1 file changed, 3 insertions(+)

Checkout Master

master
↓
←
HEAD
↓
fill-in-readme
30842dbd
Fill in the README
↓
d2b3e97d
Say Hello!

Fast Forward Merge

←
fill-in-readme
HEAD
↓
master
↓
30842dbd
Fill in the README
↓
d2b3e97d
Say Hello!

Non-Fast Forward

Suppose instead that master already had additional commits.

Divergent Master

HEAD
→
↓
master
←
fill-in-readme
2c89b052
Add src
↓
30842dbd
Fill in the README
↓
d2b3e97d
Say Hello!

Merging

2545de7d
Merge branch 'fill-in-readme'
HEAD
→
↓
master
←
fill-in-readme
↓
2c89b052
Add src
↓
↓
30842dbd
Fill in the README
↓
d2b3e97d
Say Hello!

ASCII Art

~/example-repo|master$ git log --oneline \
                         --decorate --all --graph
*   2545de7 (HEAD, master) Merge branch 'fill-in-readme'
|\  
| * 30842db (fill-in-readme) Fill in the README
* | 2c89b05 Add src
|/  
* d2b3e97 Say Hello!
* c714722 Initial commit
Gnarly commit graph from git command

Commit Exclusion

$ git log --oneline master
2545de7 Merge branch 'fill-in-readme'
2c89b05 Add src
30842db Fill in the README
d2b3e97 Say Hello!
c714722 Initial commit
$ git log --oneline ^fill-in-readme master
2545de7 Merge branch 'fill-in-readme'
2c89b05 Add src
$ git log --oneline fill-in-readme..master
2545de7 Merge branch 'fill-in-readme'
2c89b05 Add src

Merge Conflicts

Conflicting Edits

HEAD
→
↓
master
←
fill-in-readme
2c89b052
Add contributors
to README
↓
30842dbd
Fill in the README
 
↓
d2b3e97d
Say Hello!

README.md
on master

+Contributors
+------------
+
 Hello World!

README.md
on fill-in-readme

+Example Repo
+============
+
 Hello World!

Automatic Merge Fail

~/example-repo|master$ git merge fill-in-readme
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Recorded preimage for 'README.md'
Automatic merge failed; fix conflicts and then
    commit the result.

Git Status

~/example-repo|master*+|MERGING$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add ..." to mark resolution)

  both modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

Conflict Markers

README.md

<<<<<<< HEAD
Contributors
------------
=======
Example Repo
============
>>>>>>> fill-in-readme
        
Hello World!

Resolving Conflicts

README.md

Example Repo ============
<<<<<<< HEAD
Contributors ------------
======= Example Repo ============ >>>>>>> fill-in-readme
Hello World!
~/example-repo|master*+|MERGING$ git add README.md
~/example-repo|master+|MERGING$ git commit

Merge Commit Message

Merge branch 'fill-in-readme'

Conflicts:
    README.md
Keep Contributors from master, and keep top-level heading from fill-in-readme.

Rebasing

Rebase my pull request??

Scenario

~/example-repo|fill-in-readme$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
~/example-repo|master=$ git pull --ff-only
From http://github.com/mcary/example-repo
   d2b3e97..2c89b05  master     -> origin/master
Updating d2b3e97..2c89b05
Fast-forward
 src/example.c | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 src/example.c
~/example-repo|master=$ git log --oneline d2b3e97..2c89b05
2c89b05 Add src
~/example-repo|master=$ git checkout -
Switched to branch 'fill-in-readme'

Rebase Command

~/example-repo|fill-in-readme$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: Fill in the README

Rebase

 
→
master
HEAD
←
↓
fill-in-readme
2c89b052
Add src
↓
d892049
Fill in the README
↓
30842dbd
Fill in the README
↓
d2b3e97d
Say Hello!

Recap

  • Immutability
  • Amending
  • Committing
  • Branches
  • HEAD
  • Merging: fast-forward or non
  • Resoling merge conflicts
  • Commit exclusion and ranges
  • Rebasing

Dreaming in Git

Understanding Git well enough to anticipate its every move.

Marcel M. Cary


Slides: www.oak.homeunix.org/~marcel/nblug-git