Branching & Merging Deeply
Branching & Merging Deeply
In the previous lesson you learned how Git stores data as a directed acyclic graph of objects. Now we exploit that structure to understand what a branch really is, why Git can create one in microseconds, and what happens under the hood during a merge or a conflict. At Google and Meta, dozens of engineers push to the same repository every minute — every one of them depends on the mechanics covered here.
Branches Are Cheap Pointers
A branch is nothing but a file in .git/refs/heads/ containing a 40-character SHA-1 (now increasingly SHA-256) that points to a commit object. Creating a branch does not copy files; it writes 41 bytes to disk.
Every time you make a commit, Git advances the current branch pointer to the new commit. HEAD follows along, always pointing to whatever the current branch pointer points to — unless you are in a detached HEAD state (HEAD points directly to a commit, not to a branch file).
Fast-Forward Merge
When the target branch (e.g., main) has not diverged from the branch being merged in — meaning every commit on main is already an ancestor of the feature branch — Git simply advances the pointer. No merge commit is created. This is a fast-forward.
--no-ff or "merge commits" so that every feature integration is visible in git log --graph as a discrete event. This is invaluable in incident retrospectives: "When did auth-service land on main?" has a clear, searchable answer.
Three-Way Merge & Merge Commits
When both branches have diverged, Git cannot fast-forward. Instead it finds the merge base — the most recent common ancestor of both tips — and applies a three-way merge algorithm comparing: merge base, our branch tip, their branch tip. If both sides changed the same lines, you get a conflict.
Resolving Conflicts
A conflict means Git cannot automatically reconcile two sets of changes to the same region of the same file. Git writes conflict markers into the file and halts:
HEAD is your current branch (what you have). The section after ======= is what is coming in. Your job is to produce the correct merged result — often combining both changes:
vimdiff, meld, or an IDE integration so that git mergetool opens an intuitive three-pane view (LOCAL / BASE / REMOTE → MERGED). Set yours with git config --global merge.tool vimdiff and git config --global mergetool.keepBackup false.
Common Failure Modes in Production
- Long-lived branches accumulate conflicts. A feature branch open for two weeks against a busy
maincan accumulate hundreds of conflicting hunks. The fix is frequent integration — merge or rebase frommainevery day, not before the PR is due. - Octopus merges gone wrong.
git merge branchA branchB branchCperforms an octopus merge (one commit, multiple parents). Git refuses if there are conflicts; use sequential merges instead. - Binary file conflicts. Git cannot three-way-merge a PNG or a compiled binary. Adopt Git LFS and set merge strategies (
*.png merge=oursin.gitattributes) to always keep one side. - Forgetting to delete merged branches. Repositories with thousands of stale branches become slow. Enforce automated branch deletion: GitHub has "Delete branch on merge"; GitLab has the same toggle. Add a cron that prunes remote tracking refs:
git fetch --prune.
git revert to undo, not git push --force.
Merge Strategies at Scale
Git supports pluggable merge strategies passed with -s. The default is ort (Ostensibly Recursive's Twin — introduced in Git 2.34, now the default, faster and more correct than the old recursive). For binary conflicts or generated files, -s ours keeps your version wholesale. The -X flag passes strategy options: -X ours (with the default strategy) auto-resolves conflicts by preferring your side, useful when merging a release branch back into a fast-moving main.
Squash merges produce a linear history on main without the overhead of rebase, at the cost of losing granular per-commit authorship. Many companies (Shopify, Stripe) default to squash merges on their trunk precisely for bisectability — every commit on main is a complete, deployable unit.