1.4.11 · D2Python & Scientific Computing

Visual walkthrough — Git version control basics

1,915 words9 min readBack to topic

Step 1 — A project is just a folder of files, changing over time

WHAT. Before Git exists, your project is a plain folder. You edit train.py, save it, edit it again. Each save destroys the previous version — the old text is gone forever.

WHY start here. To appreciate what Git adds, we must first feel the pain it removes: with a plain folder there is exactly one version of each file, the current one. No memory, no undo.

PICTURE. Below, the black box is your folder. The file inside changes from v1 to v2 to v3. Notice the red arrow of time only moves forward — old content is overwritten and unrecoverable.

Figure — Git version control basics

Step 2 — A snapshot: freezing the folder in a photograph

WHAT. Instead of overwriting history, imagine taking a photograph of the entire folder each time you reach a good stopping point. Each photo captures every file exactly as it was.

WHY a photograph and not a diff-list? Because a photo is complete on its own — to go back to it you don't have to replay a chain of edits, you just look at the picture. This is the mental model Git wants you to have: a commit is a full snapshot you can jump to instantly. (Under the hood Git is clever about not storing duplicate bytes, but conceptually every commit is a whole photo.)

PICTURE. Three photographs pinned on a wall. Each is a frozen copy of the folder. The red one is the photo we're taking right now.

Figure — Git version control basics

Step 3 — Naming each photo: the hash

WHAT. We need to refer to a specific photo. Git gives every commit a unique name computed from its contents — a hash, printed as a short string like a3f2e1b.

WHY compute the name from the contents (why not "photo #1, #2")? Because a content-based name is tamper-evident: if even one byte of the snapshot changed, the name changes. So a hash is both an address ("fetch me commit a3f2e1b") and a fingerprint ("this is exactly that snapshot, unaltered"). Counting numbers like "#1, #2" could not do that — two people could both make a "#2".

PICTURE. Each photo now carries a small red label — its hash. The label is derived from the photo, symbolised by the arrow from the photo to its label.

Figure — Git version control basics

Step 4 — Photos in a chain: the parent pointer

WHAT. Each new photo remembers the name of the photo that came just before it. That backward link is the parent pointer.

WHY store a backward link? So the photos form a history, not a loose pile. Starting from the newest photo you can walk backward — parent, parent, parent — and replay the entire story of the project. This chain is exactly what "time travel" means in the parent note.

PICTURE. The photos are now connected by black arrows, each pointing back to its parent. The newest photo is highlighted red — this is where "now" lives.

Figure — Git version control basics

Step 5 — Why a middle room exists: the staging area

WHAT. You edited three files but only two of them are ready to be photographed. Git gives you a middle room — the staging area (also called the index) — where you place only the files you want in the next photo.

WHY not photograph the whole folder every time? Because a good snapshot is one logical change. If you fixed a bug in model.py and also half-wrote a new feature in experiment.py, you don't want them in the same photo. The staging area lets you curate: git add model.py places just that file in the middle room; the half-done file stays behind.

PICTURE. Three rooms left-to-right. Left = working directory (all edits). Middle = staging area (the chosen file, red). Right = repository (the wall of past photos). git add moves a file rightward into the middle.

Figure — Git version control basics

Step 6 — The commit: photographing the middle room

WHAT. git commit -m "fix loss bug" takes everything currently staged, snaps one photo, gives it a hash, links it to the current newest photo as parent, and empties the middle room.

WHY empty the middle room after? Because the staging area's job is "what goes in the next photo." Once that photo is taken, the next one starts empty — a clean slate for your next logical change.

PICTURE. The middle room's contents become a new photo on the wall, wired to the previous newest as parent. The middle room is now empty; the red HEAD label has moved to the brand-new photo.

Figure — Git version control basics

Step 7 — A branch is just a movable sticky-note

WHAT. A branch (like main) is another sticky-note pointing at a photo. Making a new commit slides that sticky-note forward onto the new photo automatically.

WHY is this "cheap"? A branch stores only a 41-byte hash — the name of one photo. It copies nothing. So creating a branch to try a risky idea costs almost no space. To split off, you just add a second sticky-note (feature) on the same photo; commits on feature slide its note forward while main stays put.

PICTURE. Two sticky-notes, main (black) and feature (red), start on the same photo. A commit on feature slides the red note forward, leaving main behind — the timeline has forked.

Figure — Git version control basics
Recall Check yourself

What physically is a branch? ::: A movable label (41 bytes) holding the hash of one commit — nothing more. After git commit, which label moves and where? ::: The branch HEAD points at slides forward onto the new photo. Why doesn't a plain folder let you "go back"? ::: Saving overwrites the old file content — there is no stored history to return to.


Step 8 — Degenerate & edge cases you must not be surprised by

WHAT / WHY / PICTURE, all in one figure because each case is a missing piece of a previous figure:

Figure — Git version control basics
  • Empty middle room: running git commit with nothing staged takes a photo identical to the last one → Git refuses ("nothing to commit"). The photo would carry no new information.
  • The very first commit: it has no parent — the chain has to start somewhere. Its parent pointer is empty (the root).
  • Untracked file: a brand-new file Git has never seen sits in the working directory but is invisible to snapshots until its first git add. It is neither staged nor committed — a fourth, "outside" state.
  • Fast-forward merge: if main never moved while feature gained commits, merging just slides the main sticky-note forward — no new photo needed, because there's nothing to combine.

The one-picture summary

Figure — Git version control basics

This single diagram compresses the whole walkthrough: three rooms (working → staging → repository), the wall of parent-linked photos, and the movable main / feature/ HEAD labels. Trace the red path: edit → git addgit commit → a new labelled photo appears and HEAD slides onto it.

Recall Feynman retelling (say it in plain words)

Your project is a folder that normally forgets its past. Git fixes the forgetting by letting you take a photograph of the whole folder whenever you like — that photo is a commit. Each photo gets a fingerprint-name (a hash) computed from its own contents, and remembers the name of the photo before it (its parent), so all the photos form a chain you can walk backward through — that's your time machine.

But you rarely want a photo of the entire messy folder, so Git adds a middle room, the staging area. git add carries the exact files you choose into that room; git commit photographs whatever is in the room, pins it on the wall, and clears the room. A branch is just a labelled sticky-note stuck on one photo; when you commit, the note you're standing on (HEAD) slides forward. Want to try something risky? Stick a second note (a feature branch) on the same photo and let it wander forward on its own — main stays safe. Merging that work back is often just sliding the main note up to catch it. That is the entire machine.