Module 2 — Your First Repository (git init, add, commit, status, diff)
Updated 8 September 2026
🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)
Part A — Making a repository
A1. git init — turning a directory into a repository
A repository is a directory that Git is watching: your project files, plus the snapshot database from Module 1 living alongside them. git init is what starts the watching. It does exactly one thing: creates a hidden subdirectory called .git in the current directory, containing an empty snapshot database and Git's bookkeeping files. That is the entire difference between "a folder" and "a repository" — no registration, no daemon, no server. Delete .git and the directory is just a folder again; your files are untouched, but all recorded history is gone.
Everything Git will ever record about this project lives inside that one .git directory. Module 3 opens it up and reads it; for now, treat it as Git's private filing room and never edit it by hand.
git init is hiring a notary who moves into a small back office (.git) of one specific filing cabinet (your directory). The notary only certifies documents from this cabinet, keeps every certified copy in the back office, and has no idea other cabinets exist. Firing the notary (deleting .git) empties the back office — the cabinet's current papers stay exactly where they are.
Where the analogy stops working. A notary certifies what you show them and remembers being shown it. Git's notary records nothing automatically — a repository with no commits is an empty archive even after months of editing files in it. And unlike a notary's ledger, the back office is on your disk, under your control: no external authority, no independent witness. That is why "it's in Git" only means something after you commit — and, later, share it.
🧪 Exercise 2.1 — create a repository and see what appeared
cd ~/git-course
git init kitchen # "git init <name>" creates the directory AND initializes it
cd kitchen
ls -A # -A = show hidden entries too (names starting with .)✅ Expected result — click to reveal
Initialized empty Git repository in /home/aisha/git-course/kitchen/.git/
.gitWhat to read out of it: the confirmation line names the exact path of the new .git directory — read it every time you init; if the path surprises you, you just initialized the wrong directory. ls -A shows the whole visible result: one hidden entry. No project files were created or changed. Plain ls would show nothing at all — worth trying once to convince yourself the repository is genuinely invisible to normal work. (No hint: lines about branch names here — you configured init.defaultBranch in Module 1; anyone who skipped that sees the hint from Module 1's Exercise 1.8.)
🧪 Exercise 2.2 — a deliberate failure: ask for status outside any repository
cd /tmp # a directory that is not a repository
git status
echo "exit code: $?"✅ Expected result — click to reveal (fails on purpose)
fatal: not a git repository (or any of the parent directories): .git
exit code: 128What to read out of it: this is one of the most common Git errors in real life, and it is almost always a location problem, not a Git problem. The parenthetical is the informative part: Git looked for .git in /tmp, then in /, walking up the directory tree — that walk is how Git commands work from any subdirectory of a repository. So this error means: neither this directory nor anything above it is a repository. The reflex fix is pwd and cd, not reinstalling anything. Exit code 128 — Git's "fatal error" convention from Module 1.
A2. Tracked and untracked files
Initializing the repository did not put your files under version control. Git sorts every file in the directory into two bins. Tracked files are ones Git has been explicitly told about — they are in the snapshot database (or staged to enter it), and Git watches them for changes. Untracked files are everything else: present on disk, visible to you, and completely ignored by Git's history machinery until you say otherwise. Git never starts tracking a file on its own — you opt each file in. That is a feature: build outputs, editor droppings, and secrets stay out of history unless someone deliberately adds them.
git status is the command that shows the sorting. You will run it constantly — before and after almost everything — because it answers the only three questions that matter mid-work: what is untracked, what changed, and what will go into the next snapshot.
🧪 Exercise 2.3 — watch a new file stay untracked
cd ~/git-course/kitchen
git status # before: empty repository
echo "flour, water, salt" > recipe.txt
git status # after: one untracked file✅ Expected result — click to reveal
On branch main
No commits yet
nothing to commit (create/copy files and use "git add" to track)
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
recipe.txt
nothing added to commit but untracked files present (use "git add" to track)What to read out of it: git status output is built from three parts — a header (On branch main / No commits yet), zero or more sections listing files by state, and a summary line. After creating the file, a new section appears: Untracked files, holding recipe.txt. Notice Git coaches you in parentheses on every section — use "git add" to track — status output is a cheat sheet that tells you the next command. The file's content is irrelevant here: Git has not read it, hashed it, or stored it. It has only noticed a name it does not recognize.
🎯 Interview questions — Part A
🎯 "What is a repository in Git?" — asked verbatim in Top Git Interview Questions for 2025, dev.to, Aug 2025
A repository is a project directory that Git tracks: the working files you edit, plus a hidden .git subdirectory holding the complete snapshot database — every commit ever recorded, all branches and settings for that project. The .git directory is the repository in the strict sense; the visible files are just one checked-out state of it. Repositories are self-contained: copying the directory copies the entire history; deleting .git deletes exactly the history and nothing else.
The details that separate candidates: an average answer says "a folder where Git stores your code." A strong answer separates the working files from the .git database and can say what lives where; knows creation is local and instant (git init writes one directory — no server involved); and mentions the special case worth knowing exists: a bare repository (.git contents with no working files), which is what servers host — a pointer to Module 8's material that interviewers often probe right after this question.
🎯 "How do you create a git repository?" — asked verbatim in Interview Coder's 90+ Git questions, Sep 2025
For a new project: git init inside an existing directory (or git init <name> to create-and-initialize in one step). That writes the .git directory and nothing else — the first snapshot still has to be made by staging files (git add) and committing them (git commit). A sensible first sequence is: git init, create or copy in the initial files, git add ., git commit -m "Initial commit". For a project that already exists elsewhere, you copy it instead of initializing fresh — git clone <url> — which brings the full history with it (Module 8 covers cloning and everything remote).
The details that separate candidates: an average answer stops at "git init". A strong answer distinguishes the two creation paths (init for new, clone for existing) and states clearly that init alone records nothing — a repository with zero commits protects zero work. Production signal: mentioning that on a server you would create a bare repository (git init --bare) since nobody edits files there, and that in practice most "creating a repo" today happens on a hosting platform first, then gets cloned — the local init path is for projects born on your machine.
Part B — The three trees and the staging area
B1. Working tree → staging area → repository
Here is the mental model that makes all of everyday Git derivable. Your changes live in one of three places, and two commands move them rightward:
Diagram source
flowchart LR
W["Working tree<br>the files you edit"] -->|"git add"| S["Staging area<br>the NEXT snapshot,<br>under construction"]
S -->|"git commit"| R["Repository (.git)<br>permanent snapshots"]The working tree is just your directory — the real files you edit with any tool. The repository is the snapshot database inside .git. Between them sits Git's distinctive idea: the staging area (also called the index — same thing, and interviewers use both names). The staging area is a draft of the next snapshot. git add <file> does not "save" anything permanently — it copies that file's current content into the draft. git commit seals whatever the draft contains into a permanent snapshot, and only that.
Why the middle step? Deliberate commits. Your working tree after two hours of debugging contains the fix, plus stray experiments, plus a config tweak. The staging area lets you pick exactly what belongs in the snapshot — the fix alone — and commit it, leaving the rest of the mess out. The commit becomes a curated unit of change with an honest message, not a bucket of "stuff I had at 6pm". A VCS records what, who, why (Module 1) — the staging area is what makes the what intentional.
Your desk (working tree) is covered in items. At the post office there is a counter (staging area) where you assemble one parcel: you carry items over one by one (git add), arrange them, maybe take one back. Nothing is final at the counter. When the parcel is right, you hand it over and it is sealed, weighed, and logged (git commit) — the log entry is permanent; the desk stays as messy as it was.
Where the analogy stops working. Carrying an item to the counter moves it — your desk loses it. git add copies: the file stays in your working tree, editable as ever. That is why (as B3 shows) you can stage a file and then keep editing it — the counter holds the version from the moment you carried it over, while the desk copy moves on. No physical counter behaves that way.
B2. add and commit — recording the first snapshot
git add <path> stages a file (or everything under a directory; git add . stages the whole current directory). git commit -m "message" turns the staged draft into a snapshot, stamped with your identity from Module 1 and your message. Both are local (Module 1, B2) — nothing leaves your machine.
🧪 Exercise 2.4 — stage, read status, commit
cd ~/git-course/kitchen
git add recipe.txt
git status # read the new section carefully
git commit -m "Add basic bread recipe"
git status # and read what remains✅ Expected result — click to reveal
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: recipe.txt
[main (root-commit) 9c2adb2] Add basic bread recipe
1 file changed, 1 insertion(+)
create mode 100644 recipe.txt
On branch main
nothing to commit, working tree cleanWhat to read out of it: after add, the status section changed from Untracked files to Changes to be committed — that section is the staging area, listed file by file; new file: marks content entering history for the first time. The commit line decodes as: branch (main), root-commit (first snapshot in this repository), 9c2adb2 (the snapshot's short ID — yours differs; Module 3 explains why), your message, then a tally: 1 file, 1 line inserted. create mode 100644 is the file's recorded permission bits — normal non-executable file. The final status is the cleanest sentence in Git: nothing to commit, working tree clean — all three trees now hold identical content.
B3. The state most people never understand: staged AND modified at once
git add copies the file's content as it is at that moment into the staging area. If you edit the file again afterward, the staging area still holds the older, added version — and the working tree holds the newer one. The same file is now legitimately in two states at once, and git status will list it in two sections simultaneously. Committing now would record the staged version, not what is on disk. This single fact explains most beginner confusion with Git, so we will produce the state on purpose and read it slowly.
🧪 Exercise 2.5 — put one file in two sections of git status
cd ~/git-course/kitchen
echo "flour, water, salt, yeast" > recipe.txt # edit the file
git add recipe.txt # stage THAT version
echo "flour, water, salt, yeast, sugar" > recipe.txt # edit AGAIN after staging
git status✅ Expected result — click to reveal
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: recipe.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: recipe.txtWhat to read out of it: recipe.txt appears twice, and both lines are true. Changes to be committed holds the yeast version (photographed at add time); Changes not staged for commit reports that the working tree has since moved on (the sugar edit). Committing right now records yeast, no sugar. To include sugar, run git add recipe.txt again — the newer photo replaces the older one in the draft. The parenthetical hints also introduce git restore, the undo tool Module 7 teaches; you do not need it yet, but notice status already told you it exists.
B4. git diff — seeing the differences between the trees
git status names which files differ between the trees; git diff shows the actual changed lines. The two everyday forms map exactly onto the three-trees picture: plain git diff compares working tree vs staging area ("what have I edited but not yet staged?"), and git diff --staged compares staging area vs last commit ("what exactly will the next commit record?"). Reading git diff --staged before every commit is the habit that prevents committing debris — it is the parcel opened for one final look before sealing.
🧪 Exercise 2.6 — read both diffs while the file is in both states
Run this while Exercise 2.5's double state still exists — do not commit first.
git diff # working tree vs staging area
git diff --staged # staging area vs last commit✅ Expected result — click to reveal
diff --git a/recipe.txt b/recipe.txt
index 8c3fb3f..4b22a33 100644
--- a/recipe.txt
+++ b/recipe.txt
@@ -1 +1 @@
-flour, water, salt, yeast
+flour, water, salt, yeast, sugar
diff --git a/recipe.txt b/recipe.txt
index 0fd697a..8c3fb3f 100644
--- a/recipe.txt
+++ b/recipe.txt
@@ -1 +1 @@
-flour, water, salt
+flour, water, salt, yeastWhat to read out of it: each diff has a header and a hunk. In the header, a/ is the older side, b/ the newer; the index 8c3fb3f..4b22a33 line names the content IDs being compared (Module 3 makes these meaningful — note the first diff's left ID 8c3fb3f equals the second diff's right ID: the staged version is literally the same object in both comparisons). The hunk header @@ -1 +1 @@ means "one line starting at line 1 on both sides"; - lines are the old content, + lines the new. First diff: unstaged sugar edit. Second: staged yeast edit relative to the last commit. Two different questions, two different answers — this is the three-trees model paying rent. Finish the exercise: git add recipe.txt && git commit -m "Add yeast and sugar" — then both diffs print nothing, which is itself information: no differences.
🎯 Interview questions — Part B
🎯 "What is the meaning of 'Index' in GIT?" — asked verbatim in Interview Coder's 90+ Git questions, Sep 2025; also asked as "Define 'Index'." in InterviewBit's Git questions
The index — synonym: staging area — is the draft of the next commit. It sits between the working tree (your editable files) and the repository (permanent snapshots): git add copies a file's current content into the index; git commit records exactly what the index holds, and nothing else. It exists to decouple editing from recording: you can change ten things and commit them as three clean, separately-explained commits by staging subsets (git add -p stages individual hunks within a file). Plain git diff shows working-tree-vs-index; git diff --staged shows index-vs-last-commit.
The details that separate candidates: an average answer says "where changes go before commit." A strong answer states the copy-at-a-moment semantics — the index holds content photographed at add time, so a file edited after staging is legitimately listed as both staged and modified, and committing takes the staged version. Naming the mechanics adds depth: the index is a real binary file, .git/index, holding the draft's file list with content references — not a folder of copies. And the punchline interviewers reward: commit never looks at your working tree; it serializes the index. Every "Git committed the wrong version" mystery dissolves under that sentence.
🎯 "What is 'git diff'?" — asked (as "What is 'git diff?") in Interview Coder's 90+ Git questions, Sep 2025
git diff computes line-by-line differences between any two of Git's trees or snapshots. The three forms to know cold: git diff — working tree vs index: edits not yet staged; git diff --staged (a synonym of the older --cached) — index vs last commit: exactly what the next commit will record; git diff <A> <B> — any two commits (Module 4 covers naming commits). Output is unified diff format: ---/+++ name the two sides, @@ hunk headers give line positions, -/+ prefix removed/added lines.
The details that separate candidates: an average answer says "shows changes you made." A strong answer maps each form onto the three-trees model rather than memorizing flags, and knows the review habit (git diff --staged before every commit). Practical extras that signal fluency: --stat for a per-file change summary instead of full text, --word-diff for prose, and the fact that diffs are computed on demand from snapshots — Git stores full states, not diffs (Module 1), so diff output is derived, which is why any two arbitrary commits can be compared instantly.
Part C — Everyday file operations
C1. The -a shortcut, and what it skips
git commit -a -m "…" auto-stages every tracked file that has modifications, then commits — collapsing add-then-commit into one step. Two things it deliberately does not do: it never touches untracked files (new files still need an explicit git add — Git will not guess that a new file belongs in history), and it stages everything modified, which throws away exactly the selectivity the staging area exists for. Use -a for genuinely small, everything-belongs-together changes; the moment a commit should not contain all your edits, go back to explicit staging.
🧪 Exercise 2.7 — commit -a on a tracked file
cd ~/git-course/kitchen
echo "flour, water, salt, yeast, sugar, oil" > recipe.txt
git commit -a -m "Add oil to recipe"
git status✅ Expected result — click to reveal
[main b34a325] Add oil to recipe
1 file changed, 1 insertion(+), 1 deletion(-)
On branch main
nothing to commit, working tree cleanWhat to read out of it: no git add was typed, yet the change was committed — -a staged it in passing. The tally reads 1 insertion(+), 1 deletion(-) even though you "changed one word": Git diffs whole lines, so editing a line counts as removing the old line and adding the new one. Now try the same trick with a brand-new file (touch pancakes.txt && git commit -a -m "x") — on a clean tree it fails (exit code 1) saying nothing added to commit but untracked files present, and if a tracked change is also present it commits without the new file — either way proving -a ignores untracked files. Clean up the experiment with rm pancakes.txt if you made it.
C2. Removing and renaming tracked files
Deleting a tracked file with plain rm leaves Git confused: the file is gone from disk, but the staging area still holds it, so status shows an unstaged deletion you must then stage. git rm <file> does both at once — deletes the file and stages the deletion, ready to commit. Likewise git mv old new renames on disk and stages the rename in one move. The variant worth memorizing: git rm --cached <file> removes a file from tracking while leaving it on disk — the standard fix for "I accidentally committed a file that should not be in history-tracking" (you saw status itself suggest it in Exercise 2.4).
🧪 Exercise 2.8 — remove and rename, the Git way
cd ~/git-course/kitchen
echo "temp" > scratch.txt && git add scratch.txt && git commit -q -m "Add scratch note"
git rm scratch.txt # -q on commits above just means quiet
git status -s # short status; D1 decodes this format fully
git commit -q -m "Remove scratch note"
git mv recipe.txt bread.txt
git status
git commit -q -m "Rename recipe to bread"✅ Expected result — click to reveal
rm 'scratch.txt'
D scratch.txt
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
renamed: recipe.txt -> bread.txtWhat to read out of it: git rm echoes what it removed, and short-status shows D — a staged deletion. After git mv, status says renamed: recipe.txt -> bread.txt — one staged operation, not a delete-plus-add pair. Under the hood it is a delete plus an add (Git stores snapshots, not rename instructions — Module 1); Git detects renames by noticing identical content under a new name. The renamed: line is Git being helpful about what it inferred, which is also why renaming and heavily editing a file in the same commit can show up as delete + new file instead: change enough content and the inference fails. Rename in one commit, rewrite in the next, and history stays readable.
C3. Fixing the last commit: --amend
You will constantly commit and then spot the typo in the message, or realize one staged file was missing. git commit --amend redoes the most recent commit: it takes the current staging area plus a message (reuse with --no-edit, or pass a new -m), and replaces the last commit with a corrected one. Not "edits" — replaces: the result is a different snapshot with a different ID. That distinction has consequences once commits are shared with other people, which is exactly why history rewriting gets its own module (Module 10). Until your commits leave your machine, amend freely.
🧪 Exercise 2.9 — amend a typo'd message
cd ~/git-course/kitchen
echo "Serves: 4" > serving.txt && git add serving.txt
git commit -m "Add servng info" # typo committed
git commit --amend -m "Add serving info" # replace it✅ Expected result — click to reveal
[main c42592d] Add servng info
1 file changed, 1 insertion(+)
create mode 100644 serving.txt
[main eba71cd] Add serving info
Date: Mon Sep 7 16:17:32 2026 +0000
1 file changed, 1 insertion(+)
create mode 100644 serving.txtWhat to read out of it: compare the two IDs — c42592d became eba71cd. The amended commit is a new snapshot; the typo'd one is no longer part of your branch's history. The amended output also shows a Date: line: Git kept the original author date rather than pretending the work happened at amend time — your amendment is honest about when the change was made. Same file, same content, different commit identity: hold onto that observation, because Module 3 explains precisely why any change to a commit — even one letter of its message — must produce a new ID.
🎯 Interview questions — Part C
🎯 "How do you change the last commit in git?" — asked verbatim in Interview Coder's 90+ Git questions, Sep 2025
git commit --amend. It rebuilds the most recent commit from the current index: to fix only the message, run it with nothing newly staged and pass -m "better message" (or let the editor open); to add a forgotten change, git add the file first, then git commit --amend --no-edit — the commit now includes it under the original message. Amend replaces the commit: the old ID disappears from the branch and a new one takes its place, preserving the original author date.
The details that separate candidates: an average answer names the command. A strong answer says what amend really is — a replacement, not an edit — and draws the operational line: safe while the commit exists only on your machine; risky once pushed, because teammates may already have the old commit, and the two histories then disagree (the full rules live in history rewriting, Module 10). Bonus fluency: amend only reaches the last commit; anything deeper needs interactive rebase — and knowing that boundary is itself a signal.
🎯 "What is best advisable step in cases of broken commit: Create an additional commit OR amend an existing commit?" — asked verbatim in InterviewBit's 30+ Git questions, 2025
It depends on one question: has the broken commit been shared? If it is still local-only, amend (or otherwise rewrite) — the fix vanishes into a clean history, and nobody ever sees the breakage; a repository whose every commit builds and makes sense is a gift to future readers and to tools like bisect (Module 13). If it has been pushed where others may have based work on it, add a new commit that fixes the problem — rewriting shared history forces everyone downstream to repair their copies, which costs the team more than an ugly commit does.
The details that separate candidates: an average answer picks one option universally ("always amend" or "always new commit"). A strong answer states the shared/unshared boundary as the deciding rule and explains why it decides — rewriting replaces commit IDs, and replaced IDs break everyone who holds the old ones. Naming git revert (Module 7) as the tool that formalizes "fix by new commit" — an inverse commit that undoes a bad one without touching history — turns a good answer into a complete one.
Part D — Status fluency and commit messages
D1. Reading git status -s
The long git status explains itself; the short form git status -s is what you will actually read fifty times a day once fluent. Each file gets one line with a two-character code: the first column is the staging area's state, the second is the working tree's — the three-trees model compressed into two characters. M modified, A added (new file staged), D deleted, R renamed, ?? untracked (both columns, because Git knows nothing about either tree's opinion of it). A space means "no difference in that column's comparison."
| Code | Column 1 says (index vs last commit) | Column 2 says (working tree vs index) | Plain English |
|---|---|---|---|
| M␣ | modified content staged | working tree matches index | edit staged, ready to commit |
| ␣M | index matches last commit | edited since staging/commit | edit not yet staged |
| MM | one version staged… | …and edited again after | Exercise 2.5's double state |
| A␣ | new file staged | unchanged since add | brand-new file entering history |
| D␣ | deletion staged | — | git rm done, not committed |
| ?? | — | — | untracked |
🧪 Exercise 2.10 — produce MM on purpose, then resolve it
cd ~/git-course/kitchen
echo "knead 10 minutes" > method.txt
git status -s # ??
git add method.txt
git status -s # A
echo "knead 12 minutes" > method.txt
git status -s # AM — staged new file, edited after
git add method.txt && git commit -q -m "Add kneading method"
git status -s # (nothing)✅ Expected result — click to reveal
?? method.txt
A method.txt
AM method.txtWhat to read out of it: the file walks through Git's states one command at a time — untracked (??), staged-new (A␣), then AM: column 1 still says "new file staged", column 2 says "edited since". Note the final git status -s prints nothing at all — in short format, a clean tree is silence, which is why scripts and shell prompts use it (--porcelain, its stable cousin, is built for exactly that — you will meet it in CI contexts). If your third line said A instead of AM, you edited before staging rather than after — order is the whole lesson here; run it again.
D2. Commit messages that pay rent
A commit message has two audiences: a human skimming five hundred one-line summaries, and a human who just found this commit while hunting a bug and needs to know why it exists. The widely-followed convention serves both. Subject line: around 50 characters, imperative mood ("Add retry to health check", not "Added" or "Adds" — read it as "if applied, this commit will ___"), no trailing period. Body (optional, after a blank line): the why — what was wrong, why this approach; the diff already shows the what. Multi-line messages come from running git commit with no -m, which opens the editor you configured in Module 1.
🧪 Exercise 2.11 — a body-carrying commit via the editor
cd ~/git-course/kitchen
echo "oven: 220C, 25 min" > baking.txt
git add baking.txt
git commit # NO -m: your Module 1 core.editor opensType this in the editor — subject, blank line, body — then save and close:
Add baking temperature and time
Bread was coming out pale at 180C. 220C with steam for the
first 10 minutes gives proper crust color.✅ Expected result — click to reveal
[main 3f19e02] Add baking temperature and time
1 file changed, 1 insertion(+)
create mode 100644 baking.txtWhat to read out of it: the confirmation shows only the subject line — exactly how most tools will display this commit forever, which is why the subject must stand alone. The body is stored too (Module 4's git log shows full messages). The editor buffer came pre-filled with #-commented status lines; anything starting # is stripped from the final message — they are guidance, not content. If instead you saw Aborting commit due to empty commit message., you closed the editor without saving: no harm done, the staging area still holds baking.txt — just run git commit again.
🎯 Interview questions — Part D
🎯 "What is git status?" — asked verbatim in Interview Coder's 90+ Git questions, Sep 2025
git status reports, for the current repository, how the three trees currently disagree: which files are untracked, which have working-tree edits not yet staged, and which staged changes await commit — each in its own section, with the exact next command suggested in parentheses. It reads state only; it changes nothing, so it is always safe to run. The short form -s compresses each file to a two-column code (index state, working-tree state), and --porcelain guarantees a stable format for scripts.
The details that separate candidates: an average answer says "shows changed files." A strong answer frames the output as three pairwise comparisons (working tree vs index, index vs last commit, plus the untracked bin) rather than one list — which explains how one file can appear in two sections at once — and demonstrates they read the codes: MM means staged then re-edited; committing now takes the staged version. Operational touch: naming --porcelain as the scripting interface, because parsing human-format status in automation is a classic junior mistake.
🎯 "What is a commit in Git?" — asked verbatim in Interview Coder's 90+ Git questions, Sep 2025
A commit is one recorded snapshot of the entire project, created from the staging area's contents, plus metadata: author and email (from config), timestamp, a message explaining why, and a reference to the commit that came before it. Each commit has a unique hash ID by which it can be inspected, compared, restored, or reverted later. Commits are immutable — "changing" one (as --amend does) actually creates a replacement with a new ID.
The details that separate candidates: an average answer says "a saved change." A strong answer corrects the two embedded misconceptions: a commit snapshots the whole project state, not a patch (Module 1's snapshot model), and it is built from the index, not the working tree — uncommitted-because-unstaged edits are excluded by design. Adding that each commit points to its parent, forming a chain that gives history its integrity — modify any old commit and every later ID changes — anticipates the internals (Module 3) and reads as real understanding rather than command recall.
Part E — Production practice
E1. Symptom → cause → diagnosis → fix
Click the symptom you're seeing.
⚠️ fatal: not a git repository (or any of the parent directories)
What is really happening: Neither the current directory nor anything above it contains .git — wrong location, or the repo was never initialized.
Diagnose: pwd · ls -A
The fix: cd to the project, or git init if it truly was never a repo.
⚠️ Committed, but the change "isn't in the commit"
What is really happening: The file was edited after git add — the commit recorded the staged photo, not the disk version.
Diagnose: git status -s (look for MM/AM) · git diff
The fix: git add the file again and git commit --amend --no-edit (if unshared), or make a follow-up commit.
⚠️ New file silently missing from a git commit -a
What is really happening: -a stages modifications to tracked files only; untracked files are never auto-added.
Diagnose: git status (the file sits in Untracked files)
The fix: git add <file> explicitly, then commit.
⚠️ nothing to commit, working tree clean when you expected to commit
What is really happening: Nothing is staged and no tracked file changed — often you already committed, or edited a file outside the repo.
Diagnose: git status · git log --oneline -3 (Module 4)
The fix: Check the last commit already contains the work; otherwise find where your edits actually went (pwd).
⚠️ Deleted a file with rm, status shows a lingering deleted: entry
What is really happening: The disk deletion is not staged — the index still holds the file.
Diagnose: git status -s (shows ␣D)
The fix: git rm <file> (or git add <file> — staging a deletion is also an add), then commit.
⚠️ Secret/config file committed that must stop being tracked (but stay on disk)
What is really happening: The file entered the index at some point; Git keeps tracking until told otherwise.
Diagnose: git status · check what tracks it
The fix: git rm --cached <file> and commit; note past commits still contain it — true scrubbing is Module 10/15 territory, so rotate real secrets.
⚠️ Aborting commit due to empty commit message.
What is really happening: The editor was closed without saving a message — Git treats that as "cancel".
Diagnose: git status (staged changes are intact)
The fix: Run git commit again and save this time, or use -m.
E2. Capstone — four tickets
Worked answer: the phrase "kept testing" is the confession — they edited the file after staging it. git add photographs content at that moment; the later edits stayed working-tree-only, and git commit serialized the index. Diagnose in the repo with git status -s (MM on the file) or by checking git diff is non-empty right after their commit. Fix: git add <file> again, then either git commit --amend --no-edit (if the commit is still local) or a follow-up commit. Prevention habit: git diff --staged immediately before every commit — read the parcel before sealing — and git status immediately after: it should say working tree clean if everything made it in.
Worked answer: -a stages changes to tracked files only — created files are untracked and untouched. The nightly commits will forever miss new files until something adds them. Fix the job: git add -A (stages everything: modifications, deletions, and new files) before the commit, or git add <specific paths> if the job should be conservative about what enters history — for an automated commit, explicit paths are safer: an auto-add -A bot will eventually commit a stray core dump or credentials file dropped into its directory. This ticket is why understanding staging is not interview trivia: the bug lives in the gap between "commit my changes" and what -a actually stages.
Worked answer: two separable problems. (1) Stop tracking, keep the disk file: git rm --cached secrets.env, then commit — history from now on excludes it (.gitignore, Module 14, prevents re-adding). (2) The hour-old commit still contains the secret. Because nothing was pushed, the honest options are amend (if it was the last commit): git rm --cached secrets.env && git commit --amend --no-edit — the replacement commit never contains the file; or, if it is buried deeper, history rewriting (Module 10). Then the judgment call that separates seniors: even for an unpushed commit, if there is any doubt about where the repo directory has been (backups, sync folders), rotate the credentials anyway. Secrets that touched a VCS are treated as leaked until proven otherwise.
Worked answer: propose the two-audience rule. Subject: ~50 chars, imperative mood ("Add X", "Fix Y under Z condition"), specific enough that a reader can decide relevance without opening the diff. Body (when the why is not obvious): what was wrong and why this approach — never a prose copy of the diff. Enforce socially first (review rejects unexplainable messages), mechanically second: a commit-msg hook (Module 15) can enforce format, and squash-merge workflows (Modules 9–10) mean feature-branch wip commits can be tolerated locally and cleaned at merge time — which is usually the compromise that makes engineers accept the rule. Anchor the pitch with one incident: "remember when we spent an hour figuring out what 'fix2' fixed during the outage?" A log message is the only documentation guaranteed to be attached to the change forever.
E3. Documentation reference
| Topic | Official source | What it covers |
|---|---|---|
| Creating repositories | Git Book §2.1 — Getting a Git Repository | git init, initial commit walkthrough |
| File states and staging | Git Book §2.2 — Recording Changes | Tracked/untracked, the status lifecycle diagram, staging |
| git init | git-init manual | All init options, including --bare |
| git add | git-add manual | Pathspecs, -p interactive staging, -A |
| git commit | git-commit manual | -m, -a, --amend, message conventions |
| git status | git-status manual | Long/short/porcelain formats, all two-letter codes |
| git diff | git-diff manual | All comparison modes, --stat, --word-diff |
| git rm / git mv | git-rm manual · git-mv manual | Staged removal, --cached, rename staging |
| Commit message guidelines | Git Book §5.2 — Contributing to a Project | The 50-char subject / blank line / body convention |
E4. Self-assessment
Try each question aloud, from memory, before opening its answer — the gap between your answer and the hidden one is what to study.
1. What single thing does git init create, and what happens to your project files if you delete it?
git init creates exactly one thing: a hidden .git subdirectory containing an empty snapshot database and Git's bookkeeping files — no registration, no daemon, no server. That directory is the entire difference between a folder and a repository. Delete .git and the directory is just a folder again: your project files are untouched, but all recorded history is gone.
2. Explain fatal: not a git repository (or any of the parent directories) — what did Git actually search, and in which direction?
Git looked for a .git directory in the current directory, then walked up the directory tree toward / and found nothing — that upward walk is how Git commands work from any subdirectory of a repository. The error therefore means neither this directory nor anything above it is a repository. It is almost always a location problem, not a Git problem: the reflex fix is pwd and cd, not reinstalling anything. Exit code 128 is Git's fatal-error convention.
3. Name the three trees and the two commands that move content between them, in order.
Working tree (the real files you edit) → staging area, also called the index (the draft of the next snapshot) → repository (the permanent snapshot database inside .git). git add copies a file's current content from the working tree into the staging area; git commit seals whatever the staging area holds into a permanent snapshot.
4. Why does the staging area exist? Give the concrete scenario where committing the whole working tree would be worse.
It exists to make commits deliberate — to decouple editing from recording. The scenario: after two hours of debugging, your working tree holds the fix, plus stray experiments, plus a config tweak. Committing everything would record a bucket of "stuff I had at 6pm"; the staging area lets you pick exactly what belongs in the snapshot — the fix alone — so the commit is a curated unit of change with an honest message.
5. You edit a file, git add it, then edit it again. What does git status -s show, and which version would git commit record right now?
git status -s shows MM (or AM if the file was newly added): column 1 says one version is staged, column 2 says the working tree has been edited since. Committing right now records the staged version — the content photographed at git add time — not what is on disk. To include the newer edit you must run git add again so the newer photo replaces the older one in the draft.
6. What is the difference between git diff and git diff --staged — state both as tree-vs-tree comparisons.
Plain git diff compares the working tree against the staging area — "what have I edited but not yet staged?". git diff --staged compares the staging area against the last commit — "what exactly will the next commit record?". Reading git diff --staged before every commit is the habit that prevents committing debris — the parcel opened for one final look before sealing.
7. What does git commit -a stage, and what does it never stage?
-a auto-stages modifications to every tracked file, then commits. It never stages untracked files — a new file still needs an explicit git add, because Git will not guess that a new file belongs in history. It also stages everything modified, throwing away the selectivity the staging area exists for — so use it only for small, everything-belongs-together changes.
8. What is the difference between git rm <f> and git rm --cached <f>? Which one solves "stop tracking my config file but leave it on disk"?
git rm <f> deletes the file from disk and stages the deletion in one move. git rm --cached <f> removes the file from tracking while leaving it on disk — that is the one that solves "stop tracking my config file but leave it on disk". Remember that past commits still contain the file; true scrubbing is Module 10/15 territory.
9. git commit --amend "edits" the last commit — what is wrong with that phrasing, and what does it actually do to the commit's ID?
Amend does not edit — it replaces: it takes the current staging area plus a message and substitutes a corrected commit for the last one. The result is a different snapshot with a different ID (Exercise 2.9's c42592d became eba71cd), and the typo'd commit is no longer part of your branch's history. Git keeps the original author date, so the amendment is honest about when the work happened. Because IDs change, amend freely only until your commits leave your machine.
10. Recite the commit message convention: subject length, mood, and what belongs in the body. What is the test for a good subject line?
Subject: around 50 characters, imperative mood ("Add retry to health check" — read it as "if applied, this commit will ___"), no trailing period. Body, after a blank line and only when needed: the why — what was wrong and why this approach; the diff already shows the what. The test: could a teammate decide whether this commit is relevant to their bug without opening the diff? "Update config" fails that test; "Raise DB pool size to stop timeout storms" passes.
E5. Sources
🗒️ Cheat sheet — Module 2
| Command | What it does |
|---|---|
| git init · git init <name> | Make the current directory a repository · create directory and initialize it |
| git status · git status -s | Full three-trees report with next-step hints · two-column short codes |
| git add <path> · git add . · git add -A | Stage a file's current content · stage current directory · stage everything incl. deletions |
| git add -p | Stage individual hunks within files, interactively |
| git commit -m "…" · git commit | Snapshot the staging area · same, but open the editor for a subject+body message |
| git commit -a -m "…" | Auto-stage modified tracked files, then commit (never adds untracked) |
| git commit --amend · --amend --no-edit | Replace the last commit with index + new message · keep the old message |
| git diff · git diff --staged | Working tree vs index · index vs last commit (read before committing) |
| git rm <f> · git rm --cached <f> | Delete from disk and stage the deletion · untrack but keep on disk |
| git mv <old> <new> | Rename on disk and stage the rename |
Key concepts: a repository = your files + the .git database; git init creates only .git · Git tracks nothing until told — files are tracked or untracked, and no file is auto-added · three trees: working tree → (add) → staging area/index → (commit) → repository · add photographs content at that moment — edit again and you must add again; one file can be staged and modified simultaneously (MM) · commit serializes the index, never the working tree · commits are immutable; amend replaces, producing a new ID · status codes: column 1 = index vs last commit, column 2 = working tree vs index · subject ~50 chars imperative; body says why.