Every programmer has a folder somewhere with files called site_final.html, site_final2.html and site_final_REALLY.html. Git is the tool that makes that mess unnecessary. This page explains the three places your work lives in Git, the four commands you'll type every day, and why a "branch" is much simpler than it sounds. Every command and output shown here was run with real Git.

What problem does version control solve?

When you write code you constantly change things, and sometimes a change breaks something that worked yesterday. Without help, your options are to remember what you changed (you won't) or to keep copies of the whole folder (you'll lose track of which copy is which). Things get worse when two people edit the same project: who has the latest version, and how do you combine their changes?

A version control system is a program that records the history of a folder for you. Git is by far the most used one today; Linus Torvalds wrote it in 2005 to manage the Linux kernel. With Git you can:

A Git snapshot is called a commit. The full collection of commits, plus some bookkeeping, is called a repository (or "repo"). Git keeps the repository in a hidden folder named .git inside your project folder. You turn any folder into a repository with one command:

$ cd recipes
$ git init
Initialized empty Git repository in /home/ada/recipes/.git/

Before your first commit, tell Git who you are, once per computer. It stamps every commit with this: git config --global user.name "Ada Lovelace" and git config --global user.email "[email protected]".

The three places your work lives

This is the idea that makes Git click. At any moment a file can exist in three places:

Think of a group photo. Your files are people milling about the room (working directory). git add is calling specific people up to stand in front of the camera (staging). git commit is pressing the shutter: the photo captures exactly who was standing there, at that moment, and goes into the album (repository). Someone who changes their shirt after the photo is taken doesn't change the photo.

Try it. Edit files, add them, commit, and run git status as often as you like. The terminal underneath shows the same output real Git prints (only the commit IDs will differ on your machine).

Three-zone simulator · a fresh repo with three files

Working directory your files

Staging area next commit

Repository history

A few things worth noticing as you play:

Reading what Git tells you

git status is the command you'll run most. It never changes anything; it just reports. Here's real output after creating one file in a new repo:

$ git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	recipes.md

nothing added to commit but untracked files present (use "git add" to track)

Git even tells you the next step in brackets. After git add recipes.md the file moves to "Changes to be committed", and then:

$ git commit -m "Add recipe list"
[main (root-commit) 2048aea] Add recipe list
 1 file changed, 1 insertion(+)
 create mode 100644 recipes.md

The -m gives the commit message on the spot; without it Git opens a text editor for you to write one. root-commit means this is the very first commit. And 2048aea is the start of the commit's ID, called a hash: a 40-character code Git calculates from the commit's contents. It's how you refer to a specific commit later. git log lists the history, newest first:

$ git log
commit 2048aeac48fe1c4bca5d6821ae0b67aaf65fdb62 (HEAD -> main)
Author: Ada <[email protected]>
Date:   Thu Oct 1 14:27:10 2026 +0200

    Add recipe list

$ git log --oneline
2048aea (HEAD -> main) Add recipe list

Why bother with a staging area? Because it lets you choose what goes into each commit. If you fixed a typo and started a new feature, you can git add just the typo fix, commit it with the message "Fix typo", then commit the feature separately. Small commits with clear messages make the history readable, and make it easy to undo one change without undoing the other. When you do want everything, git add . stages all changes in the current folder.

Branches are just movable pointers

Each commit remembers which commit came right before it, its parent. That chain of parents is your history. A branch is nothing more than a name pointing at one commit. Really: the main branch is a tiny file inside .git (at .git/refs/heads/main) containing a single commit hash.

What makes a branch useful is one rule: when you commit, the branch you're on moves forward to the new commit. Other branches stay where they are. Git knows which branch you're "on" through a special pointer called HEAD. That's what HEAD -> main in the log means.

To start a new branch and move onto it, use git switch -c (the -c means "create"). To move between existing branches, use git switch without it:

$ git switch -c dessert
Switched to a new branch 'dessert'
$ git switch main
Switched to branch 'main'

When you switch, Git rewrites the files in your working directory to match the commit that branch points at. Files added on dessert disappear from your folder when you switch to main, and come back when you switch again. Nothing is lost; it's all in the repository. (Older tutorials use git checkout for this. git switch arrived in Git 2.23, in 2019, as a clearer name for the same job.)

Grow a history below. Commit a few times, create a branch, commit on it, switch back, commit on main, then merge. Newest commits are at the top, like git log.

Commit graph · branches as pointers

Merging: bringing work back together

git merge takes another branch's work and brings it into the branch you're on. You run it from the branch that should receive the changes, so to bring dessert into main you first git switch main, then git merge dessert. Depending on the shape of the history, one of three things happens:

Here's the real history from the recipe example after a merge commit, drawn by Git itself:

$ git log --oneline --graph --all
*   17798c9 Merge branch 'dessert'
|\
| * d9a8011 Add cake
* | 1fb1f4f Add pancakes
|/
* 2048aea Add recipe list

Sometimes both branches change the same lines of the same file. Git can't guess which version you want, so it stops with a merge conflict, marks the clashing lines in the file, and asks you to fix them, then git add and git commit. It sounds scary, but it's just Git asking a question it can't answer alone.

After a merge, the merged branch still exists and still points where it did. If you're done with it you can delete it with git branch -d dessert; that removes only the name, never the commits, which are now part of main's history.

Check yourself

You edit style.css, then run git commit -m "Fix colors" without running git add. What happens?

Git only commits what's in the staging area, and nothing is staged. It prints the status, ending with no changes added to commit. Run git add style.css first.

You run git add index.html, edit index.html again, then git commit. Which version is committed?

git add copies the file as it is right then into the staging area. The later edit is only in your working directory until you add it again.

You're on main. You run git switch -c idea and commit twice. Where does main point now?

Only the branch you're on moves when you commit. main is a pointer that stays put until you commit on it or merge into it.

Nobody has committed on main since idea branched off it. On main you run git merge idea. What happens?

main's commit is already an ancestor of idea's, so there's nothing to combine. Git just moves the pointer forward and prints Fast-forward.

The short version

When in doubt, run git status. It's free, it changes nothing, and it usually tells you what to do next.