HomeSource & deliveryGit & GitHub
Day 9 · 10 · 11 — the course, rebuilt as a workshop

Git & GitHub

Everything Day 9–11 teaches about version control — rewritten from the raw transcripts with nothing of substance dropped, then re-ordered into the way you actually learn a tool: understand it, do it, get quizzed on it, then survive it in production.

① Theory② Lab③ Interview Q&A④ Scenario drills
Day 9 · Git & GitHub — What is version control? Day 10 · Git Branching Strategy (real-world) Day 11 · Git Interview Q&A and Commands
⚡ The whole of Git in one mental model

A Version Control System (VCS) exists to solve exactly two problems — sharing code between many developers, and versioning it so you can return to any earlier state. Git is the VCS that won because it is distributed: every clone holds the entire history, so there is no central server to fail. And its daily loop moves your work through four places:

Working directoryyour files on disk Staging areathe "index" Local repo.git — your commits RemoteGitHub / GitLab git add git commit git push git clone / git pull · git checkout (remote → local → working)
The four areas. add → commit → push moves work rightward; clone / pull / checkout brings it back left.

Keep four facts and you can reason about almost anything: (1) .git is the brain — delete it and it's not a repo; (2) a branch is just a movable label on a commit; (3) a fork is your own full copy, a clone is a download; (4) merge and rebase both integrate branches — rebase just keeps history linear.

01
Phase 1 · Theory

Why version control exists, and how Git models it

First why any of this exists at all, then the specific concepts — centralized vs distributed, Git vs GitHub, branches, and the ways to integrate them — that everything else in this chapter builds on.

T1

Why version control exists

Before Git, teams solved "many people editing the same code" with folders named final, final_v2, and final_v2_ACTUALLY_final — version control is the real answer to that problem.

Any team writing software runs into two problems the moment more than one person touches the code: sharing — how do multiple developers work on the same codebase without overwriting each other — and versioning — how do you get back to an earlier working state when a change breaks something. A version control system solves both: it tracks every change as a discrete, named snapshot (a commit), lets multiple people work in parallel without stepping on each other, and lets you rewind to any point in that history on demand.

T2

Centralized vs distributed + fork

Git's defining decision — every clone is a full copy of history — is what makes it fundamentally different from the generation of tools before it.

Older systems like SVN were centralized: one central server held the true history, and every developer's machine held only the current checked-out files, with almost no history of its own. If that central server went down, nobody could commit, branch, or look at history — the whole team was blocked. Git is distributed: every developer's clone contains the entire project history, not just the latest snapshot, so you can commit, branch, and inspect history completely offline, and any clone can, in principle, restore the whole project if the original server is lost.

A fork takes this further, specifically for collaborating with people who don't have direct write access to a repository — it creates your own full, independent copy of someone else's repository, under your own account, which you can change freely before proposing your changes back via a pull request.

T3

Git vs GitHub

One of the most common early confusions is treating these as the same thing.

Git is the version control tool itself — a program that runs on your machine, tracking commits, branches, and history, entirely independent of the internet. GitHub is a company's hosting service for Git repositories — it adds a remote place to store and share them, plus collaboration features Git itself doesn't provide: pull requests, code review, issues, and CI integration. GitLab and Bitbucket are direct competitors offering the same kind of hosting around the same underlying Git. You can use Git with no GitHub account at all, purely locally — GitHub is where teams choose to host and collaborate on top of it.

T4

Branches & why we branch

A branch is one of the cheapest, most powerful ideas in Git — and also one of the most conceptually misunderstood.

A branch is not a copy of the code — it's simply a movable, named pointer to a specific commit. Creating a branch is instantaneous and nearly free precisely because nothing is being copied; Git just adds a new label. You branch so that work in progress — a new feature, an experiment, a bug fix — can happen in isolation from the stable, shared line of history (commonly main), so nobody else is affected until that work is deliberately merged back in. This is what lets many developers work in parallel on the same repository without constantly blocking each other.

main feature branch — just a label on this line of commits merged back into main
Cheap by design. A branch costs nothing to create because it's a pointer, not a copy — work happens in isolation until it's deliberately merged back into the shared line.
T5

Branching strategy

Knowing what a branch is isn't the same as knowing how a real team should actually use them — that's a branching strategy.

A common real-world shape: main (or master) always reflects what's actually in production and is protected from direct pushes; a develop branch (in some strategies) integrates finished features before a release; short-lived feature branches branch off for each piece of work and merge back via a reviewed pull request; and hotfix branches branch directly off production for urgent fixes that can't wait for the normal cycle. The specific strategy (Git Flow, trunk-based development, GitHub Flow) varies by team, but the underlying goal is always the same: keep the branch that deploys to production stable, and give in-progress work a safe, isolated place to live until it's ready and reviewed.

T6

Integrating work: cherry-pick / merge / rebase

Once work happens on separate branches, Git gives you three distinct ways to bring it back together — each with a different shape of history as the result.

A merge combines two branches' histories, creating a new "merge commit" that has two parents — it preserves exactly what happened and when, at the cost of a history that can look tangled with many branches merging in and out. A rebase takes your branch's commits and replays them on top of the latest version of another branch, producing a clean, linear history as if you'd started your work later — at the cost of rewriting commit history, which is unsafe on any branch other people are also working from. A cherry-pick takes one specific commit from anywhere and applies just that commit onto your current branch — useful when you need a single fix from one branch without pulling in everything else that branch has.

T7

Clone vs fork

These two terms get confused constantly, and the distinction is really about ownership and access, not mechanics.

Clone downloads a copy of an existing repository (its full history included) onto your machine — you can clone any repository you have read access to, and it stays linked to the same remote you cloned it from. Fork is a GitHub-level (not Git-level) action: it creates an entirely new, independent copy of a repository under your own account, which you then clone to actually work on locally. You fork when you don't have write access to the original repository and want to propose changes back via a pull request; you simply clone when you already have direct write access and just need a local working copy.

02
Phase 2 · Lab

Hands-on — from first commit to a full remote workflow

Install and configure Git, make your first commit, branch and merge for real (including a genuine conflict), rebase and cherry-pick, then work through the remote side — clone, fork, push, pull request — and finish with stash and .gitignore.

L1

Install and first-time configuration

Install Git with your platform's package manager, then set your identity — this is what gets attached to every commit you make, and getting it right before your first commit avoids a messy history of commits under the wrong name:

bashbrew install git                              # Mac; apt/yum on Linux, or git-scm.com on Windows
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git --version
L2

Your first repository and commit

git init turns any folder into a Git repository by creating the .git directory — the "brain" that holds all history. From there, the daily loop from the essentials diagram is just three commands:

bashgit init
echo "hello" > readme.md
git add readme.md          # working directory → staging area
git commit -m "first commit"   # staging area → local repo, as a permanent snapshot
git log --oneline          # see the commit you just made

git status at any point tells you exactly which of the four areas your changes currently sit in — the single most useful command for staying oriented while learning.

L3

Branching hands-on

Create a branch, switch to it, confirm you're on it, and make a commit that only exists there:

bashgit branch feature-x           # create the label
git checkout feature-x         # switch to it (or: git switch feature-x)
git checkout -b feature-y      # create and switch in one step
git branch                     # list all branches, * marks the current one
L4

