Branching and Merging
Topic 11

Branches

Branching

A branch is a movable named pointer to a commit. It lets you develop a feature in isolation from main and merge the result back when it is ready, without your half-finished work ever touching the shared line of history. Because the pointer is the entire branch, creating one is nearly instant.

If branching feels heavyweight to you, that intuition comes from older systems where a branch was a directory copy on a server. In Git it is a 41-byte file. The cost of having ten branches open is ten small files; the cost of merging them later is the only thing worth thinking about.

What a Branch Actually Is

A branch is a file under .git/refs/heads/ containing one commit hash. That is the whole data structure. HEAD is a second pointer that names which branch you are currently on — usually it holds ref: refs/heads/main rather than a hash directly. When you commit, Git writes the new commit and then advances the current branch's hash to point at it; HEAD follows along because it points at the branch, not the commit.

This is why "switching branches" changes the files in your working tree but copies nothing: Git reads the snapshot the target branch points at and lays it down. The branch did not store those files — the commit it points to did.

Creating and Switching

Use git switch -c <name> to create a branch and move onto it in one step, and bare git switch <name> to move to an existing one. The older git checkout -b <name> does the same thing, but checkout is overloaded — it also discards file changes — and that overload is a frequent source of accidental data loss. New code should prefer switch for branches and restore for files, which split those two unrelated jobs apart.

A new branch starts at whatever commit HEAD is on when you create it. Branch from a stale local main and your feature starts behind the remote before you have written a line.

Tracking and Remote Branches

A local branch and its remote-tracking counterpart are different refs. main is your local branch; origin/main is a read-only local snapshot of where the remote's main was at your last fetch. Setting upstream tracking with git push -u origin <branch> links the two so a bare git push and git pull know where to go. Deleting a local branch does nothing to the remote — that takes git push origin --delete <branch>.

Listing and Deleting

git branch lists local branches with the current one marked. git branch -d <name> deletes a branch but refuses if its commits are not merged anywhere, which is the safety you want; git branch -D forces the delete regardless. The lowercase form is the one to reach for by default — the uppercase form throws away commits that only git reflog can recover.

Branching Strategies

Short-lived feature branches that live hours to a few days are the low-conflict path: they diverge from main for a small window, so the merge back is small. Trunk-based development pushes this further, integrating to main behind feature flags many times a day. The opposite — a branch that lives for weeks while main moves on — turns the eventual merge into a conflict-heavy ordeal that frequent integration would have avoided entirely.

Common Mistakes
  • Letting a feature branch live for weeks while main moves on, so the eventual merge is a conflict-heavy ordeal that a daily merge or rebase would have spread out into trivial ones.
  • Deleting a branch with git branch -D before its work is merged anywhere, discarding commits that only git reflog can recover.
  • Confusing a local branch with its remote-tracking origin/<branch> and pushing to the wrong place, or assuming a local delete also removed the branch on the remote.
  • Working directly on main and pushing, bypassing review and turning any rollback into a shared-history rewrite.
  • Creating a branch from a stale local main instead of fetching first, so the branch starts behind the remote and merges dirty.
Best Practices
  • Use git switch -c <name> to create feature branches and keep them short-lived, on the order of hours to a few days.
  • Fetch and update main before branching so the new branch starts from current state.
  • Use git branch -d (lowercase) for deletes so Git refuses to drop unmerged work.
  • Set upstream tracking with git push -u origin <branch> so plain git push and git pull resolve the remote automatically.
  • Integrate main into a long-running branch regularly rather than saving one giant merge for the end.
Comparable toolsMercurial named branches are heavyweight and permanent; bookmarks are the movable Git-branch analogSubversion branches are directory copies under a branches/ pathPerforce uses streams or branch specsFossil branches are tags on the graph, first-class but never deleted

Knowledge Check

What is a Git branch, physically?

  • A small ref file under .git/refs/heads/ holding one commit hash
  • A full copy of the project's files at the branch point
  • A server-side directory created by copying the tree under a branches/ path
  • A stored diff against main that Git recomputes on every new commit

What is the difference between main and origin/main?

  • main is your local branch; origin/main is a local snapshot of the remote as of your last fetch
  • They are two interchangeable aliases for one underlying ref and always resolve to the exact same commit hash
  • origin/main is the editable branch you commit on directly, while main is a read-only mirror that updates on fetch
  • origin/main lives only on the server and is queried fresh over the network each time

Why is git branch -d safer than git branch -D?

  • -d refuses a branch whose commits are unmerged, while -D forces the delete and can discard work
  • -d deletes only the remote copy on origin over the network, leaving the local branch and its reflog fully intact
  • -D erases the underlying commit objects while -d only hides the branch from listings
  • There is no practical difference between them; the two flags are interchangeable aliases for the same delete operation

Why does a long-lived feature branch cost more than several short ones?

  • It diverges from main for a long window, so the eventual merge accumulates many overlapping changes to resolve at once
  • Long-lived branches consume proportionally more disk over time because Git keeps a separate full copy of the working files for each one
  • Git enforces a hard ceiling on how many commits a single branch may accumulate, and a long-lived branch eventually hits that limit
  • Remote-tracking refs expire automatically after a week of inactivity, breaking any branch that has lived longer than that window

You got correct