Skip to content
RungsySign in

Git Fundamentals

Commits are snapshots on a graph — once that clicks, git stops being scary.

35 minDifficulty 1/5tooling · fundamentalsHand-writtenWritten by a person and not yet reviewed by a second one. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.

Why this exists

Most people learn git as a list of spells. `git add`, `git commit`, `git push` — and when something goes wrong, `git reset --hard` and hope. It stays frightening because the commands are being memorised instead of the model underneath them, and that model is genuinely small: commits are immutable snapshots linked into a graph, and branches are sticky notes pointing at one. Almost every confusing thing git does becomes obvious once those two sentences are real to you.

The mental model

A save-game system where every save is permanent and numbered by its contents. You can never overwrite an old save. Branches are labelled bookmarks you stick on a save, and moving a bookmark costs nothing because it's just a label — the saves themselves never move.

How it works

A commit is a snapshot with a parent

A commit records the complete state of your tracked files at one moment, plus a pointer to the commit that came before it. Not a diff — a full snapshot. Git shows you diffs because diffs are what humans want to read, but it stores snapshots, deduplicating identical file contents so this is cheaper than it sounds. The parent pointer is what turns a pile of snapshots into history: follow the pointers backwards and you have the story. A commit with two parents is a merge, and that's the only reason history is a graph rather than a line.

A commit's name is a hash of its contents

The id you see — `a1b2c3d` — is a SHA hash of the snapshot, the parent, the author, the message and the timestamp. Change any one of those and you get a different id, which means you get a *different commit*. This is why nothing in git is ever edited in place. Amending a commit doesn't modify it; it builds a new one and moves the branch pointer. Rebasing doesn't move commits; it replays them as new commits with new ids. Once you expect that, the warnings about rewriting shared history stop being superstition: the old commits still exist, and anyone who had them now has commits nobody else does.

Branches are pointers, which is why they're free

A branch is a file containing one commit id. That's the entire implementation. Creating a branch writes 41 bytes; deleting one removes 41 bytes. Nothing is copied, so branching a ten-gigabyte repository is as fast as branching an empty one. `HEAD` is one more pointer, usually pointing at a branch rather than directly at a commit — and when it points straight at a commit instead, that's the famous detached HEAD state, which is far less alarming than its warning message suggests. It just means there's no bookmark that will follow you when you commit.

Three trees, and every command moves between them

Your working directory is what's on disk. The index, also called the staging area, is what will go into the next commit. HEAD is the last commit. Every git command you know is a move between these three. `add` copies working directory into the index. `commit` turns the index into a new commit and advances HEAD. `checkout` and `switch` overwrite the working directory from a commit. `reset` moves HEAD and, depending on the flag, the index and working directory with it. The reason the staging area confuses newcomers is that it's usually taught as a nuisance step rather than as the thing being committed — but `git commit` commits the index, not your files, and every surprising commit you've ever made comes from that gap.

Committed work is very hard to lose

Because commits are immutable and named by content, a commit that nothing points at still exists on disk. `git reflog` records every position HEAD has held for the last ninety days, including the ones you destroyed with a bad reset or rebase. Recovering is usually one line: find the id in the reflog, and point a branch back at it. This is the single most useful thing to know about git, and it changes how you use it — experiments stop being risky. The genuinely dangerous operations are the ones touching work you never committed: `git checkout .`, `git reset --hard` with unstaged changes, and `git clean -fd` all destroy things git was never told about.

The mechanism

Watch a file cross all three trees: ```bash echo "hello" > notes.txt git status --short # ?? notes.txt — untracked: in the working dir only git add notes.txt git status --short # A notes.txt — now in the index too git commit -m "notes" git status --short # (empty) — all three trees agree ``` Now the part that makes branching make sense: ```bash cat .git/HEAD # ref: refs/heads/main cat .git/refs/heads/main # a1b2c3d4e5... git switch -c experiment cat .git/refs/heads/experiment # a1b2c3d4e5... — identical ``` Two branches, one commit, forty-one bytes of difference.

