Branches and HEAD
Trace commits and branches, choose an appropriate undo operation, and resolve a small merge conflict.
THE IDEA
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.
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.
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 onSwitching 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.
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.
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.
TRY IT YOURSELF
A repository with three commits on main. The prompt shows where HEAD is. The graph on the right redraws after each command; branch labels hang above commits.
- Create a branch named feature at the current commit, but stay on main
- Move HEAD onto feature
- Create search.txt and commit it on feature. main must not move
- Go back to main and notice search.txt disappear from the folder
- Check out the very first commit so that HEAD points straight at a commit (detached HEAD)
- Make a commit while detached, then keep it by creating a branch named rescue at that spot
Commit graph (live)
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
Two branches point to commit B. How many copies of B exist?
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.