Start Here: What Git Is

This course is written for people who can read a C program, know what an inode is, and spend their day in bash — and who have never actually learned git. Maybe you’ve cloned a repo, copied three commands off a wiki page to get your change in, and moved on. That’s a normal place to start, and it’s the place most git tutorials quietly fail you from.

This page does three things: says what git is actually for, explains why those tutorials didn’t stick (the reason is structural, not a failure of attention), and gets a working repo in front of you. Then the real course starts.

Sanitized — no real hosts, paths, or project names. The whole series follows one fictional project, sensor-logger, so the examples build on each other.


What git is for

You already have a version control system. It looks like this:

sensor.c
sensor.c.bak
sensor.c.bak2
sensor-working.c
sensor-final.c
sensor-final-ACTUALLY.c

It works until you need to answer a question it can’t answer: what changed between the version that worked in the lab and the one that doesn’t? Or worse, until a second person edits the same directory and one of you saves over the other.

Git does two jobs. The first is the one people expect: it remembers every state a directory tree has ever been in, so you can go back to any of them, compare any two, and find out when and why a line changed. The second job is the one that actually explains git’s design: it reconciles changes that two people made independently, without either of them stopping.

That second job is why git isn’t a backup script or a sync tool. rsync and Dropbox make a remote copy match the newest version — last write wins, and the state before it is gone. Git never overwrites anything; it appends new states and keeps track of how they relate to each other. When two people change the same file, a sync tool has to pick a winner. Git has enough structure to combine both, and to stop and ask you when combining is genuinely ambiguous.

Almost everything strange about git makes sense once you assume that second job is the hard requirement and the first one is a side effect.

Every clone is the whole database

The thing that surprises most people arriving from a shared network drive or from Subversion: git clone copies the entire history, not just the current files. Every commit, every branch, all of it, onto your disk.

So git log, git diff, git branch, git commit, and git blame are all local operations. No server, no network, no waiting. You can work on a plane and commit twenty times.

The server — GitLab, GitHub — is just another clone that everyone has agreed to meet at. It has no special powers; it’s a convention. That’s what “distributed” means in “distributed version control,” and it has a practical consequence worth internalizing early: if the server dies, every developer’s laptop holds a complete copy of the project’s history.

The closest thing in your world is every host carrying a full local package mirror instead of NFS-mounting one server’s directory. Same trade: more disk, far less coupling.

Why git’s commands feel arbitrary

Here’s the part that explains your last three years with git.

Git’s command set is officially split into two layers, and the metaphor is bathroom fixtures.

Plumbing is the low-level commands that operate directly on the repository’s data structures: hash-object, cat-file, write-tree, commit-tree, update-ref, rev-parse, update-index. These are the pipes in the wall. They are stable, boring, precisely specified, and almost nobody types them daily.

Porcelain is the high-level, human-facing commands that compose plumbing into workflows: add, commit, checkout, merge, pull, rebase. This is the sink you actually touch.

This is not community slang — it’s git’s own vocabulary, and you can see the split yourself:

$ git help -a
...
Main Porcelain Commands
...
Low-level Commands / Manipulators
Low-level Commands / Interrogators
Low-level Commands / Syncing Repositories

(One false friend, since you’ll meet it in scripts: the --porcelain flag, as in git status --porcelain, means the opposite of what you’d guess — stable, machine-parseable output. Different sense of the word, same era of naming decisions.)

A porcelain-first tutorial is one that starts at the workflow surface and mostly stays there: here’s how to stage, here’s how to commit, here’s the command to type when you want to undo, here’s a branching-model diagram. The object model shows up late if at all, usually as an “under the hood” appendix you were never told to read.

That approach is fine for a lot of people. It fails specifically on people who think in terms of on-disk structures, because git’s porcelain is famously incoherent as an interface:

  • git checkout switches branches, restores individual files, and creates detached HEADs — three unrelated operations sharing one verb. (Git eventually conceded the point and split off git switch and git restore.)
  • git reset has three modes that do substantially different things to three different pieces of state, and the mode is a flag you have to already understand to choose.

Handed that surface as the primary artifact, there is no consistent model to generalize from. So you memorize recipes, and you guess when a recipe doesn’t fit — which is exactly the failure people describe as “I don’t really get git.” You weren’t missing effort. You were given the fixtures and no plumbing diagram, and you know from filesystems that surface commands only make sense after the data structure does.

So this course teaches the store first

The corrective is to learn git the way you’d learn any storage system: read the on-disk format, then let the commands become obvious.

The whole model is smaller than the command surface suggests. Git is a content-addressed key-value store — the key of an object is the hash of its own contents — holding four object types, plus a set of tiny pointer files that name interesting hashes. Refs are not metaphors: .git/refs/heads/main is a 41-byte text file containing forty hex characters and a newline, and .git/HEAD is a one-line file that usually just says ref: refs/heads/main.

The mapping to things you already know is close enough to carry real weight:

GitWhat you already know
Object databaseAn immutable content-addressed store — an inode table where the hash is the address
BlobFile contents with no name and no mode — an inode’s data blocks
TreeA directory: names → (mode, hash)
CommitA whole-tree snapshot plus a parent pointer — a node in a linked list
Branch / tagA pointer file naming a hash; one moves as you work, one doesn’t
HEADA pointer to a pointer
IndexA write buffer holding the next commit, flushed by git commit

Don’t memorize that table — Git-from-First-Principles derives every row of it with commands you run yourself. The reason it’s here is so you know what the destination looks like: once the store is in place, the porcelain stops being a pile of arbitrary verbs and becomes thin sugar over write some objects, move a pointer. Every command in the rest of this course is one of those two things.

The words, before they’re defined

You’ll hear these constantly. One line each, so nothing is unfamiliar the first time it appears — the real definitions come with the mechanisms.

TermRoughly
RepositoryThe object database plus its pointers — everything under .git/
Working treeThe actual files you edit, checked out from one commit
Index (staging area)The proposed next commit, assembled by git add
CommitAn immutable snapshot of the whole tree, with author, message, and parent
BranchA moving pointer to a commit; creating one costs 41 bytes
HEADWhere you are now — which branch you’re standing on
RemoteAnother clone you sync with, conventionally named origin
Clone / push / pullCopy a repo; send commits to a remote; fetch and integrate theirs
Merge / rebaseTwo ways to combine independent lines of work
WorktreeAn additional checkout of the same repository, on its own branch

Ten minutes to your own repo

Do this now. It’s a throwaway whose only job is to let you watch a repository being born from nothing — the course’s real exercises happen in the lab repo you’ll fork in the next section, so give this one its own name and directory.

Confirm what you have and tell git who you are — the name and email are stamped into every commit you make, permanently:

git --version                                    # anything 2.30 or newer is fine
git config --global user.name "A. Developer"
git config --global user.email adev@example.com
git config --global init.defaultBranch main

Then make the repository and put one commit in it:

mkdir git-scratch && cd git-scratch
git init
printf 'int main(void) { return 0; }\n' > main.c
git add main.c
git commit -m "feat: initial skeleton"

Now look at what you actually created, first through the porcelain and then through the plumbing:

git log --oneline
git cat-file -p HEAD

That second command is a plumbing command, and its output — a tree hash, an author line, a message — is the subject of the next page. If it looks like an unfamiliar record format rather than a git command, you’re in exactly the right frame of mind.

Worth knowing before you experiment: almost nothing you can do here is destructive. Committed work is effectively impossible to lose, for reasons Undoing-Things-in-Git makes precise. Commit early and often while you learn; the only work git can’t get back for you is work you never committed.

The lab repo

You just built a repo with one commit in it, which is the right way to see one born but a poor place to practise merging. So the rest of the course uses a second repo — a small C project called sensor-logger, whose history was constructed specifically so that the examples on later pages are things you run rather than read. Clone it somewhere other than inside git-scratch; the two are unrelated. It has a merge conflict waiting to happen, a branch that merges cleanly, a tag, and a .gitignore mistake already made for you.

Fork it to your own account rather than cloning it. A fork is yours: you get a remote you can actually push to, which the pull/push and stash exercises need, and you can wreck it without consequence.

gh repo fork unixtool1192/sensor-logger --clone
cd sensor-logger

If you’d rather not use the gh CLI, use the Fork button at github.com/unixtool1192/sensor-logger and then clone the copy that appears under your own account.

The repo’s COURSE.md lists the exercises page by page — keep it open alongside these notes. Apply one setting before you reach the merge page, because it changes what a conflict looks like, for the better:

git config --global merge.conflictStyle zdiff3

How to read this course

The docs are ordered, and each one names its prerequisites and hands off to the next. Four movements:

  1. The foundationGit-from-First-Principles. Everything else is a consequence of this page. Don’t skip it, and don’t skim it.
  2. Getting work out of your directoryRemotes-and-Tracking-Branches, Ignoring-Files-in-Git, Pull-Commit-Push-on-a-Shared-Repo, Using-git-stash-Safely. What a remote actually is, deciding what git watches before you start sharing, and then the daily loop. After these you can work on a shared repo without breaking anything.
  3. Combining and undoingMerging-Rebasing-and-Reading-History, Undoing-Things-in-Git. These are what make you unafraid, which matters more than fluency. The second one is the single most useful page in the series on the day something goes wrong.
  4. Working at scaleGit-Worktrees-and-Parallel-Agents. Several checkouts of one repository at once, which is the mechanism behind parallel coding agents.

If you read only through movement 2 you’ll be productive. If you stop before movement 3, you’ll still be nervous, and nervous is what makes people paste commands they don’t understand.

Next: Git-from-First-Principles — the object database, and why a commit is a snapshot rather than a diff.