Foundations: Python, Git and toolsLoading progress…

Branches and HEAD

Trace commits and branches, choose an appropriate undo operation, and resolve a small merge conflict.

A branch is a sticky note on a commit

A branch is just a name that points at one commit. Creating a branch writes a tiny pointer; no files are copied. HEAD says where you are: usually it points at a branch, and the branch points at a commit. When you commit, the new commit gets your current commit as its parent, and the branch HEAD points at moves to it.

5f48158 A5f48158A3a2d82a B3a2d82aBHEAD → main

HEAD is attached to main. A new commit moves only that label. Commits so far: 2.

Press a button. The commands run on a real (simulated) repository.
Create a branch, switch, commit. Only one label moves per commit; the file count of the repository does not double.
git branch feature     # new label on the current commit, you stay put
git switch feature     # HEAD now points at feature
git switch -c idea     # create and switch in one go
git branch             # list branches; * marks the one HEAD is on

Switching branches rewrites your folder to match that commit, which is why a file you committed only on feature disappears when you go back to main.

Detached HEAD

You can also point HEAD straight at a commit with git switch --detach <commit>. That is fine for looking around. But a commit made there has no branch name, so when you switch away nothing points at it.

5f48158 A5f48158A3a2d82a B3a2d82aBHEAD → main

Dashed commits are not reachable from any branch. They still exist for now, but nothing points at them.

Press a button. The commands run on a real (simulated) repository.
Commit while detached, leave, and see the commit become unreachable. Then rescue it by naming it.

Predict first. Branches main and feature both point to commit B. How many copies of B exist?

Chapter context

Git records snapshots in a graph of commits. Branch names are movable references to commits. Treat the working tree, staging area and recorded history as distinct states, and many apparently mysterious commands become predictable.

Chapter reference: math, worked example & code

This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.

Math and notation

Model history as a directed acyclic graph. A commit is reachable from a branch when following parent edges reaches it. A merge preserves both ancestry paths; rebasing creates a different path with new commit identities.

Worked example

Let B have parent A, C have parent B, and D have parent B. A merge M with parents C and D makes both C and D reachable. Moving a branch from C to M is different from copying D's files over C: the graph also records why those histories meet.

Reference code

The following example uses the Python standard library and does not call a remote service.

parents = {'A': [], 'B': ['A'], 'C': ['B'], 'D': ['B'], 'M': ['C', 'D']}
def ancestors(commit):
    seen, todo = set(), [commit]
    while todo:
        node = todo.pop()
        if node not in seen:
            seen.add(node)
            todo.extend(parents[node])
    return seen
assert ancestors('M') == {'A', 'B', 'C', 'D', 'M'}

Python examples are provided for study and local execution; this page does not execute Python.

Common mistakes

Git commit does not automatically upload data. Rebase changes commit identities. Reset modes have different effects on uncommitted work.