Merging and resolving a real conflict

Merge a feature branch back into main, and deliberately create a conflict (edit the same line on both branches) to practise resolving one — this is the single most important practical Git skill, because it happens constantly on any real team:

bashgit checkout main
git merge feature-x
# if both branches touched the same line, Git marks it directly in the file:
#   <<<<<<< HEAD ... your version ... ======= ... their version ... >>>>>>> feature-x
# edit the file to keep the correct content, removing the markers, then:
git add conflicted-file.txt
git commit                     # completes the merge
L5

Rebase, hands-on

Rebase your feature branch onto the latest main instead of merging, and compare the resulting history — it's linear, with no merge commit:

bashgit checkout feature-y
git rebase main                # replays feature-y's commits on top of main's latest commit
# if a conflict appears mid-rebase: fix it, then
git add resolved-file.txt
git rebase --continue

The rule to internalise here: never rebase a branch other people are already pulling from — rewriting history that others have based work on creates a divergence that's genuinely painful to untangle.

L6

Cherry-pick, hands-on

Grab one specific commit from another branch without merging everything else on it:

bashgit log feature-x --oneline     # find the commit hash you need
git checkout main
git cherry-pick a1b2c3d         # applies just that one commit onto main
L7

Remote workflows — clone, fork, and the pull request

Clone an existing repository, or fork one you don't have write access to, then push a branch and open a pull request for review:

bashgit clone https://github.com/org/repo.git      # direct write access
# — or, if you don't have write access: fork on GitHub's UI first, then —
git clone https://github.com/yourname/repo.git
git checkout -b my-fix
# ...make changes, commit...
git push origin my-fix
# open a pull request on GitHub from my-fix into the upstream branch
L8

Stash and .gitignore

git stash temporarily shelves uncommitted changes so you can switch branches cleanly without committing half-finished work, then bring them back later. A .gitignore file tells Git which files to never track at all — build artefacts, dependency folders, local secrets — so they never accidentally get committed:

bashgit stash                 # shelve current uncommitted changes
git checkout other-branch
# ...do something else...
git checkout feature-y
git stash pop              # bring the shelved changes back

# .gitignore
node_modules/
*.log
.env
03
Phase 3 · Interview Q&A

The Git questions you'll be asked

These are the questions the instructor explicitly flags in the Day 11 interview video, with answers built from his explanations. Answer each out loud, then reveal.

Q1What is the difference between Git and GitHub?reveal ▸
Answer

Git is the version control tool itself, running locally and entirely independent of the internet. GitHub is a hosting service built around Git that adds a remote place to store repositories plus collaboration features — pull requests, code review, issues — that Git alone doesn't provide. You can use Git with no GitHub account; GitHub is where teams choose to host and collaborate on top of it.

Q2What is the difference between a centralized and a distributed version control system?reveal ▸
Answer

A centralized system (like SVN) keeps the true history on one central server, and developer machines mostly just hold the current checkout — if the server goes down, nobody can commit or view history. A distributed system (Git) gives every clone the entire project history, so commits, branching, and history browsing all work fully offline, and any clone could in principle restore the whole project.

Q3What is a branch, really?reveal ▸
Answer

A branch is a movable, named pointer to a specific commit — not a copy of the code. That's why creating one is instant and nearly free: Git just adds a label, it doesn't duplicate any files. Branching lets work happen in isolation until it's deliberately merged back into the shared history.

Q4What is the difference between merge and rebase?reveal ▸
Answer

Merge combines two branches' histories with a new merge commit that has two parents, preserving exactly what happened and when, at the cost of a history that can look tangled. Rebase replays your branch's commits on top of another branch's latest commit, producing clean, linear history, at the cost of rewriting commits — which makes it unsafe on any branch other people are already working from.

Q5What is cherry-pick, and when would you use it?reveal ▸
Answer

Cherry-pick applies one specific commit from anywhere onto your current branch, without bringing in anything else from the branch it came from. It's the right tool when you need just one fix — say, a hotfix that was committed on a feature branch — without merging that entire branch's other, unfinished work.

Q6What is the difference between clone and fork?reveal ▸
Answer

Clone downloads a copy of an existing repository, including its full history, and stays linked to the remote you cloned from — you use it when you already have write access. Fork is a GitHub-level action that creates your own independent copy of someone else's repository under your account; you use it when you don't have write access and want to propose changes back via a pull request.

Q7What is the difference between git revert and git reset?reveal ▸
Answer

git revert creates a brand-new commit that undoes a previous commit's changes, preserving history — safe on shared branches, since nothing is rewritten. git reset moves the branch pointer itself backward (optionally discarding changes), which rewrites history — safe only on a branch nobody else has already pulled or based work on.

Q8What is git stash for?reveal ▸
Answer

It temporarily shelves your uncommitted changes so you can switch branches with a clean working directory, without having to make a throwaway commit. You bring the changes back later with git stash pop, on the same branch or a different one.

Q9What is .gitignore, and why does it matter?reveal ▸
Answer

A .gitignore file lists patterns for files Git should never track — build output, dependency folders, local secrets. It matters because without it, exactly this kind of file gets accidentally committed, bloating the repository or, worse, leaking a secret into permanent history.

Q10What is a detached HEAD state?reveal ▸
Answer

Normally HEAD points at a branch, which itself points at a commit. A detached HEAD happens when you check out a specific commit directly instead of a branch — HEAD now points straight at that commit. Any new commits you make there aren't on any branch, so they're at real risk of being lost once you check out something else, unless you create a branch from that point first.

Q11How do you undo the last commit but keep the changes in your working directory?reveal ▸
Answer

git reset --soft HEAD~1 moves the branch pointer back one commit while leaving your changes staged; git reset HEAD~1 (mixed, the default) leaves them unstaged but still in your working directory. Either lets you fix the commit message or the contents and recommit, without losing the actual work.

Q12What is a merge conflict, and why does it happen?reveal ▸
Answer

It happens when two branches being merged (or rebased) have both changed the same lines of the same file in different ways, and Git can't automatically decide which version is correct. Git marks the conflicting section directly in the file with conflict markers, and a human has to edit it to the intended final content before completing the merge.

Q13What is a common real-world branching strategy?reveal ▸
Answer

main reflects what's actually in production and is protected from direct pushes; short-lived feature branches carry in-progress work and merge back via reviewed pull requests; hotfix branches come directly off production for urgent fixes. The exact strategy (Git Flow, trunk-based, GitHub Flow) varies, but the goal is always keeping the deployed branch stable while giving work-in-progress a safe, isolated place to live.

Q14Why should you never rebase a shared/public branch?reveal ▸
Answer

Rebase rewrites commit history — it replaces the old commits with new ones that have different hashes. If anyone else has already pulled the old commits, their history and yours now diverge, and reconciling that is a genuinely painful, error-prone exercise. The safe rule: only rebase branches that are still entirely your own, never ones others are already building on.

04
Phase 4 · Scenario drills

The senior layer — Git where history is the incident

You can now commit, branch, merge, rebase, and collaborate through a remote. These 17 scenarios are the senior version: rewritten history, leaked secrets, corrupted repos, and the git archaeology that finds exactly which commit broke production. Answer out loud, reveal, and mark yourself.

