Commit often, perfect later, publish once

Subversion logo
Centralized VCS
Distributed VCS

Why git?

  • Everything is local
  • Fast, works offline
  • Based on simple rules
  • Branching and merging is rather easy
  • Very flexible regarding workflows
  • Corrections are possible
  • Good documentation, lots of articles and Q&A online
  • Available on all platforms

Hello, git

A completely ignorant, childish person with no manners.
$ /dont-panic/
$ git <verb>
$ git help <verb>
$ git <verb> --help
$ git init
Initialized empty Git repository in /scratch/.git/

$ echo hello, world > hello.txt

$ git add hello.txt

$ git commit --message 'my first commit!'
[master (root-commit) deadbee] my first commit!
 1 file changed, 1 insertion(+)
 create mode 100644 hello.txt
$ git show
commit deadbeef16165bb95a541321a7acf9cef9731c1d
Author: Alexander Groß <agross@therightstuff.de>
Date:   Sat Feb 6 12:41:24 2010 +0100

    my first commit!

diff --git a/hello.txt b/hello.txt
new file mode 100644
index 0000000..4b5fa63
--- /dev/null
+++ b/hello.txt
@@ -0,0 +1 @@
+hello, world

Content Lifecycle

Staging Area = Power

Take-aways

  • Partial operations
    $ git add --patch
    $ git reset HEADrestore --staged --patch
    $ git checkoutrestore --patch
  • Temporarily ignore changes to tracked files
    $ git update-index --[no-]skip-worktree
  • List ignored changes
    $ git ls-files -v | grep '^h'

Branching

Simple history

Here are some better alternative names for the "master" branch in your source code repository:

- bleeding-edge
- nevergreen
- works-on-my-machine
- happy-merging-XD
- there-be-real-users
- features-come-here-to-die
- (L°O°)L_|_|_

— Markus Tacker 🇳🇴 (@coderbyheart) March 6, 2018

The HEAD pointer

Creating a branch

$ git branch topic [<where>]

(<where> defaults to HEAD)

Current branch = where HEAD is

$ git checkoutswitch topic

A Shortcut

$ git checkout -bswitch -c <name> [<where>]

==

$ git branch <name> [<where>] && git checkoutswitch <name>

Advancing the topic branch

$ git commit -am "work on topic"

Rule #1

The only branch that can change is the current branch.

Back to master

$ git checkoutswitch master

Rule #2

When HEAD's position changes your working copy will be updated.

Uncommitted changes will be attempted to be preserved.

Advancing the master branch

$ git commit -am "work on master"

Detached HEAD

A detached head of a sculpture

Detached HEAD

$ git checkoutswitch --detach deadbeef
Note: checking out 'deadbeef'.

You are in 'detached HEAD' state. You can look
around, make experimental changes and commit them…HEAD is now at deadbeef

You're on no branch when you checkout a SHA, tag or remote branch.

Restoring unreachable commits

$ git reflog
6bbd21d HEAD@{0}: commit (amend): JUG WIP
e7d4298 HEAD@{1}: commit: JUG WIP
71ab831 HEAD@{2}: checkout: moving from gh-pages to jug
71ab831 HEAD@{3}: commit: Support more than one fade
76cd1b0 HEAD@{4}: commit: Tabs -> spaces

$ git fsck --unreachable | grep commit
unreachable commit 60c5758f209c63e5cc2d00f4c8e3ab8fc7037609
unreachable commit 6bc882d6491597250a2baa880e19f0759e1f585b
unreachable commit 74cfdec6a9ba5b3e70c7bd1128fc3347440539b8
unreachable commit b76bb1f752afaaedc1b21d35c75c3acdce3d46e5

Making corrections

Modifying the last commit

$ git commit --amend -m 'C was bad'

Undoing recent commits

$ git reset --hard B

# Like a pro:
$ git reset --hard HEAD~

Applying a negated commit

$ git revert [--no-commit] B

Rewriting the whole graph 💣

  • Extracting libraries from projects
    $ git filter-branch --subdirectory-filter src/lib -- --all
  • Ensuring no internal files are published when a project is open-sourced
    $ git filter-branch --index-filter 'git rm secret.txt' HEAD
  • Converting Subversion repositories with svn:externals (there be 🐉)
    $ git svn-clone-externals svn://…

Rewriting parts of the graph

🔜

I'll tell my story after I heard yours

$ git checkoutswitch topic
$ git rebase master

Commits C, D and E are copied on top of master as C', D' and E'.

Complex Rebase

How to get rid of C when rebasing client on master?

Complex Rebase

$ git rebase --onto master server client

A Second Variant

$ git rebase --onto B server client

Preparing for code review

$ git rebase --interactive A

Integration


Your options:

Diverged History

Recursive Merge

$ git checkoutswitch master
$ git merge topic

Recursive merge (~ ORT merge since git 2.34): Integrates two diverged branches.

Undoing The Merge

$ git reset --hard F

# Like a pro (covers recursive and ff merges):
$ git reset --hard @@{1}

Linear History

Fast-Forward Merge

$ git checkoutswitch master
$ git merge topic

Fast-forward merge: The master pointer can be moved from C to E without losing commits reachable from master.

