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