🌿

Git & GitHub

17 scenarios · 1–17

The complete Git scenario set, worked through in depth. This is the thinking and the story behind each one, not answers to memorise. The goal is that a detached HEAD or a leaked secret in history is a puzzle you already know how to open, not a page you have to look up.

⚡ The universal Git-recovery reflex

Before panicking about "lost" work, remember these in order:

  1. Git almost never actually deletes anything immediately. Old commits stick around until garbage collected — git reflog is your history of where HEAD has pointed, including commits no branch references anymore.
  2. Is this local or already pushed? A local mistake is trivially fixable; something already pushed and pulled by others needs a much more careful, communicated fix.
  3. Would rewriting history hurt anyone else? If anyone else has the old history, rewriting it (rebase, force-push, filter-repo) creates real pain for them — communicate before you do it.
  4. What's the smallest, safest fix? A revert commit is almost always safer than rewriting history, even when rewriting feels "cleaner."

Almost every scenario below is one of these four questions applied to a specific situation. Get the reflex automatic and the rest is detail.

---

📋 The full scenario inventory (distinct — no padding)

A. Recovery & mistakes

  1. Accidentally committed to the wrong branch
  2. Deleted a branch with unmerged work
  3. A commit was lost after a hard reset

B. History rewriting risks

  1. A force-push overwrote a teammate's commits
  2. A rebase goes badly wrong mid-way
  3. Squashing commits before merging a pull request

C. Secrets & sensitive data

  1. A secret was committed and pushed
  2. A large binary file bloats the repository forever

D. Conflicts & merges

  1. A merge conflict touches dozens of files
  2. The same bug fix was cherry-picked onto two branches and now conflicts

E. Collaboration & workflow

  1. A fork has drifted far behind the upstream repository
  2. A pull request has hundreds of unrelated changed files
  3. Protected branch rules are blocking an urgent hotfix

F. Investigation

  1. Finding which commit introduced a regression
  2. Finding who changed a specific line, and why

G. Repository health

  1. The .git directory is corrupted
  2. Onboarding a new engineer to a messy, undocumented repository

---

1Accidentally committed to the wrong branchScenario

You've just realised three commits you meant to make on a feature branch actually went straight onto main.

What is actually happening (the mental model).

A commit lives on whatever branch HEAD pointed to when you made it — if you forgot to create/switch branches first, the commits are exactly where you told Git to put them, they're just not where you meant. Nothing is broken; this is a labeling fix, not a data-loss problem, as long as it hasn't been pushed yet.

How to work through it.

  1. If not yet pushed: create a new branch right at the current commit (git branch feature-x) — it now points at the same three commits — then move main back to where it was before them (git reset --hard <commit-before-your-work>). The commits now live only on feature-x, exactly as intended.
  2. If already pushed and others may have pulled main: don't force-reset the shared branch — instead branch off at the current point for your work, and use git revert to add commits undoing the mistaken ones from main, keeping history intact and safe for anyone who already pulled.

The root causes and trade-offs.

The root cause is usually just forgetting to create a branch before starting work, and the habit fix is simple: always branch first. Beyond that habit, the right response differs sharply depending on whether the mistake is still purely local, where a reset is perfectly safe, or whether it's already been shared, where a revert is the only safe path. Resetting a shared branch trades a clean-looking history for a real risk to anyone who has already pulled the old commits.

The trap that less-experienced engineers fall into.

Force-resetting or force-pushing over a shared main to "clean up" the mistake, without checking whether anyone else has already pulled it — turning a simple labeling fix into a shared-history incident.

🎯 Interviewer follow-up questions you should expect.

  • "How do you move commits off main onto the right branch?" You branch at the current commit, then reset main back — safe only if nobody's pulled it yet.
  • "What if it's already been pushed and pulled by others?" Don't rewrite history — revert the mistaken commits on main instead.
Say it like this"The commits aren't lost, they're just on the wrong label, so the fix is mostly about whether this has been pushed yet. If it's still local, I branch at the current point and reset main back to before those commits — clean and safe. If it's already been pushed and possibly pulled by someone else, I don't touch history at all; I revert the mistaken commits on main instead, which is slower but never breaks anyone else's clone."

---

Mark:
2Deleted a branch with unmerged workScenario

You ran git branch -D feature-x and only afterward remembered it had commits that were never merged anywhere.

What is actually happening (the mental model).

Deleting a branch only removes the label — the commits themselves stay in Git's object database until garbage collection eventually cleans up anything genuinely unreachable, which doesn't happen immediately. As long as you act before that cleanup, the commits are recoverable.

How to work through it.

  1. git reflog — shows a log of everywhere HEAD has recently pointed, including the tip of the branch you just deleted, even though no branch label points to it anymore.
  2. Find the commit hash that was the tip of feature-x in that log, and recreate the branch pointing at it: git branch feature-x <that-hash>.
  3. Act reasonably promptly — reflog entries and unreachable commits do eventually get garbage collected (by default after a number of months), so this isn't an indefinite safety net, but it's rarely an emergency either.

The root causes and trade-offs.

The root cause is usually just deleting a branch without first checking whether its work had actually been merged, and the habit fix is to run git branch --merged before deleting anything. Relying on reflog to recover from this is a genuine, reliable recovery path, not a hack, but it only works locally — reflog is never pushed or shared, so the recovery only works on the exact machine where the commits were originally made.

The trap that less-experienced engineers fall into.

Assuming a deleted branch's commits are immediately, permanently gone and either panicking or giving up on recovering them, instead of checking reflog first.

🎯 Interviewer follow-up questions you should expect.

  • "You deleted a branch with unmerged work — is it gone?" Not immediately — git reflog can find the commit hash, and a new branch can be created pointing at it.
  • "Does this work if you deleted the branch on a different machine?" No, because reflog is local to each clone, so recovery only works on the machine the commits were actually made on.
Say it like this"Deleting a branch only removes the label, not the commits themselves — they stick around until garbage collection eventually cleans them up, which isn't immediate. So I go straight to git reflog to find the commit hash that was the tip of that branch, and recreate the branch pointing at it. The one caveat is this is local — it only works on the machine where the commits were actually made, since reflog isn't shared or pushed anywhere."

---

Mark:
3A commit was lost after a hard resetScenario

Someone ran git reset --hard to an earlier commit, and the commits after that point — along with the working directory changes — appear to have vanished.

What is actually happening (the mental model).

git reset --hard moves the branch pointer and overwrites the working directory to match, discarding uncommitted changes outright — but any commits that existed before the reset are, like the deleted-branch scenario, still sitting in Git's object database and reachable via reflog, only uncommitted working-directory changes are genuinely, permanently gone.

