How Git works: snapshots, not diffs
Git ends the shop_final_v2.py mess: every version of your project kept, any version recoverable, a note on each — all in one hidden folder, no server required. Three simulators show commits as full snapshots in a chain, the working-directory → stage → repository pipeline behind git add and git commit, and branches as nothing more than movable name tags.
mindmap — quick refresh
Prerequisites: Your toolbox — you'll need the terminal. Nothing else; Git predates and outlives every language you'll use. Time: ~45 minutes.
Three hard facts before anything else:
- Git was written by Linus Torvalds in 2005 to manage the source code of the Linux kernel — thousands of contributors, no central coordinator. Two decades later, essentially all of the world's source code lives in Git repositories. Learn this one tool and you can time-travel through every codebase you'll ever touch.
- A commit is a full snapshot of your project, not a list of changes. The "diffs" Git shows you are computed on demand by comparing two snapshots — they're never what's stored. Pro Git opens its first chapter with exactly this: Git thinks of its data as a stream of snapshots, and most confusion about Git comes from assuming it stores differences.
- Everything lives in one hidden
.gitfolder inside your project. No server, no account, no internet connection required. GitHub is optional and comes later; Git itself is just a program on your machine keeping snapshots of your files.
The problem Git solves
You've already lived it. shop.py works, but you want to try something risky — so you copy it to shop_backup.py first. Then shop_final.py. Then shop_final_v2.py, and after the demo, shop_final_v2_REAL.py. A week later: which one actually runs? Which had the delivery fix? What changed between v2 and REAL, and why? Copies-with-creative-names is version control — done by hand, badly, with no memory of intent.
Name what you actually want and the tool designs itself: every version kept (not just the ones you remembered to copy), any version recoverable (in one command, not by archaeology), and a note on each saying what changed and why. That's all version control is: a lab notebook for code. Git is the notebook everyone settled on.
Snapshots and the chain of commits
The unit of Git is the commit. A commit records a snapshot of every file in your project — not just the ones you touched — plus a message ("raise momo price"), the author's name and email, a timestamp, and one more thing that turns isolated snapshots into history: a pointer to the parent commit, the snapshot that came before it. Follow the parent pointers backwards and you walk through every version of your project ever recorded. That linked chain — later a graph, once branches appear — is your project's history.
Each commit gets an id like 7d20b4f (the short form of a longer 40-character string). The id isn't a counter — it's a hash of the commit's content: same content, same id, on any machine, forever. Two useful consequences, no cryptography required: identical files are stored once and reused across snapshots (so "every commit stores every file" costs far less disk than it sounds), and history can't be silently altered — change anything in a commit and its id changes too.
Watch the chain grow, and then watch the payoff — checking out an old commit brings the old files back, intact:
The lesson in the last step is the whole sales pitch: once a version is committed, it is permanently recoverable. You will never again keep a _backup copy out of fear.
The three places your files live
Between "I saved the file" and "it's in history" Git inserts one deliberate stop. Your files live in three places:
- The working directory — the real files you edit in VS Code. Just files; Git watches but doesn't act.
- The staging area (the "index") — a loading dock inside
.git.git add shop.pycopies the current version ofshop.pyonto it. - The repository — the chain of snapshots.
git committurns whatever is on the stage into a new snapshot and clears the dock.
Why the extra stop? So a commit can contain some of your changes. You fixed a bug and restyled the receipt in the same sitting; the stage lets you add just the bug-fix files, commit "fix delivery threshold", then stage and commit the rest as "restyle receipt" — two honest notebook entries instead of one mumbled "stuff". And between all three places, git status is the dashboard: it lists what's modified but unstaged, what's staged and ready, and what's untracked. When in doubt — and beginners should be in doubt often — run it.
Now the real thing — the daily loop you'll run for the rest of your career, on the momo shop from Python from zero. Open your terminal:
- Check Git is installed:
git --version
Expected (any 2.x version is fine):
git version 2.39.5
If not: macOS offers to install its command-line tools the first time you run git — accept and wait. Windows: download from https://git-scm.com/downloads and install with defaults. Linux: sudo apt install git.
- Introduce yourself — once per machine, never again. Git stamps every commit with an author, and this sets what it stamps (the third line makes new projects start on a branch named
main, matching what you'll see everywhere):
git config --global user.name "Anna Sharma"
git config --global user.email "anna@example.com"
git config --global init.defaultBranch main
Expected output: nothing — silence is how the terminal says "done".
- Give
shop.pya folder of its own. In yourprojectsfolder:
mkdir momo-shop
mv shop.py momo-shop
cd momo-shop
(Windows: move shop.py momo-shop.) Git manages folders, one project per folder — a repository is a folder with a .git inside.
- Turn the folder into a repository:
git init
Expected:
Initialized empty Git repository in /Users/anna/projects/momo-shop/.git/
That's the entire setup — one hidden folder appeared. No server was contacted.
- Ask the dashboard what it sees:
git status
Expected:
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
shop.py
nothing added to commit but untracked files present (use "git add" to track)
Git sees shop.py but has never been told to track it. Notice it also tells you the fix — status output always suggests the next command.
- Stage it, and look again:
git add shop.py
git status
Expected:
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: shop.py
- Commit — the
-mis the notebook note:
git commit -m "First working momo shop"
Expected (your hash and line count will differ):
[main (root-commit) a1f9c3e] First working momo shop
1 file changed, 27 insertions(+)
create mode 100644 shop.py
✓ Check: git status now says nothing to commit, working tree clean — all three places agree. Snapshot one exists.
- Change something. In VS Code, add a line to the menu dict in
shop.pyand save:
"coke": 90,
- Ask Git what changed — this is the on-demand diff from fact 2, computed right now by comparing your working file against the last snapshot:
git diff
Expected:
diff --git a/shop.py b/shop.py
--- a/shop.py
+++ b/shop.py
@@ -3,4 +3,5 @@
"fried momo": 180,
"jhol momo": 200,
"chai": 60,
+ "coke": 90,
}
Lines starting with + are new. Press q if the output opens in a pager.
- Stage and commit the change:
git add shop.py
git commit -m "Add coke to the menu"Expected:[main 7d20b4f] Add coke to the menu
1 file changed, 1 insertion(+)
- Read your notebook back:
git log --onelineExpected (newest first):7d20b4f (HEAD -> main) Add coke to the menu
a1f9c3e First working momo shop✓ **Check:** two commits, each with your note. That's the loop — *edit → status → add → commit* — and it's 90% of daily Git. `HEAD` is simply "where you are now"; it follows you around the chain.Branches: movable name tags
Here's the fact that makes branches click: on disk, the branch main is a 41-byte file — .git/refs/heads/main — containing one commit id and a newline. Nothing more. A branch isn't a copy of your code; it's a name tag pinned to one commit, and every time you commit while "on" that branch, the tag moves to the new snapshot.
That's why creating a branch is instant — git switch -c feature writes one 41-byte file, copies nothing, and works the same in a ten-file project or in Linux's tens of millions of lines. Switching branches is the working-directory trick from the first simulator: Git rewrites your real files to match the snapshot that branch points at. So a branch is a cheap parallel experiment: commit freely on feature while main stays pinned to the last version that worked. When the experiment succeeds, merge: Git creates one new commit with two parents — one on each chain — and the histories join.
One honest caveat: if both branches edited the same lines of the same file, Git can't know which version you want, so the merge stops and asks — that's a merge conflict. It sounds scarier than it is: Git writes both versions into the file between marker lines (<<<<<<<, =======, >>>>>>>), and they're just text. You open the file, keep the lines you want, delete the markers, and commit. That's the entire procedure.
GitHub is another computer holding the same snapshots
Everything so far happened in one .git folder on your machine — and that's genuinely all of Git. A remote like GitHub is simply another computer holding a copy of the same snapshot graph. git push sends your new commits to that copy; git pull fetches commits others added and merges them into yours. Same commits, same hashes, same chains — the content-hash ids are why copies on different machines can agree perfectly about history. That's the whole concept, and it's why GitHub is a service around Git rather than part of it. No setup steps here on purpose: you'll meet push and pull properly when you publish your first project, and by then the mental model — moving snapshots between two copies of the graph — will make the commands obvious.
What you can skip (for now)
git rebase— rewrites the chain to make history tidier before sharing; learn it when your team asks for it, not before.git cherry-pick— copies a single commit onto another branch; useful the day you fix a bug on the wrong branch.- Tags — permanent name tags (unlike branches, they don't move) for marking releases like
v1.0; matters when you ship versions to others. - Submodules — a repository embedded inside another; you'll know you need them, and you'll wish you didn't.
- Pull requests and hosting workflows — GitHub's review-before-merge ceremony; it matters the day you collaborate, and it's a feature of the host, not of Git.
Takeaways
- A commit is a full snapshot, not a diff — message, author, and a pointer to its parent. Diffs are computed on demand; the chain of parents is your history.
- Committed means recoverable, forever. One
.gitfolder holds every version;git checkout <hash>brings any of them back. Delete your_backupcopies. - Three places, two commands: working directory →
git add→ stage →git commit→ repository. The stage exists so one commit can tell one story — some of your changes, not all. git statusis the dashboard — run it before and after anything; it always names the next command.- A branch is a 41-byte name tag, so branching is instant and switching just rewrites your files to another snapshot. A merge is a commit with two parents; a conflict is just text markers you edit.
- GitHub is another computer with the same graph — push and pull move commits between copies. Git itself never needed the internet.
References
- Chacon, S., & Straub, B. (2014). Pro Git (2nd ed.). Apress. https://git-scm.com/book/en/v2
- Git Project. (n.d.). Git reference documentation. Software Freedom Conservancy. Retrieved August 22, 2026, from https://git-scm.com/docs
- Git Project. (n.d.). gitglossary — A Git glossary. Software Freedom Conservancy. Retrieved August 22, 2026, from https://git-scm.com/docs/gitglossary
- GitHub. (n.d.). About remote repositories. GitHub Docs. Retrieved August 22, 2026, from https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories