A ledger of snapshots
Most people meet Git as a list of incantations to memorise — add, commit, push, and a cold sweat when something goes wrong. That is the wrong way in, and it is why Git feels arbitrary. Learn it instead as one small model, and the commands stop being spells and start being obvious. Here is the model: Git is a ledger of snapshots. Every time you commit, Git writes down the complete state of your tracked files at that instant and stamps it with a unique identifier and a parent. It is not storing a list of changes; it is storing a chain of photographs, each pointing back to the one before.1
- Commit
- A snapshot of your tracked files at a moment, with a unique id, an author, a message, and a pointer to its parent commit. The history is the chain of these snapshots, newest pointing back to oldest.
This one idea dissolves most confusion. A branch, as §3 shows, is just a sticky note on one of these snapshots. Undoing work, as §5 shows, is just moving a pointer or writing a new snapshot that reverses an old one. "Merge" and "rebase" are two ways of joining chains. None of it is arbitrary once you see that everything Git does is either take a snapshot or move a pointer among snapshots. For a cloud engineer this is not optional trivia: from Phase Three on, your infrastructure, your pipelines, and your policies all live as text in a Git repository, and the day you cannot confidently undo a bad change is the day the repository owns you rather than the reverse.
The three areas
Between the file you are editing and the permanent ledger sit two more places, and knowing which of the three a file is in explains every confusing thing Git will ever tell you.
- Working directory
- The files as they exist on disk right now, including edits you have not told Git about. This is where you actually work.
- Staging area
- The set of changes you have marked to go into the next commit, with git add. A deliberate holding bay — you choose exactly what the next snapshot contains.
- Committed history
- The ledger. Once you git commit, the staged changes become a permanent snapshot with an id.
The flow is always the same: edit in the working directory, git add to move a change into the staging area, git commit to write it into history. The staging area is the part beginners resent and professionals rely on: it lets you commit this fix without also committing the half-finished experiment sitting in the same file. When git status talks about "changes not staged" versus "changes to be committed," it is simply telling you which of these three areas your edits are in. Read status as a map of the three areas and it stops being noise.
A branch is a pointer
The single most demystifying fact in Git: a branch is not a copy of your files, not a folder, not a heavy thing. A branch is a pointer to one commit — a movable label. When you commit on a branch, the label slides forward to the new snapshot. That is the entire mechanism, and Figure 10 shows it moving.
Because a branch is only a pointer, creating one is instant and cheap — git switch -c feature writes a new label at your current commit and moves you onto it.2 This is why the whole industry works in branches: they cost almost nothing, and they let many people build in parallel on the same ledger without treading on each other until they choose to combine their chains.
Merge or rebase — joining two chains
When a branch has moved forward and you want its work in another branch, you join the chains, and Git offers two ways. The difference is honesty versus tidiness, and there is a one-line rule that keeps you out of trouble.
| Merge | Rebase | |
|---|---|---|
| What it does | Ties the two chains together with a new merge commit | Replays your commits on top of the other branch as new snapshots |
| History | Truthful — shows the branch really happened | Tidy — a single straight line, as if written in order |
| Rewrites commits? | No | Yes — new ids for the replayed commits |
Never rebase history other people already have.
That pull-quote is the rule of thumb, and it flows straight from the model. Rebase rewrites commits — it makes new snapshots with new ids to replace the old ones. If those old commits exist only on your machine, rewriting them is harmless and gives you a clean, linear history. But if you have already shared them — pushed to a branch others pulled — rewriting them means your history and theirs now disagree about which snapshots are real, and combining them becomes a mess. So: rebase your own local, unshared work to tidy it before sharing; merge when you are combining work that has already been published. Tidy in private, truthful in public.
Undoing things without fear
Most Git terror is really the fear of undo — of making it worse. The model makes undo calm, because there are only three tools and each does an obvious thing. The trick is knowing which is safe on history other people share.
- git restore
- Throws away uncommitted changes in the working directory, or unstages a staged change. It only ever touches work that is not yet committed — the gentlest undo.
- git revert
- Creates a new commit that undoes an earlier one. History grows forward; nothing is rewritten. This is the safe way to undo something already shared.
- git reset
- Moves the branch pointer backward to an earlier commit. Powerful and local-friendly — but on shared history it rewrites what others have, and --hard also discards your working changes.
Here is the discipline in one breath. On work that lives only on your machine, reset freely — it is just sliding your own pointer. On work you have shared with others, undo with revert, which adds a new snapshot everyone can take cleanly, and never with reset, which rewrites the shared ledger and forces the ugly recovery of the next class's situations.3 And treat git reset --hard with real caution: it moves the pointer and throws away uncommitted work in one step, which is the single most common way people lose an afternoon. Undo is not dangerous; using reset where you needed revert is.
The repository that will hold everything
Phase Three opens with a small, consequential act: Campux's infrastructure gets a home in version control. You create campux-platform, the repository that will hold the tenant design from Class Seven, the VNet from Class Ten, and — over the next eight classes — the Bicep, the Terraform, and the pipelines that deploy them. From here on, nothing Campux runs is configured by clicking; it is described as text, committed to this ledger, and reviewed before it ships.
The first commit is almost empty — a README and a licence — and that is the point. It establishes the ledger that every later class writes into, with a history you can read, blame, and safely undo. When something breaks in Class Twenty-Three's pipeline, the answer will be in this repository's history, because by then the repository is Campux's infrastructure. Get comfortable with the three areas and the three undos now, on an empty repo, so that when the repo matters you are not learning Git under pressure.
The official module, and a CAMPUX overview
Introduction to version control with Git
learn.microsoft.com/training/modules/intro-to-git/
Practice: Source control in Visual Studio Code (docs)
code.visualstudio.com/docs/sourcecontrol/overview
A short walkthrough of the ledger model, the three areas, and a branch pointer moving will live here. Video to be added.
Feel the three areas
Git is terminal-first — there is no portal here, and that is the point of learning it directly. You will make a repository, then watch a single change travel through the three areas into the ledger.
In a terminal, make a folder and initialise a repository:
mkdir campux-platform && cd campux-platform git init git config user.name "You" && git config user.email "you@campux.co"
What just happened: git init created an empty ledger in a hidden .git folder. The repository is just that folder plus your files.Create a file, then ask Git where it stands:
echo "# Campux Platform" > README.md git status
On screen: README.md shows as untracked — it exists in the working directory but Git is not yet watching it. Read status as "which of the three areas is this in?"Stage the change, and read status again:
git add README.md git status
On screen: it now reads changes to be committed — the file has moved into the staging area, the deliberate holding bay for the next snapshot.Commit, then view the ledger:
git commit -m "First commit: README" git log --oneline
The lesson: the change is now a permanent snapshot with a short id. You just walked one edit through working → staged → committed — the whole flow, once.
Watch the pointer move
Now make Figure 10 concrete: branch, commit, and see the label slide forward — then join the chains with a merge.
Create a branch and move onto it in one command:
git switch -c add-license
What just happened: Git wrote a new pointer named add-license at your current commit and switched you to it. Nearly free — it is one label, not a copy of your files.Make a commit on the branch, then look at the graph:
echo "MIT" > LICENSE git add LICENSE && git commit -m "Add licence" git log --oneline --graph --all
On screen: add-license now points one commit ahead of main — the pointer slid forward, exactly as the figure showed, while main stayed put.Switch back to main and confirm the file is not there:
git switch main ls
On screen: LICENSE is gone from the working directory, because main's pointer never moved to the commit that added it. Switching branches moves you to a different snapshot.Merge the branch into main and look again:
git merge add-license git log --oneline --graph --all
The lesson: main now includes the licence commit — the two chains are joined. Because main had no new commits of its own, Git could simply fast-forward its pointer; when both sides have moved, it writes a merge commit instead.
The three undos, on a repo you can break
Practise the three undos here, on local history nobody shares, so the muscle is built before the stakes are real. Notice which tool rewrites history and which grows it.
restore — throw away an uncommitted edit:
echo "oops" >> README.md git restore README.md git status
On screen: the working directory is clean again; the stray line is gone. restore only ever touches uncommitted work — the gentlest undo, impossible to hurt anyone with.revert — undo a committed change by adding a new commit:
git revert --no-edit HEAD git log --oneline
On screen: a new commit appears that reverses the last one; the original stays in history. Because it only grows the ledger forward, this is the safe undo for anything already shared.reset — move the branch pointer backward (local only):
git reset --hard HEAD~1 git log --oneline
The lesson: the branch pointer jumped back and the last commit is gone from this branch — and --hard also discarded working changes in the same move. On local history this is fine and fast; on shared history it rewrites what others have, which is the disaster of this class's drills. Reset backward in private; revert forward in public.You broke and fixed a repo without fear — delete the folder when done.
From blame meeting to one-line answer
A config change from Tuesday broke something, and the room is guessing. You are not guessing — you read the history, find the exact commit and its diff, and revert precisely that. Version control turns "who touched this and when?" from a blame meeting into a one-line answer, and turns a frightening rollback into a calm one.
Examination
Four drills, then two situations. The situations have no marking scheme — write your answer before you reveal the reasoning, or the exercise is worthless. Nothing is stored; this is between you and the page.
A. "Not staged" means the edit lives in the working directory and has not been moved to the staging area with git add. B would read "changes to be committed"; C requires a commit; D requires a push. Reading status as a map of the three areas is the whole skill — the words are precise once you know which area each phrase names.
C. A branch is a lightweight, movable label pointing at one commit; committing slides it forward. A and B are the heavy mental models that make Git feel expensive and confusing — they are wrong, which is exactly why branching is instant and cheap. Once "branch = pointer" is reflex, merge, rebase and reset all become questions of which pointer points where.
Revert grows history forward, reset moves the pointer, and rebase rewrites into new commits. The two false statements matter: restore only touches uncommitted work — it cannot rewrite shared commits — and Git commits are snapshots, not stored diffs (it shows you diffs, but it stores states). Believing "commits are diffs" is the misconception that makes reset and rebase feel unpredictable; they are perfectly predictable once you hold the snapshot model.
SHARED BRANCH cleanup — as run
1. git switch main # a branch several people share
2. git pull # get everyone's latest
3. git reset --hard HEAD~5 # "remove the messy commits"
4. git push --force # push the rewritten history
Line three. reset --hard HEAD~5 on a shared branch rewrites history everyone else already has — it discards five commits from main's chain and moves the pointer back — and the force-push in line four then imposes that rewritten history on the remote. Lines one and two are harmless.
Consider the consequence. Every teammate's clone now disagrees with the remote about which commits are real; their next pull conflicts or, worse, they force their own history back and work is lost in the crossfire. The correct tool to remove shared commits is git revert, which undoes them by adding new commits everyone can pull cleanly. Reset rewrites; revert grows. On anything shared, you grow.
The commits are almost certainly not gone — calm first. A force-push moved the remote pointer; it did not erase the snapshots. They still exist in someone's clone and in Git's reflog. The panic comes from thinking "vanished from the remote" means "destroyed," and the model says otherwise: history is snapshots, and a pointer moved off them, not through them.
Recover from a clone that still has the work. The colleague's local repository still points at their morning commits. From there — or from the reflog on the machine that has them — you identify those commit ids and restore main to include them, then push the corrected history. The exact commands matter less than the principle: find a copy that still references the lost snapshots and move the pointer back to them.
Then fix the cause, kindly. The lesson is not "punish the junior" — it is that --force onto a shared branch is the loaded gun, and the fix is branch protection (next class) so the platform refuses it. Blameless: the tooling should have made this hard, and by Class Eighteen it will. Recover the work, restore the pointer, then remove the foot-gun.
Reject "always," keep the useful half. Rebase genuinely does make a cleaner, linear history — that part of the advice is true. The danger is the absolutism, because rebase rewrites commits, and rewriting commits other people already have is how you create the mess from Drill 04 and Situation 01.
Give the rule that flows from the model. Rebase your own, unshared commits to tidy them before you open the pull request — new ids on work only you have is harmless and pleasant. Once the branch is shared, or when integrating it into main, merge — it is truthful and rewrites nothing others depend on. Tidy in private, truthful in public.
Close on the team dimension. Whether to prefer a rebase-then-merge or a straight merge is partly a team convention — Class Nineteen settles the branching strategy — so the honest answer is "clean up your own history with rebase, integrate shared history with merge, and follow whatever the team agreed for the final step." Never "always rebase," because "always" is where the shared-history damage lives.
Five things worth carrying out of this class
- Git is a ledger of snapshots, not diffs. Everything it does is take a snapshot or move a pointer among snapshots.
- Three areas: working directory, staging area, committed history. Read git status as a map of which area your change is in.
- A branch is a movable pointer to one commit. Committing slides it forward, which is why branching is instant and cheap.
- Merge is truthful and rewrites nothing; rebase is tidy but rewrites commits. Never rebase history others already have.
- restore for uncommitted work, revert to undo shared history safely, reset to move your own local pointer. Reset backward in private, revert forward in public.
- Under the hood Git stores each commit as a full tree of the project's state, de-duplicated so unchanged files are not stored twice — so "snapshot, not diff" is literally true while remaining efficient. Git computes and shows you diffs on demand, which is why people assume it stores them; it does not. Holding the snapshot model, not the diff model, is what makes reset and rebase predictable rather than frightening. ↩
- Commands in Git have accumulated synonyms over the years — git switch and git restore are the newer, clearer split of what git checkout used to do alone. You will see checkout everywhere in older material; it still works. Prefer the newer verbs when you can — they name the intent — but do not be thrown when a tutorial uses the old one. ↩
- "Blameless" is a real practice, not politeness: the aim after an incident is to fix the system that allowed it, not the person who tripped the wire. The force-push disaster is a tooling failure — the shared branch should have been protected — far more than a personal one. Class Eighteen adds exactly that protection, and Class Thirty-Five makes the blameless postmortem a formal step. ↩