How Git Works
A few ideas explain almost everything Git does: files move between three areas, a commit is a snapshot of the whole project, commits point back to their parents, and branches and HEAD are just labels on commits. Ten minutes here will make every later command feel logical instead of magical.
The three areas
Files in a repository live in three places at once:
working tree staging area repository (.git)
(files you edit) (the next commit, drafted) (saved commits)
edit files ──── git add ────▶ ──── git commit ───▶
◀─────────────── git restore ──────────────────
| Area | What it holds |
|---|---|
| Working tree | The actual files on disk that you open, edit and run. |
| Staging area (also called the index) | A draft of the next commit. You choose exactly which changes go into it. |
| Repository | The permanent history: every commit ever made, stored in .git. |
The staging area is what makes Git different from "save a backup copy". You might have edited five files but only want to commit two of them as one logical change, and the other three as another. You stage the two, commit, then stage the rest.
The states of a file
From Git's point of view, each file is in one of these states:
- Untracked — a new file Git has never been told about.
- Unmodified — tracked, and identical to the last commit.
- Modified — tracked, and changed in the working tree since the last commit.
- Staged — the change has been added to the staging area and will be in the next commit.
You can see them all at once in a single git status:
git status
On branch main Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: pancakes.txt Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: omelette.txt Untracked files: (use "git add <file>..." to include in what will be committed) porridge.txt
pancakes.txt is staged, omelette.txt is modified but not staged, and porridge.txt is untracked. Notice how Git even tells you the commands to move each file to another state.
Commits are snapshots
A commit is not a list of differences — it records the full contents of every tracked file at that moment, plus:
- the author's name and email, and the date;
- the commit message;
- a pointer to the parent commit — the one that came before.
Git stores this efficiently: a file that didn't change is stored once and shared between snapshots, and its database is compressed. Because each commit knows its parent, commits form a chain you can walk backwards through:
A ◀── B ◀── C ◀── D
▲
main
▲
HEAD
Hashes
Each commit is identified by a 40-character hash calculated from its contents (the snapshot, the message, the author, the date and the parent). Change anything — even one letter of a message — and you get a different hash. That makes history tamper-evident: if a commit's hash matches, so does everything before it. You'll usually see hashes shortened to the first seven characters, and Git accepts any unique prefix.
Branches and HEAD are labels
A branch is just a movable name that points to one commit. When you commit on a branch, Git creates the new commit with the current one as its parent, then moves the branch label forward to it.
HEAD means "where you are now". It normally points to a branch, which in turn points to a commit. When you switch branches, Git moves HEAD and updates your working tree to match that branch's snapshot.
E ◀── F ◀── feature
/
A ◀── B ◀── C ◀── D ◀── main ◀── HEAD
Here two branches share history up to C, then diverge. Creating a branch costs almost nothing — it's a tiny file containing a hash — which is why Git users branch freely. Lesson 22 covers this in practice.
Almost nothing is ever lost
Once something has been committed, it is very hard to lose: even commits that no branch points to any more are kept for weeks and can be found again (lesson 20). The dangerous moments are changes that were never committed or staged — Git can't restore what it never saw. Commit early and often.
Summary
| Idea | In one sentence |
|---|---|
| Three areas | Edit in the working tree, choose changes in the staging area, save them in the repository. |
| Commit | A snapshot of the whole project with a message, author, date and parent. |
| Hash | A commit's unique ID, calculated from its contents. |
| Branch | A movable label pointing at a commit. |
HEAD | Where you are now — usually the current branch. |
Try it yourself
In a test repository, make one file untracked, one modified and one staged at the same time, and check that git status shows all three sections.