flowchart LR
    subgraph trees[The three trees]
        WD[Working directory<br/>files on disk] -->|git add| IDX[Index<br/>staged for commit]
        IDX -->|git commit| HEADC[HEAD<br/>last commit]
        HEADC -->|git checkout| WD
    end
    subgraph graph[The commit graph]
        C1[c1] --> C2[c2] --> C3[c3]
        C3 -.->|main points here| MAIN[main]
        C3 -.->|and so does| HEADP[HEAD]
    end
    HEADC --> C3
Diagram source for Git Fundamentals.

What people get wrong

git commit saves my files.
It saves the index. Anything you edited but did not stage is not in the commit. The working directory and the index are different trees. This is the source of nearly every 'I committed but the change isn't there' moment, and why reviewing with git diff --staged before committing is worth the two seconds.
Creating a branch copies the repository.
A branch is a file containing one commit id. Creating one writes 41 bytes. People coming from older version control systems expect branching to be expensive and therefore avoid it. In git it is the cheapest operation there is, which is why the workflow is built around making branches freely.
git reset --hard destroys work permanently.
It destroys uncommitted work permanently. Anything that was ever committed is recoverable from the reflog for about ninety days. Commits are immutable and content-addressed, so an unreferenced commit still exists on disk. The reflog remembers where HEAD has been, which is enough to point a branch back at it.
Git stores diffs between versions.
It stores full snapshots, with identical file contents shared between commits. Diffs are computed on demand for display. This is why checking out any commit is fast regardless of how far back it is — there is no chain of patches to replay.
Rebasing moves my commits onto another branch.
It creates new commits with the same changes and different ids, then moves the branch pointer to the last one. A commit id is a hash that includes the parent. Change the parent and you have a different commit by definition. This is exactly why rebasing shared branches causes trouble: colleagues hold the originals, which now have no relationship to yours.

When not to use it

Large binary assets — video, datasets, compiled artefacts.
Git LFS, or object storage referenced from the repo. Git stores every version of a binary in full, so a 200MB file edited fifty times is a 10GB clone for everyone, forever.
You want a backup of your work.
An actual backup. Git protects committed history on machines that have it; it does nothing for uncommitted work, and a repo that only exists on your laptop has exactly one copy.
Secrets — API keys, certificates, credentials.
Environment variables and a secret manager. A committed secret is in history permanently and is compromised the moment it is pushed, even if you delete it in the next commit.

Terms

Commit
An immutable snapshot of tracked files, plus its parent, author, message and timestamp. Named by a hash of all of that.
HEAD
A pointer to where you are — normally to a branch, which in turn points to a commit.
Index (staging area)
The set of changes that will become the next commit. Distinct from both your files and your last commit.
Ref
A named pointer to a commit. Branches and tags are both refs; the difference is that a branch moves when you commit and a tag does not.
Detached HEAD
HEAD pointing straight at a commit instead of at a branch. Commits still work, but nothing follows you, so they are easy to lose track of.
Reflog
A local log of every position HEAD has held. The recovery mechanism for anything that was committed and then orphaned.
Fast-forward
A merge where the target branch is simply an ancestor, so git can move the pointer forward instead of creating a merge commit.

In an interview

What is the difference between git reset and git revert?

  • reset moves the branch pointer backwards, so the commits become unreferenced
  • revert creates a NEW commit that undoes the changes, leaving history intact
  • reset rewrites history and is unsafe on a shared branch
  • revert is the correct choice for anything already pushed
  • reset has soft, mixed and hard modes that differ in whether the index and working directory move too

Why is creating a branch in git effectively free?

  • a branch is a file containing a single commit id, roughly 41 bytes
  • no files are copied — the commits already exist and are shared
  • commits are immutable and content-addressed, so they can be shared between any number of branches
  • this is a deliberate design difference from centralised systems where branching copied the tree

Can you recall it?

Explain what a commit actually is, and use that to explain why creating a branch costs almost nothing.

Sources

Keep track of this

Add Version Control to your map and Rungsy will schedule reviews so you actually remember it.