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:

TXT
  working tree          staging area             repository (.git)
 (files you edit)   (the next commit, drafted)   (saved commits)

   edit files  ──── git add ────▶            ──── git commit ───▶
               ◀─────────────── git restore ──────────────────
AreaWhat it holds
Working treeThe 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.
RepositoryThe 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
Output
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:

TXT
  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.

TXT
              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

IdeaIn one sentence
Three areasEdit in the working tree, choose changes in the staging area, save them in the repository.
CommitA snapshot of the whole project with a message, author, date and parent.
HashA commit's unique ID, calculated from its contents.
BranchA movable label pointing at a commit.
HEADWhere 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.