Three areas
Version control keeps every saved version of a project so you can look back. git init turns a folder into a Git repository: it adds a hidden .git folder, where Git will store every version you save.
The working directory is the files you see and edit. You write hello.py here. git status calls it untracked: Git can see the file but is not keeping any version of it yet.
git add copies the file into the staging area, a waiting room for your next save. Only what is staged goes into the next commit, so you choose exactly which changes to save together.
Commits
git commit saves everything in the staging area as one commit: a snapshot of the whole project, plus your name, the time and a short message saying why. The commit lands in the repository.
Git names each commit by a hash: 40 hexadecimal characters (digits 0-9 and letters a-f) worked out from everything in it. Ours starts 6ad1b38, and that short form is usually enough. Change a single byte and the hash comes out completely different.
You change hello.py to ask for a name. git status now lists it as modified, because it differs from the staged copy. git diff shows exactly which lines changed: - marks a removed line, + an added one.
git add and git commit again make a second commit. Inside it, Git stores the hash of the commit before it, its parent. The arrows point back in time, so from the newest commit Git can walk the whole history.
Ignore and look back
Some files should never be saved: .env files with passwords, or the __pycache__ folders Python makes. List them in a file named .gitignore. git status stops showing them and git add . skips them.
git log lists the commits, newest first, each with its hash, author, date and message. git log --oneline prints one short line per commit, handy for finding the hash of an older version.
git checkout 6ad1b38 copies that commit's snapshot back into your working directory, so hello.py is the first version again. Nothing is lost: the newer commit is still there, and git switch - takes you back to it.
Try it in a terminal
;; step 1/10 · git init $ mkdir hello && cd hello $ git init Initialized empty Git repository in /home/sam/hello/.git/
Git's own name for the staging area is the index. After a commit it still holds the committed files, so when all three areas match, git status says: nothing to commit, working tree clean. The hashes in this figure are real: they are the SHA-1 of the Git objects for this exact file, author (Sam), time (5 January 2026) and message. Any change to these gives different hashes.