Module 08 · Tooling · All tracks
Version Control · Git
Most Git pain comes from memorising commands without the model underneath. Build the model and the commands become obvious — including the ones that scare people.
By the end you'll be able to explain, with conviction:
- The three-area model that makes every Git command make sense.
- Merge vs rebase — and when each is the right call.
- How to undo things safely, and why
revertbeatsreseton shared branches.
1The Git mental model — three areas
Almost every Git command just moves changes between three places. See the places, and the commands stop being magic.
Your work lives in three areas: the working directory (the files you edit), the staging area / index (a holding pen for the exact changes you're about to commit), and the repository (the permanent, hashed history of commits). The staging area is the part people skip — and it's the whole point. It lets you craft a commit deliberately, choosing which changes go together rather than dumping everything at once.
add stages, commit records, push shares. Every command is just moving changes across this line.A commit is a snapshot of the whole tree plus a pointer to its parent — which is why history is a graph, and why branches are astonishingly cheap. A branch is just a movable label pointing at a commit. Internalise that one sentence and rebasing, fast-forwarding, and detached HEAD all stop being mysterious.
Why it matters
"A branch is a pointer to a commit; HEAD is a pointer to where you are" is the single most clarifying idea in Git. Most "how do I undo this" questions dissolve once you think in terms of moving pointers.
2Branching strategies — GitFlow vs trunk-based
A branching strategy is a team agreement about how work flows into the main line. Two dominate, and they trade off in opposite directions.
GitFlow uses long-lived branches: main, develop, plus feature/*, release/*, and hotfix/*. It's structured and supports scheduled, versioned releases — but the long-lived branches drift apart and merging gets painful. It fits software with explicit release versions (desktop apps, libraries).
Trunk-based development keeps everyone on main with very short-lived branches merged daily, guarded by CI and feature flags to hide unfinished work. It minimises merge pain and is the backbone of continuous delivery — which is why most web teams have moved to it. The cost is discipline: it demands strong automated tests because half-built code is constantly hitting main.
Interview angle
"GitFlow suits versioned, scheduled releases; trunk-based suits continuous delivery. The core tradeoff is integration frequency — short-lived branches mean small, frequent merges and fewer conflicts, but they only work if your test suite can be trusted."
3Merge vs rebase
Both combine work from two branches; they differ in what they do to history. Merge creates a new "merge commit" that ties the two histories together — it's non-destructive and preserves exactly what happened, at the cost of a branchy graph. Rebase replays your commits one by one on top of the target branch, producing a clean linear history — at the cost of rewriting those commits (they get new hashes).
The decision rule that keeps you safe: rebase to tidy your own local, unpushed work; merge to integrate shared work. The golden rule of rebasing — never rebase commits that others have already pulled — follows directly, because rewriting shared history forces everyone else into a painful reconciliation.
Common trap
Rebasing a shared branch and force-pushing is how you ruin a teammate's afternoon. Rewriting history is fine on a private branch and dangerous on a public one — that distinction is the whole answer.
4Resolving conflicts calmly
A conflict isn't an error — it's Git honestly admitting it can't decide which of two changes to the same lines should win, and handing you the choice. Panicking is the only real mistake.
Git marks the contested region with <<<<<<<, =======, and >>>>>>>. Your job is to read both sides, decide what the code should be (often a blend, not one side wholesale), delete the markers, then stage the resolved file and continue. The calm habit is to resolve in small, frequent integrations rather than one giant end-of-feature merge — the smaller the divergence, the smaller the conflict.
Interview angle
"A conflict just means two changes touched the same lines and Git won't guess — I read both sides, decide the correct result, and remove the markers. The real fix is upstream: integrate often so divergences stay small."
5Undoing safely — reset vs revert, stash, cherry-pick
The undo commands separate people who fear Git from people who don't.
git revert— creates a new commit that undoes a previous one. History is preserved, nothing is rewritten. The safe choice on shared branches.git reset— moves the branch pointer backwards.--softkeeps your changes staged,--mixed(default) unstages them,--harddiscards them entirely. It rewrites history, so reserve it for local, unpushed commits.git stash— shelves uncommitted changes so you can switch context (e.g. an urgent fix), thenstash popbrings them back.git cherry-pick— copies a single commit from one branch onto another. Ideal for pulling one specific fix into a release branch without merging everything.
Common trap
Reaching for reset --hard on a branch others share rewrites public history and loses work. On anything shared, revert is the answer — it undoes the effect while keeping an honest record.
6The PR workflow
The pull request is where version control meets collaboration. The standard loop: branch off main → commit focused work → push → open a PR → automated CI runs (build, tests, linters) → teammates review → address feedback → squash-or-merge once approved and green. Branch protection rules enforce that nothing reaches main without passing checks and review.
The senior habits mirror Module 02's review etiquette: keep the PR small and single-purpose, write a description that explains the why and how to verify, and treat a red CI run as a hard stop, not a suggestion. The PR is also a permanent record of why a change was made — worth writing for the engineer who reads it in a year.
Go deeper
"Squash and merge" collapses a messy feature branch into one clean commit on main — tidy history, but you lose the granular steps. "Merge commit" keeps every commit. Teams pick one for consistency; knowing the tradeoff (clean history vs full detail) is the interview-ready point.
7Tags, releases, and semver
A tag is a permanent, named pointer to a specific commit — unlike a branch, it doesn't move. Tags mark releases so you can always return to exactly what shipped. Semantic Versioning gives those tags meaning with MAJOR.MINOR.PATCH:
- MAJOR — breaking changes; existing users must adapt.
- MINOR — new, backward-compatible features.
- PATCH — backward-compatible bug fixes.
The value of semver is that the version number becomes a promise: a consumer reading 2.4.1 → 2.5.0 knows new features arrived but nothing broke, while 2.x → 3.0 warns them to read the migration notes. It turns upgrades from a gamble into an informed decision.
Interview angle
"A tag pins a release to an exact commit, and semver makes the number a promise — patch is safe, minor adds features compatibly, major signals a breaking change. It lets consumers upgrade with eyes open."
Recap — what you can now teach
- Git has three areas — working dir, staging, repo — and a branch is just a pointer to a commit.
- GitFlow for versioned releases, trunk-based for continuous delivery; the tradeoff is integration frequency.
- Rebase your local work, merge shared work — never rebase commits others have pulled.
- A conflict is Git asking you to choose; the real fix is integrating often.
reverton shared branches,resetonly locally; stash to shelve, cherry-pick to copy one commit.- Tags pin releases; semver makes the version a compatibility promise.
Self-check
Say each answer out loud before revealing it.
What is a Git branch, really?
Just a movable label (pointer) to a specific commit — which is why branches are so cheap to create.
When do you rebase, and when do you merge?
Rebase to tidy your own local, unpushed commits into a linear history; merge to integrate shared work. Never rebase commits others have already pulled.
Why prefer revert over reset on a shared branch?
revert adds a new commit that undoes the change without rewriting history; reset rewrites history, which breaks everyone who already pulled it.
What does the staging area give you that committing directly wouldn't?
Deliberate control over exactly which changes go into a commit, so you can craft focused, logical commits instead of dumping everything.
What does a MINOR version bump promise under semver?
New functionality that is backward-compatible — nothing existing should break.