Branches: Parallel Lines of Work

This chapter shows you how branches work, how to move between them, and what happens when you merge two of them back together.

What a Branch Actually Is

A branch is a movable pointer to a commit. That is the entire definition.

When you run git branch feature/login, git creates a file called .git/refs/heads/feature/login with a commit hash in it. That's the branch. No copy of files is made. Branches are cheap.

When you commit on a branch, the pointer moves forward to the new commit. When you switch to a branch, HEAD moves to point at that branch.

Keep this in mind throughout the chapter. Every command maps back to "move a pointer".

Listing and Creating Branches

git branch                    # list local branches; * marks current
git branch -a                 # also list remote-tracking branches
git branch -v                 # show tip commit and message
git branch -vv                # also show tracking relationships

git branch feature/login      # create (doesn't switch)
git branch -d feature/login   # delete (safely; refuses if unmerged)
git branch -D feature/login   # force delete
git branch -m old new         # rename

The sign of a tidy repo is a git branch list short enough to read without scrolling.

Switching Branches

Modern git:

git switch main                 # switch to existing branch
git switch -c feature/login     # create and switch in one step
git switch -                    # switch to previous branch (like cd -)

Older tutorials use git checkout <branch> for the same thing. switch is clearer; use it.

When you switch, git updates the working tree to match the target branch. Uncommitted changes that don't conflict come along; changes that would conflict block the switch. Stash or commit first.

Fast-Forward Merge

The simplest merge. main hasn't moved since you branched, so merging is just moving the main pointer to your branch tip:

Before                       After fast-forward merge
A --- B (main)               A --- B --- C --- D (main, feature/login)
       \
        C --- D (feature/login)
git switch main
git merge feature/login
Updating 3f1a22c..9b2f11a
Fast-forward
 src/login.ts | 42 ++++++++++++++++++++++++++++++++++++++++++

No merge commit is created. History stays linear.

To force a merge commit even when fast-forward is possible:

git merge --no-ff feature/login

Some teams prefer this so every feature lands in a single visible commit. Opinion varies; Chapter 10 covers workflows.

Three-Way Merge

When both branches have moved since they diverged, git creates a merge commit with two parents:

Before                       After three-way merge
A --- B --- X --- Y (main)   A --- B --- X --- Y --- M (main)
       \                            \             /
        C --- D (feature/login)      C --- D ----
                                     (feature/login still points at D)
git switch main
git merge feature/login

If the changes don't overlap, git merges automatically and opens your editor for the merge commit message. If they do, you get a conflict; Chapter 7 handles that.

M is a merge commit. It has two parents. git log --graph draws the shape clearly.

Deleting a Branch

Once merged, delete the branch. Keeps the list tidy.

git branch -d feature/login

Git refuses to delete an unmerged branch. If you're sure:

git branch -D feature/login

Capital D force-deletes. The commits are only gone if nothing else points at them; the reflog still has the tip for a while (Chapter 8).

HEAD and Detached HEAD

HEAD is usually a symbolic reference pointing to a branch:

HEAD → refs/heads/main → 3f1a22c

If you check out a commit directly (not a branch):

git switch --detach 3f1a22c
# or the older
git checkout 3f1a22c

You end up in "detached HEAD" state. HEAD points straight at a commit, no branch in between. Git warns you loudly:

Note: switching to '3f1a22c'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

Commits you make here have no branch holding them. When you switch away, they become unreachable. The reflog can still rescue them, but the safer move is to make a branch:

git switch -c experimental

Now your commits have a home.

Tags

A tag is another kind of pointer: a name for a specific commit that doesn't move.

Two kinds:

Lightweight tag: just a name for a commit.

git tag v1.0.0

Annotated tag: a full object with tagger, date, and message. Used for releases.

git tag -a v1.0.0 -m "1.0 release"

Push tags to a remote (they don't go automatically):

git push origin v1.0.0
git push --tags          # push all tags

Use annotated tags for anything you'd release or deploy. Lightweight tags for personal bookmarks.

Stash: Quick Shelf for Uncommitted Work

You're mid-edit and someone pings: "can you check something on main?" You don't want to commit half-done work. Stash it:

git stash push -m "WIP login validation"
git switch main
# do the thing
git switch feature/login
git stash pop

git stash list shows your stashes. git stash show -p stash@{0} shows the diff. git stash drop stash@{0} throws one away.

Stash is a shortcut, not a filing system. Keep stashes small and short-lived; long-lived stashes are a bug magnet.

Common Pitfalls

Committing on the wrong branch. Run git status; it shows the branch in the first line. Checking twice is cheaper than moving commits later.

Working on main in a team repo. Create a branch before you start editing. Trying to push a commit to a protected main will fail; trying to push to an unprotected one will cause drama.

Deleting a branch you hadn't merged. Use git branch -d (lowercase). It refuses unmerged branches. If you force-deleted and need the branch back, git reflog can find the tip commit.

Expecting git branch to switch. It only creates. Use git switch to switch, or git switch -c to create-and-switch.

Next Steps

Continue to 04-remotes.md to sync your work with other machines.