Controlling git merge behavior

$ git merge --ff-only

Enforces a fast-forward merge, aborts if history is diverged.

$ git merge --no-ff

Enforces a recursive merge, even if a fast-forward merge would be possible.

Controlling git merge recursive strategy

$ git merge --strategy-option ours

Prefer our changes when encountering conflicts.

$ git merge -X theirs

Prefer their changes when encountering conflicts.

Squash merge 🍋

$ git merge --squash [--commit] topic

Merge HEAD's and topic's snapshot, create a new commit, but only assign HEAD as its parent.

Octopus merge 🐙

$ git merge perf css report

Integrate any number of non-conflicting branches with a single merge commit.

Cherry-pick 🍒

$ git cherry-pick [--no-commit] D

Apply a commit from somewhere else.

More options

  • Make obsolete's commits reachable, but keep tree as-is
    (i.e. archive obsolete)
    $ git merge --strategy=ours obsolete
  • Craft merge commit with release's tree
    (i.e. no file-based merging)
    $ git commit-tree release^{tree}
                      -m "Merge branch 'release'"
                      -p HEAD
                      -p release

Branching Strategies

Branches Provide Isolation

  • git merge if you want to keep information about integrated branches
  • git rebase if you do not care

OneFlow – Features

OneFlow – Releases

OneFlow – Hotfixes

git-flow

HubFlow

Gerrit

Workflows

$ git config --global alias.pfusch 'push -f'

Centralized

Centralized workflow

Integration Manager

Integration manager

Benevolent Dictator

Benevolent dictator

Ad-hoc

Server (9418/tcp)

$ git daemon --base-path=. --export-all --verbose
[4242] Ready to rumble

Client

$ git clone git://host/relative/path/to/repo/.git foo
Cloning into 'foo'...
$ git remote add hans git://host/relative/path/to/repo/.git
$ git fetch hans
remote: Counting objects: 42, done.
…
From git://host/relative/path/to/repo/.git
   e3205a5..0282413  master        -> hans/master

Git Internals

Commit = Trees + Blobs

A commit object has references to tree and blob objects

Commit = Trees + Blobs

A commit object has references to tree and blob objects

Commit = Trees + Blobs

A commit object has references to tree and blob objects

History = Linked Commits

The history is a sequence of commits objects

Pointers Everywhere

Pointers reference individual commits

Pointers Everywhere

Full data model (first commit)

Pointers Everywhere

Full data model (second commit)

Pointers Everywhere

Full data model (third commit)

Delta Storage

Delta storage

Snapshot Storage

Snapshot storage

Advanced Topics

Throw-Away Integration Branches

  • Use temporary pu branches
  • Do not base any work off of pu
  • Enable “Reuse recorded resolution”
    git config --global rerere.enabled true
    git config --global rerere.autoUpdate true
# All branches were tested in isolation
Three features are done and work in isolation
$ git checkout -bswitch -c pu master
Create a temporary branch for proposed updates
$ for b in perf css report; do git merge $b; done
Merge topics and resolve conflicts, test all feature together
$ git reset --hard master
Back to square one
$ for b in css report; do git merge $b; done
The second integration attempt is successful
$ git checkoutswitch master; git merge pu; git branch -d pu
Merge pu into master

Bisecting

  • Attempts to find bug-introducing commits by testing a minimum set of revisions
  • Enters detached HEAD state while searching
  • After the bad commit has been found, undo it
    git revert [--no-commit] <bad-commit>
  • Automate testing
    git bisect run <some-script>
  • some-script can do things like cherry-picking reproductions, demo available

v1.0 works but v1.1 contains a regression

$ git stash push -m "whatever you're doing"
v1.0 works but commits between v1.0 and v1.1 broke something

Start looking for the bug

$ git bisect start v1.1 v1.0
Start searching the breaking commit

Test commit D

$ make test # => error
Test your app at commit D

Give feedback about D

$ git bisect bad
Give git feedback about the test result

Test commit B

$ make test # => success
Test your app at commit B

Give feedback about B

$ git bisect good
Give git feedback about the test result

Test commit C

$ make test # => success
Test your app at commit C

Give feedback about C

$ git bisect good
Give git feedback about the test result

Culprit found!

# D is the first bad revision
git reports first bad revision

Exit search

$ git bisect reset
After reset the graph looks like before

bisect and cherry-pick

$ git checkout -bswitch -c repro v1.1 && git commit -m 'repro'
Create bug reproduction commit

Start looking for the bug

$ git bisect start v1.1 v1.0
Create bug reproduction commit

Apply repro commit

$ git cherry-pick repro
Apply reproduction on top of HEAD

Test commit D + R'

$ make test # => error
Test your app at commit D with R'

Undo R'

$ git reset --hard HEAD~
Undo R' and return to commit D

Give feedback about D

$ git bisect bad
Give git feedback about the test result

Repeat!

$ git cherry-pick repro
Give git feedback about the test result

Documentation

Written by Experts

Le Petit Robert
git-rebase - Forward-port local commits to the updated upstream head
Pro Git

Image Credits

Show a printable version of this presentation. Use your browser to print.