How to work through it.

  1. Distinguish what was actually lost: committed work (recoverable via reflog) versus uncommitted working-directory changes (not recoverable through Git at all, since Git never had a snapshot of them).
  2. For committed work: git reflog, find the commit hash from before the reset, and either create a branch there or git reset --hard forward to it (careful — check nobody's relying on the current, reset-to state first).
  3. Going forward, treat --hard specifically as the dangerous variant — git reset --soft or --mixed preserve working-directory changes, and are the safer default unless you specifically intend to discard uncommitted work too.

The root causes and trade-offs.

Confusing what reset actually discards — committed history is recoverable via reflog, uncommitted changes are not, because Git never captured them as a distinct, addressable object in the first place. This is exactly why "commit early, commit often" is good practice — a WIP commit is recoverable if you change your mind; an uncommitted edit is one careless command away from gone.

The trap that less-experienced engineers fall into.

Reaching for --hard as a default reset flag out of habit, without registering that it also silently discards any uncommitted working-directory changes, not just moving the commit pointer.

🎯 Interviewer follow-up questions you should expect.

  • "Can you recover commits lost to a hard reset?" Yes, via reflog, as long as they were actually committed first.
  • "What about uncommitted changes at the time of the reset?" Those are genuinely gone — Git never had a snapshot of them to recover.
Say it like this"The key distinction is what was actually committed versus what was just sitting uncommitted in the working directory — committed work survives a hard reset in Git's object database and is recoverable via reflog, but uncommitted changes are gone the moment --hard overwrites the working directory, because Git never had a snapshot of them. That's part of why I commit early and often — a WIP commit is a safety net, an uncommitted edit isn't."

---

Mark:
4A force-push overwrote a teammate's commitsScenario

Someone ran git push --force on a shared branch, and a teammate's commits that were already pushed are now missing from the remote.

What is actually happening (the mental model).

--force tells the remote "make your history match mine, no matter what" — if your local branch didn't have the teammate's commits (because you hadn't pulled them yet), force-pushing overwrites the remote's history and their commits are no longer part of any branch on the remote, even though they still exist on the teammate's own machine.

How to work through it.

  1. Confirm the teammate still has the commits locally (they almost certainly do, since force-push only affects the remote) — ask them to check git log or git reflog on their own machine.
  2. Have them push their branch again (or push it to a temporary branch name first if you want to review before reintegrating) so their commits return to the remote.
  3. Going forward, prefer git push --force-with-lease over a plain --force — it refuses to overwrite the remote if it has commits your local copy doesn't know about, catching exactly this situation before it happens instead of after.
  4. Treat this as a signal to establish a team norm — force-pushing to shared branches (as opposed to your own personal feature branch) should be rare and communicated, not routine.

The root causes and trade-offs.

This usually happens when someone force-pushes to a branch that other people are also pushing to, without first pulling their latest changes. It's made worse by using a plain --force instead of the safer --force-with-lease, which would have caught the conflict and refused to push. The commits usually aren't truly lost, since the teammate still has them locally — the real cost here is the disruption and confusion, not permanent data loss.

The trap that less-experienced engineers fall into.

Panicking and assuming the teammate's work is permanently destroyed, instead of first checking whether it still exists on their own machine, which it almost always does.

🎯 Interviewer follow-up questions you should expect.

  • "A force-push overwrote a teammate's commits on the remote — are they gone?" Very likely not — they still exist on the teammate's own machine, and re-pushing from there restores them.
  • "How do you prevent this?" You use --force-with-lease instead of plain --force, and treat force-pushing shared branches as something to communicate, not do routinely.
Say it like this"A force-push only overwrites the remote — it doesn't touch the teammate's own machine, so their commits almost certainly still exist locally for them, and having them push again restores the remote. Going forward I'd use force-with-lease instead of a plain force, since it specifically refuses to overwrite commits your local copy doesn't know about, which catches exactly this situation before it happens rather than after."

---

Mark:
5A rebase goes badly wrong mid-wayScenario

Halfway through resolving a series of conflicts during a rebase, you've lost track of what state the branch is even in, and you just want to get back to where you started.

What is actually happening (the mental model).

A rebase in progress is a genuinely intermediate state — Git is replaying your commits one at a time onto a new base, pausing at each conflict — and it's completely safe to abandon and start over, because your original branch and commits haven't actually been altered yet until the rebase fully completes.

How to work through it.

  1. git rebase --abort — cleanly cancels the in-progress rebase and returns the branch to exactly the state it was in before you started, no matter how far into resolving conflicts you'd gotten.
  2. Once back to a clean starting point, reconsider the approach — sometimes a large, conflict-heavy rebase is a sign to rebase in smaller pieces, or to merge instead if the branch has diverged too far to make a clean rebase practical.
  3. If you'd rather pause without losing progress entirely, git rebase --continue after fixing just the current conflict moves to the next one, rather than abandoning the whole operation.

The root causes and trade-offs.

This usually happens when a branch has diverged significantly from its base, which produces many conflicts all at once in a single rebase. The alternative, and the better habit, is rebasing in small, frequent steps that keep each individual rebase simple. Aborting and reconsidering does cost you the time already spent resolving conflicts, but that's still far safer than pushing forward while confused about the current state.

The trap that less-experienced engineers fall into.

Continuing to push through a confusing, conflict-heavy rebase out of a reluctance to "waste" the conflict-resolution work already done, instead of simply aborting — which is completely free — and reconsidering the approach with a clear head.

🎯 Interviewer follow-up questions you should expect.

  • "You're lost mid-rebase — what's the safe move?" git rebase --abort cleanly returns you to the pre-rebase state with nothing altered.
  • "How do you avoid huge, painful rebases?" You rebase in smaller, more frequent increments rather than letting a branch diverge far from its base.
Say it like this"A rebase in progress hasn't actually changed anything permanently yet, so if I'm lost mid-way, the safe move is just git rebase --abort — it cleanly returns to exactly where I started, no matter how many conflicts I'd already resolved. I don't push forward confused just to avoid 'wasting' that work; it's genuinely free to abort and reconsider, and if this keeps happening it's usually a sign to rebase in smaller, more frequent steps instead of letting a branch diverge too far."

---

Mark:
6Squashing commits before merging a pull requestScenario

A pull request has 23 commits, many of them "fix typo," "wip," and "actually fix it this time" — a reviewer asks you to clean it up before merging.

What is actually happening (the mental model).

Individual work-in-progress commits are genuinely useful while you're working — cheap checkpoints you can return to — but they're noise in the permanent, shared history of main, where what matters is a clean record of what changed and why, not the messy real-time process of getting there. Squashing combines many commits into one (or a few) logically meaningful commits before they join permanent history.

How to work through it.

  1. Interactive rebase (git rebase -i) against the base branch lets you mark which commits to squash into the one before them, combining the WIP noise into a single, well-described commit.
  2. Many platforms (GitHub included) offer a "squash and merge" option directly in the pull request UI, which does the same thing automatically without you needing to rebase manually first.
  3. Write the resulting single commit message properly — this is often the only record future readers will see of why this change was made, so it deserves real care, not just the first WIP message left standing.
  4. Remember this is exactly the "don't rewrite shared history" rule from earlier, but with a specific exception: it's safe here because the branch is your own feature branch that only you have been pushing to, not something others have already branched from.

The root causes and trade-offs.

Committing very frequently during development is genuinely good practice for your own personal checkpoints, even though it's noisy for permanent history. Squashing before merge trades that fine-grained detail away for a clean permanent history, and that fine-grained WIP trail is rarely missed once it's gone. This is a deliberate, common trade-off that teams make on purpose, not an accident to avoid.

The trap that less-experienced engineers fall into.

Squashing a branch that other collaborators have also been pushing to or branching from — reintroducing the exact shared-history-rewrite risk that squashing your own solo feature branch doesn't have.

🎯 Interviewer follow-up questions you should expect.

  • "Why squash commits before merging?" WIP commits are useful while working but noisy in permanent history, so squashing leaves a clean, meaningful record of what changed and why.
  • "Isn't rewriting history always risky?" Only when others have already based work on it — a solo feature branch you alone have pushed to is safe to rewrite before it merges.
Say it like this"WIP commits are great while I'm actually working — cheap checkpoints — but they're just noise in main's permanent history, so I squash them into one well-described commit before merging, either with an interactive rebase or the platform's squash-and-merge option. This is safe specifically because it's my own feature branch that only I've been pushing to — it's the one case where rewriting history doesn't risk anyone else's work, unlike rewriting a branch other people are already collaborating on."

---

Mark:
7A secret was committed and pushedScenario

An API key was accidentally committed and pushed to a shared repository three commits ago — it's already been merged into main.

What is actually happening (the mental model).

Deleting the file or making a new commit that removes the secret does not remove it from history — the secret still exists in every earlier commit's snapshot, retrievable by anyone with access to the repository (or its history via any prior clone), so the actual, non-negotiable first step is entirely outside Git: the credential itself has to be treated as compromised.

How to work through it.

  1. Rotate/revoke the actual secret immediately, at the source system (the cloud provider, the API vendor) — this is the only step that actually neutralises the risk; nothing done inside Git alone accomplishes that.
  2. Only after rotation, consider cleaning the secret out of history (tools like git filter-repo or BFG Repo-Cleaner rewrite every commit that touched the file) — this is a hygiene step for a cleaner repository, not the security fix itself.
  3. Rewriting history this way affects every commit hash downstream of the change, so it requires a force-push and, critically, coordinating with every other person who has a clone, since their local copies will diverge from the rewritten history.
  4. Add a .gitignore entry and, ideally, a pre-commit secret-scanning hook so the same mistake doesn't recur.

The root causes and trade-offs.

The root cause is usually a secret committed directly into the code instead of being loaded from an environment variable or a secrets manager, combined with no automated scanning in place to catch it before the push went through. Rewriting history to remove the secret from the repository is genuinely disruptive, since it forces everyone to re-clone or carefully reconcile their own copy, so it's worth doing for hygiene but it is never a substitute for actually rotating the credential.

The trap that less-experienced engineers fall into.

Treating "I deleted the file in a new commit" as having fixed the problem — the secret is still sitting in the older commit's snapshot, fully retrievable, and the credential remains genuinely compromised until it's actually rotated.

🎯 Interviewer follow-up questions you should expect.

  • "A secret was committed and pushed three commits ago — what's your first action?" You rotate or revoke the actual credential immediately — that's the only step that actually neutralises the exposure.
  • "Does deleting the file in a new commit fix it?" No — the secret is still in the earlier commit's history, retrievable by anyone with repo access.
Say it like this"Deleting the file in a new commit doesn't remove the secret from history — it's still sitting in that earlier commit's snapshot, so the first and only non-negotiable step is rotating the actual credential at its source, immediately. Only after that do I think about cleaning the repo's history with something like filter-repo or BFG, which requires a force-push and coordinating with everyone else's clone — that's a hygiene step, not the security fix."

---

Mark:
8A large binary file bloats the repository foreverScenario

Someone committed a 400MB video file eight months ago. It was deleted in a later commit, but every clone of the repository is still huge and slow.

What is actually happening (the mental model).

Git stores every version of every file that was ever committed — a large binary added and later deleted doesn't shrink the repository at all, because Git still keeps the full snapshot from the commit where it existed, for as long as that commit is reachable in history, which is normally forever.

How to work through it.

  1. Confirm the bloat's actual source — git count-objects -v or dedicated tools can identify the largest objects in the repository's history, rather than guessing.
  2. The only real fix is history rewriting — tools like git filter-repo or BFG Repo-Cleaner specifically strip a given file out of every commit that ever contained it, actually shrinking the repository.
  3. As with the secret scenario, this requires a force-push and coordinating with every clone, since it changes commit hashes throughout history — plan and communicate it as a deliberate, scheduled maintenance event, not a quiet fix.
  4. Prevent recurrence with Git LFS (Large File Storage) for anything that's genuinely meant to be a large binary asset going forward — it stores the large content outside the normal Git object history, keeping the repository itself lean.

The root causes and trade-offs.

The root cause is simply having no policy or tooling — like Git LFS, or a pre-commit file-size check — in place to catch large binaries before they get committed. Cleaning the existing history afterward is genuinely disruptive, but it's usually worth doing, since otherwise the repository stays meaningfully slower to clone and work with for everyone, indefinitely.

The trap that less-experienced engineers fall into.

Assuming that deleting a large file in a later commit fixes the repository's size — it doesn't, because the earlier commit's full snapshot, including that file, still exists in history and gets downloaded by anyone who clones or fetches the full history.

🎯 Interviewer follow-up questions you should expect.

  • "A large file was deleted in a later commit — is the repo smaller now?" No — the earlier commit still contains the full file, and it stays in history indefinitely unless it's explicitly rewritten out.
  • "How do you prevent this going forward?" You use Git LFS for genuinely large binary assets, so they're stored outside the normal object history from the start.
Say it like this"Deleting a large file in a later commit doesn't shrink the repo at all — the earlier commit still has the full snapshot, and that stays in history indefinitely. The only real fix is rewriting history with something like filter-repo or BFG to strip it out of every commit that ever had it, which needs a force-push and coordinating everyone's clone, so I'd treat that as a planned maintenance event. Going forward I'd put genuinely large assets in Git LFS from the start so this doesn't happen again."

---

Mark:
9A merge conflict touches dozens of filesScenario

A long-lived feature branch is finally being merged into main after months of divergence, and the merge reports conflicts across 40 files.

What is actually happening (the mental model).

The size of a merge conflict scales roughly with how long two branches have been diverging and how much overlapping code they've both touched — a 40-file conflict is less a Git problem and more a symptom of a branching workflow that let one branch drift for too long without regularly integrating the latest main.

How to work through it.

  1. Don't try to resolve all 40 in one heroic sitting — work through them methodically, file by file, understanding what each side actually changed and why, rather than blindly picking one side to "make the conflict markers go away."
  2. Use a proper merge tool (many IDEs have one built in) that shows both versions side by side with the common ancestor — far easier to reason about than raw conflict markers in a plain text editor for a conflict at this scale.
  3. Lean on whoever wrote each side's changes if the intent isn't clear from the diff alone — guessing wrong on a resolution at this scale is a realistic way to silently reintroduce a bug or lose a fix.
  4. Once resolved, this is exactly the moment to also address the root cause: agree on a cadence for regularly merging or rebasing long-lived branches against the latest main, so conflicts stay small and frequent instead of large and rare.

The root causes and trade-offs.

The root cause is a branching strategy that allows a branch to live for months without regularly integrating the latest changes from upstream. The fix is either merging main into the feature branch periodically, or preferring short-lived branches entirely, and both trade a series of more frequent, smaller conflicts for never having to face one conflict this large again.

The trap that less-experienced engineers fall into.

Resolving a large conflict quickly by mechanically picking "ours" or "theirs" for entire files without actually reading what changed — a fast way to silently discard a real fix or reintroduce an old bug.

🎯 Interviewer follow-up questions you should expect.

  • "A merge has 40 conflicting files — how do you approach it?" You go through it methodically, file by file, with a real merge tool, consulting whoever wrote each side when intent isn't clear — never mechanically picking one side for everything.
  • "How do you prevent conflicts this large in the future?" You merge or rebase long-lived branches against main regularly, or prefer short-lived feature branches entirely.
Say it like this"A conflict this large is really a symptom of a branch that's been diverging for months without regularly integrating main, so beyond resolving it carefully — file by file, with a real merge tool, never just mechanically picking one side — I'd use it as the moment to fix the actual root cause: agree on a cadence for merging main into long-lived branches regularly, or push the team toward shorter-lived branches so conflicts stay small and frequent instead of occasional and enormous."

---

Mark:
10The same bug fix was cherry-picked onto two branches and now conflictsScenario

A hotfix was cherry-picked onto both a release branch and main. Later, when the release branch is merged back into main, Git reports a conflict on that exact fix.

What is actually happening (the mental model).

Cherry-picking creates a genuinely new commit with its own hash, even though its content is identical to the original — Git's merge logic works from commit ancestry, not just matching content, so it doesn't automatically recognise "these are the same logical change" and can report a conflict even when the actual code ends up identical or trivially compatible.

How to work through it.

  1. Recognise this specific pattern when it happens — it's confusing the first time, but it's a known, explainable consequence of cherry-pick, not a sign anything is actually broken.
  2. Resolve the reported conflict by comparing the actual resulting content, not the commit history — often the "conflict" resolves to exactly the same code on both sides, and the correct resolution is simply keeping that shared content.
  3. Where this pattern recurs often on a given branching model, consider whether cherry-picking hotfixes onto multiple branches is really the right shape, versus fixing forward on a single mainline and using a proper release process to promote it — fewer duplicate, divergent copies of the same fix means fewer of these conflicts down the line.

The root causes and trade-offs.

A branching model that relies on cherry-picking the same fix onto multiple branches, which trades faster hotfix delivery on each branch for exactly this kind of downstream merge friction. It's a real, known trade-off of cherry-pick-heavy workflows, not a bug in Git.

The trap that less-experienced engineers fall into.

Assuming a reported conflict always means genuinely different, competing changes, and spending real effort trying to reconcile two versions of a fix that are actually functionally identical, just wrapped in commits with different hashes.

🎯 Interviewer follow-up questions you should expect.

  • "Why does merging report a conflict on a fix that's identical on both branches?" Cherry-pick creates a new commit with a different hash, and Git's merge logic works from ancestry, not just matching content, so it can't automatically tell they're the same logical change.
  • "How do you avoid this pattern recurring?" You consider fixing forward on a single mainline and promoting through a release process, instead of cherry-picking the same fix onto multiple branches repeatedly.
Say it like this"This looks alarming the first time but it's a known consequence of cherry-pick — it creates a brand-new commit with a different hash even though the content's identical, and Git's merge logic works from ancestry, not content matching, so it can report a conflict on a fix that's actually the same on both sides. I resolve it by comparing the actual resulting code, which is usually identical, and if this keeps happening I'd question whether cherry-picking hotfixes onto multiple branches is the right shape versus fixing forward on one mainline."

---

Mark:
11A fork has drifted far behind the upstream repositoryScenario

Your fork of a shared repository hasn't been synced in six months, and the upstream project has moved on substantially — your pull request now conflicts badly.

What is actually happening (the mental model).

A fork doesn't automatically stay in sync with the repository it was forked from — it's an independent copy from the moment it's created, and without deliberately pulling upstream changes into it, it simply diverges further every day the original repository keeps moving, exactly like any other unmerged branch, just at repository scale.

How to work through it.

  1. Add the original repository as a second remote (conventionally named upstream, alongside your own fork as origin) if it isn't already configured: git remote add upstream <original-repo-url>.
  2. Fetch and merge (or rebase) the upstream's main branch into your fork's main branch regularly: git fetch upstream, then git merge upstream/main.
  3. For a fork that's badly behind, expect this sync itself to involve real conflict resolution — the same discipline as the earlier "40-file conflict" scenario, just once as a catch-up rather than something that should have to happen at this scale again.
  4. Going forward, sync on a regular cadence (not just right before submitting a pull request) so future syncs stay small and manageable.

The root causes and trade-offs.

The root cause is usually never configuring an upstream remote in the first place, or configuring it but never actually syncing from it, combined with only ever syncing reactively right before a pull request rather than on an ongoing basis. Regular syncing costs a small routine effort in exchange for never having to face a six-month reconciliation like this one.

The trap that less-experienced engineers fall into.

Assuming a fork silently stays up to date with its origin on its own, and being surprised months later that it's drifted significantly — a fork is a snapshot at the moment of forking, not a live mirror.

🎯 Interviewer follow-up questions you should expect.

  • "How do you keep a fork in sync with its upstream repository?" You add the original repo as an upstream remote, and regularly fetch and merge or rebase its main branch into your fork's.
  • "Why did the fork drift in the first place?" A fork is an independent copy from creation, not a live mirror — it only stays in sync through deliberate, regular action.
Say it like this"A fork is a snapshot the moment you create it, not a live mirror, so it only stays current through deliberate syncing — I'd add the original repo as an upstream remote and regularly fetch and merge its main branch into mine. For a fork this far behind, the sync itself is going to involve real conflict resolution, similar to the large-merge scenario, but going forward I'd sync on a routine cadence rather than only right before submitting a pull request, so it never gets this far behind again."

---

Mark:
12A pull request has hundreds of unrelated changed filesScenario

A pull request meant to add one small feature shows 340 changed files, most of which look like formatting or whitespace changes unrelated to the actual feature.

What is actually happening (the mental model).

This is almost always a base branch problem, not an actual-change problem — the feature branch was likely created a long time ago and never updated against the latest main, so the pull request's diff includes every change that's landed on main since then (a company-wide formatter run, unrelated merges) alongside the actual feature, because Git is comparing against a stale base.

How to work through it.

  1. Confirm the suspicion: compare the feature branch's actual commits (git log main..feature-branch) against the full diff shown in the pull request — if the commit list is small but the diff is huge, it's a stale-base problem, not a huge real change.
  2. Update the feature branch against the current main (merge or rebase, per the team's convention) before the pull request is reviewed — this collapses the diff back down to just the branch's own actual commits.
  3. If a large, unrelated formatting change genuinely did land on main in the meantime, that's worth flagging separately — mixing a mass reformat into a feature's history (rather than as its own isolated commit) is exactly what causes this kind of diff bloat for the next person too.

The root causes and trade-offs.

The root cause is usually a long-lived feature branch that was never updated against a moving main, and the fix is simply to update it before review. The other common cause is a large formatting or refactoring change that was committed without isolating it as its own dedicated commit, and the fix there is to always land a wide mechanical change as its own separate commit, never mixed in with feature work.

The trap that less-experienced engineers fall into.

Reviewing (or worse, trying to resolve conflicts in) all 340 files as if they were real, intentional changes in the pull request, instead of recognising the pattern and simply updating the branch against the current main first.

🎯 Interviewer follow-up questions you should expect.

  • "A small PR shows hundreds of changed files — what's your first suspicion?" You'd suspect a stale base branch — the feature branch hasn't been updated against the latest main, so the diff includes everything that's landed there since.
  • "How do you confirm that diagnosis?" You compare the branch's actual commit list against the diff size — a small commit list with a huge diff confirms it's a base-branch problem, not a real one.
Say it like this"Hundreds of changed files in a small feature PR is almost always a stale base, not a real change — I'd confirm by checking the branch's actual commit list against the diff size, and if the commits are few but the diff is huge, that confirms it. The fix is updating the feature branch against the current main before review, which collapses the diff back down to just the real feature work, rather than reviewing 340 files that mostly aren't actually part of this change."

---

Mark:
13Protected branch rules are blocking an urgent hotfixScenario

Production is down, you have a one-line fix ready, but main is protected and requires two approvals and a passing CI run before it can merge — and both approvers are asleep.

What is actually happening (the mental model).

Branch protection rules exist specifically to prevent unreviewed, untested changes from reaching production — which is exactly the right default, but a genuine incident is exactly the scenario those rules need a deliberate, pre-planned exception path for, rather than either blindly bypassing them or waiting helplessly while production stays down.

How to work through it.

  1. Check whether the repository already has a defined emergency process — many teams configure specific people (often senior engineers or admins) who can bypass branch protection in a declared incident, precisely for this situation.
  2. If no such process exists, this is the incident itself surfacing a real gap — get the minimum viable review (even an async, rapid one from anyone available, not necessarily the two designated approvers) rather than either bypassing entirely unreviewed or waiting on people who are asleep.
  3. Whatever path is taken, document it as part of the incident record — an emergency bypass is a legitimate tool, but it should be visible and reviewed afterward, not quietly normalized as a way around the process going forward.
  4. In the retrospective, add or refine the actual emergency-bypass process if this incident revealed it didn't exist or didn't work — this exact situation recurring is itself a sign the protection rules were designed only for the calm-day case.

The root causes and trade-offs.

The root cause is branch protection configured with no emergency exception path at all, which is a real gap that usually only gets exposed during an actual incident. The underlying trade-off is between strict protection, which is the right default for safety in the common case, and having zero flexibility for a genuine, time-critical emergency — a good process needs both, not just one.

The trap that less-experienced engineers fall into.

Either waiting passively for the designated approvers while production stays down with no other action taken, or unilaterally disabling branch protection entirely to push the fix through — both are worse than using (or, if necessary, creating on the spot) a deliberate, documented emergency path.

🎯 Interviewer follow-up questions you should expect.

  • "Protected branch rules are blocking a critical hotfix during an outage — what do you do?" You use the team's defined emergency-bypass process if one exists; if not, you get the fastest available review rather than either waiting helplessly or bypassing unreviewed.
  • "How do you prevent this dilemma next time?" You establish and document a specific emergency-bypass process ahead of time, and revisit it in the incident's retrospective if it didn't work well this time.
Say it like this"Branch protection is the right default, but a real outage is exactly the case it needs a pre-planned exception for — so first I'd check whether the team already has a defined emergency-bypass process. If it doesn't, I'd get the fastest available review from anyone reachable rather than either waiting on sleeping approvers or unilaterally disabling protection, and I'd make sure that bypass is documented and reviewed afterward. Then in the retrospective I'd make sure a real emergency path actually exists for next time."

---

Mark:
14Finding which commit introduced a regressionScenario

A bug is confirmed present today and confirmed absent a month and roughly 200 commits ago — nobody knows which specific commit introduced it.

What is actually happening (the mental model).

Manually checking 200 commits one at a time is a linear search; git bisect turns this into a binary search over commit history — you tell it a known-good and known-bad commit, and it checks out the midpoint for you to test, halving the search space every round, so 200 commits takes roughly eight tests instead of up to two hundred.

How to work through it.

  1. git bisect start, then git bisect bad (marking the current, broken commit) and git bisect good <commit-from-a-month-ago> (marking the known-working one).
  2. Git checks out the midpoint commit automatically — test it, then tell Git git bisect good or git bisect bad based on what you find, and it narrows the range and checks out the next midpoint.
  3. Repeat until Git identifies the exact first bad commit — for a genuinely automatable test (a script that returns pass/fail), git bisect run <script> automates the entire process with no manual checking at each step at all.
  4. Once found, the offending commit's message, author, and diff usually make the actual root cause obvious — the hard part was narrowing down where to look, not understanding the bug itself once you're there.

The root causes and trade-offs.

If there's no automatable test for the bug, bisect still works fine — it just requires manual judgment at each step rather than being fully scripted. If the bug does have a genuinely automatable reproduction, bisect run turns the whole thing into a fully unattended search. Either way, bisect is dramatically faster than a manual linear search once the history runs to more than a handful of commits.

The trap that less-experienced engineers fall into.

Manually checking out commits one at a time, roughly in order, to try to spot when a bug appeared — a linear search that can take many times longer than the binary search bisect performs automatically.

🎯 Interviewer follow-up questions you should expect.

  • "How do you find which commit introduced a bug across 200 commits?" You use git bisect — a binary search that needs roughly log2(200), about 8 tests, instead of checking every commit.
  • "Can this be fully automated?" Yes, with git bisect run <script>, if the bug can be detected by a script that returns pass or fail.
Say it like this"Checking 200 commits one at a time is a linear search; git bisect turns it into a binary search — I mark a known-good and known-bad commit, and it checks out the midpoint each round for me to test, halving the range every time, so 200 commits takes around eight tests instead of up to two hundred. If the bug's detectable by a script, bisect run automates the entire thing with no manual checking at all."

---

Mark:
15Finding who changed a specific line, and whyScenario

A specific, odd-looking line of configuration needs to change, but nobody currently on the team remembers why it's there or whether it's safe to touch.

What is actually happening (the mental model).

Git attaches every line of every file, at every point in history, to the specific commit that last changed it — this is exactly the information needed to answer "why does this exist," and it's sitting in the repository already, not something that requires asking around and hoping someone remembers.

How to work through it.

  1. git blame <file> — shows, line by line, which commit and author last touched each line, including the specific line in question.
  2. Take that commit's hash to git show <hash> or git log -1 <hash> to read its full commit message and diff — a well-written commit message at this point often directly explains the "why," which is exactly what a bare line of code can never tell you on its own.
  3. If the commit message itself doesn't explain enough, use it to find the associated pull request or ticket (many teams reference one in every commit message) for the fuller discussion and context around the change.
  4. If the line has been touched many times, git log -p <file> or git blame with the -w flag (ignoring whitespace-only changes) can help skip past trivial reformatting commits to find the change that actually mattered.

The root causes and trade-offs.

This comes down entirely to commit message discipline. Poor discipline means a line's blame points to a commit message like "fix stuff," which tells you who touched it but nothing about why. Good discipline means the commit message directly answers the question on its own. This is exactly why commit message quality matters well beyond the moment of writing it — it's read by someone doing archaeology, potentially years later.

The trap that less-experienced engineers fall into.

Asking around the team hoping someone remembers, or simply guessing and changing the line without investigating — when the actual answer is very often sitting directly in the repository's own history via blame, faster than asking a person who may not even remember.

🎯 Interviewer follow-up questions you should expect.

  • "Nobody remembers why a line of code exists — how do you find out?" You use git blame to find the commit, then git show on that commit for its message and full diff.
  • "What if the commit message doesn't explain it either?" You look for a linked pull request or ticket reference in the message, which often has the fuller discussion.
Say it like this"This information is usually already sitting in the repository, so instead of asking around and hoping someone remembers, I go straight to git blame on that file to find exactly which commit last touched that line, then git show on that commit for its full message and diff — a well-written commit message at that point often answers the 'why' directly. If it doesn't, I'll follow a linked ticket or pull request reference in the message for the fuller context."

---

Mark:
16The .git directory is corruptedScenario

A local repository starts throwing errors on nearly every Git command — something about corrupted or missing objects — after an interrupted disk operation.

What is actually happening (the mental model).

Because Git is distributed, a corrupted local .git directory is rarely a true disaster the way it would be in a centralized system — as long as a remote (or any other clone) exists, the complete, uncorrupted history is sitting safely elsewhere, and the local corruption only threatens whatever uncommitted or unpushed work existed solely on this machine.

How to work through it.

  1. First check what's actually unpushed — git status and git log (if they still run at all) or, in the worst case, inspecting the working directory files directly — since that's the only thing genuinely at risk here.
  2. If the repository is unusable, the simplest reliable recovery is a fresh clone from the remote into a new folder, then manually copying over any files that had uncommitted changes from the old, corrupted working directory.
  3. Tools like git fsck can sometimes diagnose or even repair a corrupted local repository without a full re-clone, worth trying first if the corruption seems partial rather than total.
  4. Treat this as a reminder that anything not yet pushed exists in exactly one place — regularly pushing work-in-progress branches (even ones not ready for review) is cheap insurance against exactly this kind of local disk problem.

The root causes and trade-offs.

The root cause is usually an interrupted write to the .git directory — a crash, a disk failure, a killed process — that leaves some objects in an inconsistent state. What makes this recoverable is that Git is distributed, so a remote (or any teammate's clone) almost always has a complete, healthy copy, unlike a centralized system where the same local corruption could mean real, permanent data loss.

The trap that less-experienced engineers fall into.

Panicking as though the entire project's history might be lost, without registering that a remote (or any teammate's clone) almost certainly holds a complete, healthy copy — the actual risk is narrowly scoped to unpushed local work, not the whole project.

🎯 Interviewer follow-up questions you should expect.

  • "Your local .git directory is corrupted — is the project's history at risk?" No, as long as a remote or another clone exists — the complete history lives there, and only unpushed local work is genuinely at risk.
  • "How do you recover?" You try git fsck for partial corruption; for anything unusable, a fresh clone from the remote plus manually recovering any uncommitted local files works.
Say it like this"Because Git is distributed, a corrupted local .git directory isn't the disaster it would be in a centralized system — the remote, or any teammate's clone, almost certainly has the complete, healthy history. The actual risk is narrowly scoped to whatever was uncommitted or unpushed on this one machine. I'd try git fsck first for partial corruption, and if that doesn't work, a fresh clone from the remote plus manually recovering any local uncommitted files is a completely reliable fallback."

---

Mark:
17Onboarding a new engineer to a messy, undocumented repositoryConcept

A new hire's first task is to contribute to a repository with an inconsistent branching pattern, commit messages like "fix" and "update," and no written contribution guide.

What is actually happening (the mental model).

A repository's Git history and conventions are a form of documentation, whether or not anyone intended them that way — a new engineer's first real friction is usually not the application code itself, but decoding the team's unwritten Git conventions (or lack of them) well enough to make a change that fits in rather than adding to the mess.

How to work through it.

  1. Before writing any code, read a sample of recent, real pull requests (not just the commit log) to reverse-engineer the team's actual, currently-followed conventions — the true norms are often different from anything written down, if anything is written down at all.
  2. Ask directly, early, rather than guessing and being corrected later: what's the branching model, what typically blocks a PR from merging, is there a preferred commit message format even if it isn't enforced anywhere.
  3. As the new person, resist the urge to unilaterally "fix" the team's Git conventions in your first weeks — propose improvements once you've built context and credibility, the same principle as the very first scenario in this chapter's fundamentals cousin.
  4. If you're in a position to influence it, this is a good moment to suggest lightweight, low-cost documentation (a short CONTRIBUTING.md, a commit message convention) precisely because you're experiencing the friction of its absence firsthand, while it's still fresh.

The root causes and trade-offs.

The root cause is having no written contribution guide at all, and relying entirely on tribal knowledge instead — knowledge that's easy for existing team members to take for granted and genuinely hard for a newcomer to infer on their own. Writing that documentation down costs a small amount of upfront effort, in exchange for a meaningfully smoother onboarding for every future hire.

The trap that less-experienced engineers fall into.

Either silently guessing at conventions and getting quietly corrected repeatedly in review, or loudly trying to overhaul the team's Git practices in the first week before having enough context or credibility to know which parts are load-bearing and which are just historical accident.

🎯 Interviewer follow-up questions you should expect.

  • "How do you get up to speed on a messy, undocumented repo's conventions?" You read recent real pull requests to reverse-engineer actual practice, and ask directly rather than guessing.
  • "Should you fix the mess right away?" Not unilaterally in the first weeks — you build context and credibility first, then propose improvements, ideally including lightweight documentation.
Say it like this"A repo's Git history is a kind of documentation whether anyone meant it to be or not, so before writing any code I'd read recent real pull requests to reverse-engineer the team's actual conventions, and ask directly about anything unclear rather than guessing and getting corrected in review. I wouldn't try to overhaul the conventions in week one — I'd build context first — but I'd absolutely flag, while the friction is still fresh, that a short contribution guide would make the next new hire's first week easier."

---

Mark:
🧠 How to turn this into muscle memory
  • Drill the reflex first (Git rarely deletes anything immediately → local or already pushed → would rewriting history hurt anyone → what's the smallest safe fix) until it's automatic — nearly every scenario above is this reflex applied to a specific mistake.
  • Break and recover, in a real repo. Delete a branch on purpose and recover it with reflog; force-push over your own test commits and practise the fix; run a rebase into a wall of conflicts and abort it; commit a fake secret to a throwaway repo and practise the rotate-first response.
  • One scenario, three passes. Reason it out, then say the spoken version without looking, then explain it to a teammate — especially reflog recovery, the secret-in-history scenario, and merge vs rebase, which come up constantly.
  • Keep a "Git mistake → real fix" log — the branch you recovered, the history you rewrote carefully, the bisect that found a regression in eight steps instead of two hundred. These become the concrete stories you tell in the behavioural round.

Next in the course order: Virtual Machines, at this same depth.

No scenarios match that search. Clear search
Git & GitHub — DevOps Zero → Hero. Theory, Lab & Interview rebuilt from Days 9–11 (why version control exists, centralized vs distributed, branching, merge/rebase/cherry-pick, clone vs fork); the 17 scenario drills are the Scenario Playbook's Git domain, rewritten in plain English. ← All chapters