LineLapse BlogMake your own →
← All posts

How does git branching and merging actually work?

#git#version-control#developer-tools#workflow

Every programmer eventually hits the same moment of confusion: you make a branch, someone else makes a branch, and somehow git manages to combine your changes into one coherent history without either of you touching the other's code. It looks like magic. It isn't — it's a simple idea about snapshots and common ancestors, applied very consistently.

Git Branching & Mergingwhat's really happening under the hoodcommit Acommit Bcommit Cmaingit checkout -b feature/loginfeature/logincommit Dcommit Efeature/logineach commit → parentgit merge feature/logincommon ancestordifferent files → autosame lines → conflictmerge committwo parentsmainhistory unifiedparentparent
▶ Watch it draw

A commit is a snapshot, not a diff

The first misconception to clear up: git doesn't store your history as a chain of changes. Each commit is a full snapshot of your project at that moment (internally it's smarter than copying every file, but conceptually that's the model), plus a pointer back to the commit that came before it. String those pointers together and you get a line running backward through time — your history.

A branch, then, is almost nothing. It's just a movable label pointing at one commit. When you create a branch, git doesn't duplicate any files — it just writes down "this name points here." When you commit on that branch, the label moves forward to the new commit. That's the whole trick: branches are cheap because they're just pointers, not copies of your codebase.

Branching: two labels, one shared past

Say you're on main at commit C, and you create a branch called feature. Both labels now point at C. You start committing on feature, and its label walks forward — D, then E — while main stays put at C. Nothing about main has changed; you've simply been laying down new snapshots that only the feature label can see.

Meanwhile, someone else keeps committing on main itself, moving its label forward too — say to F. Now you have two branches that agree on their shared history up through C, and then diverge: one path goes C → D → E, the other goes C → F. That point where they last agreed, C, is called the common ancestor, and it's the key to everything that happens next.

Merging: three snapshots, one comparison

To merge feature back into main, git doesn't need to understand your code or replay every commit one by one. It only needs three snapshots: the common ancestor (C), the tip of main (F), and the tip of feature (E). For every file, it asks the same question: what changed between C and F, and what changed between C and E? If only one side touched a given file, git takes that side's version. If both sides changed the same lines, that's a conflict, and it asks you to pick the outcome by hand. Everything else resolves automatically.

The result is a new commit with two parents — one pointing back to F, one back to E — which is how the graph shows a merge as two lines flowing into one. main's label moves to this new commit, and from that point on, the history contains both sets of work.

Fast-forwards: when there's nothing to merge

There's a special case worth knowing about. If main hadn't moved at all while you were working — it's still sitting at C — then merging feature in doesn't require combining anything. Git can just slide the main label forward to E, since E already contains everything C had plus your new work. This is called a fast-forward, and it's why some merges show up in your history as a clean straight line instead of a diamond shape with two parents.

Why conflicts happen (and why they're not really "git's fault")

A conflict isn't a sign that something went wrong — it's git being honest about a situation it can't resolve safely. If you changed line 12 of a file on feature and someone else changed the same line 12 on main, there's no way to combine both edits without a human deciding what the line should actually say. Git flags exactly that spot, leaves both versions visible in the file, and waits. Everywhere else in the file that only one side touched, it has already merged for you without a word.

The takeaway

Branches are just labels on commits, commits are snapshots linked to their parents, and merging is a three-way comparison against the last point two branches agreed on. Once you see the graph this way — labels sliding along a web of snapshots — the commands stop feeling like incantations and start feeling like exactly what they say: branch makes a label, commit moves it forward, merge reconciles two labels' paths back to where they split.


This explainer was made with LineLapse — type a topic, get a hand-drawn video. Make your own →