Module 4 — Reading History (git log, show, diff ranges)

Updated 8 September 2026

Module 4 — Reading History (git log, show, diff ranges). A repository's value is its history — but only to people who can interrogate it. This module turns the parent chain you saw in Module 3 into answers: what happened, when, by whom, to which files, and what exactly changed. These are the commands you will run during incidents, reviews, and every "who touched this?" conversation.

🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)

Before you start. You need Module 2 (the kitchen repository, which this module keeps reading) and Module 3 (commits chained by parent pointers; HEAD; hashes). Tools: just Git and a terminal.

Part A — git log: walking the chain

A1. The log is the parent chain, printed

git log does exactly what you did by hand in Module 3, tirelessly: start at HEAD's commit, print it, follow its parent pointer, print that, and so on until a commit with no parent (your root commit). Newest first, because the chain only points backwards. Plain git log prints each commit's hash, author, date, and full message; git log --oneline compresses each to one line — short hash plus subject — and is the form you will type most, because history questions usually start with a skim.

🧪 Exercise 4.1 — the full log, then the skim view
bash
cd ~/git-course/kitchen
git log | head -12      # first two entries of the full form
git log --oneline       # the whole history, one line each
Expected result — click to reveal
plain text
commit cff4379b8d3816585024559acacc1a8c68ed5cd7
Author: Aisha Rahman <[email protected]>
Date:   Mon Sep 7 16:28:01 2026 +0000

    Add copy of serving info

commit 04776eb6b5ed6e367dc3eff4115eaccd7ed7b4a0
Author: Aisha Rahman <[email protected]>
Date:   Mon Sep 7 16:27:47 2026 +0000

    Add baking temperature and time

cff4379 Add copy of serving info
04776eb Add baking temperature and time
9a73162 Add kneading method
fa851d0 Add serving info
60253b5 Rename recipe to bread
4ee3a90 Remove scratch note
febb461 Add scratch note
b65b55d Add oil to recipe
8117cac Add yeast and sugar
98b1d78 Add basic bread recipe

What to read out of it: the full form is Module 3's commit object, dressed up — hash, author, date, message (the tree and parent lines are consumed by the walk rather than printed). Your hashes differ; the ten subjects should match if you followed Module 2, and read bottom-up they tell the project's story — that is your commit-message discipline paying out for the first time. If plain git log filled the screen and swallowed your prompt, you are in the pager (less): arrows/space to scroll, q to quit. Git pipes long output through it automatically; q is the exit, not Ctrl-C.

Real-world analogy — the ship's logbook

A ship's logbook is written forward but read backwards: when something goes wrong, the captain starts at the latest entry and works back toward the cause. Each entry is dated, signed, and explains itself. git log is that reading: newest first, every entry signed (author), self-explaining (message) — and the skim view is running your thumb down the margin dates until something catches your eye.

Where the analogy stops working. A logbook has one line per event, chosen by whoever held the pen. Git's log is derived from the snapshots themselves — it can be re-rendered with any level of detail after the fact: one line per commit today (--oneline), full patches tomorrow (-p), only entries touching one file the day after (-- <path>). You are not reading a fixed document; you are querying a database that can print itself a thousand ways.

A2. Seeing what each commit changed: -p and --stat

Official docs: git-log manual

The log's message says why; two flags add the what. git log --stat appends a per-file change summary to each entry (which files, how many lines). git log -p appends the full patch — the actual diff each commit introduced, computed on demand between the commit's snapshot and its parent's (Module 3's "diffs are derived, not stored", visible live). -p output gets long; combine with -1, -2 (limit to N commits) or a path filter from A3.

🧪 Exercise 4.2 — one commit, three zoom levels
bash
cd ~/git-course/kitchen
git log --oneline -1     # zoom 1: subject only
git log --stat -1        # zoom 2: which files, how much
git log -p -1            # zoom 3: the exact lines (q to quit the pager)
Expected result — click to reveal
plain text
cff4379 Add copy of serving info
commit cff4379b8d3816585024559acacc1a8c68ed5cd7
Author: Aisha Rahman <[email protected]>
Date:   Mon Sep 7 16:28:01 2026 +0000

    Add copy of serving info

 serving-copy.txt | 1 +
 1 file changed, 1 insertion(+)
commit cff4379b8d3816585024559acacc1a8c68ed5cd7
...same header...
diff --git a/serving-copy.txt b/serving-copy.txt
new file mode 100644
index 0000000..44e0f28
--- /dev/null
+++ b/serving-copy.txt
@@ -0,0 +1 @@
+Serves: 4

What to read out of it: --stat's summary line (serving-copy.txt | 1 +) shows file, line count, and a +/- bar — at a glance you see the commit's shape before reading any code. In the -p patch, the markers of a file birth: new file mode 100644, an old side of /dev/null, and old-blob ID 0000000 (no previous content existed). Note the new side's blob ID: 44e0f28 — the serving-info blob from Module 3's dedup exercise, recognized on sight now. Diff literacy compounds: every tool you will ever use (reviews, CI, tickets) speaks this format.

A3. Asking precise questions: log filters

Real repositories have tens of thousands of commits; nobody scrolls. You filter. The everyday six: -N (only the N newest), --author="name" (by committer), --grep="text" (search commit messages), --since="2 weeks ago" / --until="2026-01-01" (time windows, and yes, plain English works), and — the one you will use most — -- <path>: only commits that touched that file or directory. The -- separates revisions from paths (you saw Git itself suggest this convention in an error message once; now you know what it disambiguates). Filters combine freely: git log --oneline --author=Rahman --since="1 week ago" -- deploy/ is a sentence: what did Rahman change under deploy/ this week?

🧪 Exercise 4.3 — interrogate the kitchen's history
bash
cd ~/git-course/kitchen
git log --oneline --grep="serving"       # messages mentioning serving
git log --oneline -- bread.txt           # commits touching bread.txt...
git log --oneline --follow -- bread.txt  # ...and the same, following the rename
Expected result — click to reveal
plain text
cff4379 Add copy of serving info
fa851d0 Add serving info
60253b5 Rename recipe to bread
60253b5 Rename recipe to bread
b65b55d Add oil to recipe
8117cac Add yeast and sugar
98b1d78 Add basic bread recipe

What to read out of it: the grep finds both serving-related commits by message text (it searches messages, never file contents — content search is Module 13). Then the trap this exercise exists for: plain -- bread.txt returns one commit, as if the file had no life before its rename — the path filter matches the name literally, and bread.txt only existed from the rename onward. --follow re-walks the history noticing the rename (blob-reuse inference, Module 3) and recovers the file's full biography back to Add basic bread recipe. On any long-lived repo, file histories without --follow routinely lie by omission.

Now imagine this at 500 hosts. During an incident, "what changed?" is the first question and git log is its answer machine — if messages and commits were disciplined. The team habit that pays off here: deploys tag or record the commit they shipped (Module 12), so git log --oneline <last-good>..<deployed> (C2's syntax) lists exactly the suspect changes, and --stat shows whether any touched the failing subsystem. Teams without that discipline scroll Slack instead of history. Same tool, opposite outcomes — decided months earlier by commit hygiene.

🎯 Interview questions — Part A

🎯 "How do you view the commit history in Git?" — asked verbatim in Final Round AI's 90+ Git Interview Questions, Aug 2025

git log — it walks the parent chain from HEAD backwards, printing each commit's hash, author, date, and message. The high-value variants: --oneline for a skimmable summary; --stat to add per-file change counts; -p to add full patches; --graph to draw the branch structure; and filters that turn it into a query tool — -N, --author, --grep (messages), --since/--until, and -- <path> for one file's history (with --follow to trace through renames). git show <commit> inspects one commit in detail.

The details that separate candidates: an average answer stops at "git log, and --oneline for short." A strong answer demonstrates query thinking — composing filters to answer real questions ("Rahman's changes to deploy/ since Tuesday") — and knows the two subtleties that bite: path-filtered logs silently truncate at renames without --follow, and --grep searches messages, not code. Mentioning that log output is derived on demand from snapshots (so any zoom level is available after the fact) connects the command to the storage model, which is where strong interviews go.

Part B — git show: one thing, in full

B1. Showing a commit

Official docs: git-show manual

git log surveys many commits; git show <commit> examines one: header, full message, and the patch it introduced — equivalent to finding it in git log -p, without the scrolling. With no argument it shows HEAD. It accepts any commit name: full hash, short hash, and (from Part C) relative names like HEAD~2. Two flags turn it into the "what did this commit touch" tool interviews ask about: --stat (per-file summary) and --name-only (just the file list).

🧪 Exercise 4.4 — inspect one historical commit, three ways

Use your hash for Add kneading method from your git log --oneline output.

bash
cd ~/git-course/kitchen
git show 9a73162                                   # full: header + message + patch
git show --name-only --format="%h %s" 9a73162      # just the file list
git show --stat --format="%h %s" 9a73162           # file list with change sizes
Expected result — click to reveal
plain text
commit 9a73162feb3e3337760f0e93b6a981cfe24c199b
Author: Aisha Rahman <[email protected]>
Date:   Mon Sep 7 16:27:47 2026 +0000

    Add kneading method

diff --git a/method.txt b/method.txt
new file mode 100644
index 0000000..ccaa999
--- /dev/null
+++ b/method.txt
@@ -0,0 +1 @@
+knead 12 minutes
9a73162 Add kneading method

method.txt
9a73162 Add kneading method
 method.txt | 1 +
 1 file changed, 1 insertion(+)

What to read out of it: the full form is a complete answer to "what is this commit?" — you will paste exactly this into tickets. The --format="%h %s" part previews D1's formatting language: %h short hash, %s subject — here it just keeps the header to one line so the file list stands alone. --name-only is the scripting-friendly answer to "which files did commit X change"; --stat is the human version. All three read the same immutable objects — zoom level is the only difference.

B2. Showing a file as it was at any commit

Official docs: git-show manual

The second face of git show: git show <commit>:<path> prints a file's content as of that commit — without touching your working tree. This is Module 3's commit→tree→blob walk packaged as one command, and it is how you answer "what did the config look like before Tuesday's deploy?" safely, mid-incident, with zero risk to your current files.

🧪 Exercise 4.5 — read the past, and two instructive failures

Use your hash for Add basic bread recipe (the oldest line in your --oneline output).

bash
cd ~/git-course/kitchen
git show 98b1d78:recipe.txt     # the recipe as of the first commit
git show HEAD:recipe.txt        # the same file at HEAD...
echo "exit code: $?"
git show HEAD:cake.txt          # ...and a file that never existed
echo "exit code: $?"
Expected result — click to reveal (the last two commands fail on purpose)
plain text
flour, water, salt
fatal: path 'recipe.txt' does not exist in 'HEAD'
exit code: 128
fatal: path 'cake.txt' does not exist in 'HEAD'
exit code: 128

What to read out of it: the first line is time travel — the original three-ingredient recipe, read straight from the blob the first commit's tree names, while your working tree stays untouched. Then the good failure: recipe.txt does not exist in HEAD — you renamed it to bread.txt in Module 2, and the error names the revision it searched, not just the file. A path exists per commit, not globally; yesterday's name needs yesterday's commit. The second failure reads identically because, to Git, "renamed away" and "never existed" are the same fact about this tree — there is no rename record to consult (Module 3), only trees that do or do not list a name.

🎯 Interview questions — Part B

🎯 "How do you find a list of files that have changed in a particular commit?" — asked verbatim in Edureka's Git Interview Questions, updated Nov 2024

Day to day: git show --name-only <commit> (file names), git show --stat <commit> (names plus change sizes), or --name-status for names with a change letter — A added, M modified, D deleted, and R with a similarity score for renames (R100 = content identical). In scripts and hooks, the plumbing form many interviewers expect by name: git diff-tree --no-commit-id --name-only -r <commit> — pure file list, no header to strip, stable output contract. All of these compute the commit-vs-parent diff on demand; nothing stores "the files this commit changed" as data.

The details that separate candidates: an average answer gives one command. A strong answer separates the human tools (show --stat) from the plumbing (diff-tree -r) and knows why plumbing wins in automation (porcelain output formats may change; plumbing is a contract — the same reasoning as Module 3's rev-parse-not-cat rule). The flourish: --name-status beats --name-only in CI decisions, because "modified" and "deleted" usually route to different actions — deploy vs cleanup.

Part C — Naming commits without hashes

C1. Relative names: HEAD~N

Official docs: gitrevisions manual

Copy-pasting hashes gets old fast. Git's revision syntax names commits by position: HEAD~1 is HEAD's parent, HEAD~2 its grandparent, HEAD~N counts N steps up the parent chain (~ alone means ~1). The names work anywhere a hash works — show, diff, log — because they resolve through the same machinery (git rev-parse will show you the resolution any time). There is a sibling operator ^HEAD^ also means "parent" — whose real purpose is choosing between parents when a commit has more than one; single-parent chains like yours make ~1 and ^ identical, and the distinction becomes meaningful with merges in Module 6.

🧪 Exercise 4.6 — walk the chain by name, and step off its end
bash
cd ~/git-course/kitchen
git rev-parse --short HEAD      # resolve a position to a short hash...
git rev-parse --short HEAD~1    # ...its parent...
git rev-parse --short HEAD~3    # ...three steps up (one revision per call)
git show --format="%h %s" -s HEAD~3         # -s: header only, no patch
git log --oneline HEAD~99                   # 99 generations up — off the end
echo "exit code: $?"
Expected result — click to reveal (the last command fails on purpose)
plain text
cff4379
04776eb
fa851d0
fa851d0 Add serving info
fatal: ambiguous argument 'HEAD~99': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
exit code: 128

What to read out of it: three names, three hashes — check them against your --oneline output: HEAD~1 is the second line, HEAD~3 the fourth. (One revision per --short call: the flag implies verification of a single revision — plain git rev-parse A B C would resolve several at once, to full-length hashes.) The failure teaches Git's parsing model: your history is 10 commits deep, so HEAD~99 resolves to nothing — and because Git could not resolve it as a revision, it wonders aloud whether you meant a path called HEAD~99 (that is what "ambiguous argument" means here, and why the error suggests --, the separator from A3). Once you can read this error, a whole family of confusing Git messages becomes legible.

C2. Ranges: what happened between A and B

The question behind every release note and every incident review is "what happened between these two points?" — and it has two different answers, with a command each. Which commits? git log A..B — the two-dot range: commits reachable from B but not from A (walk back from B, stop where A's history begins). What net change? git diff A B — one diff between the two snapshots, as if all those commits were a single edit. Keep the pairing straight: log lists steps, diff shows the sum of them.

Counter-intuitive: git diff A..B (two dots) is also legal and means exactly the same as git diff A B — for diff, the dots add nothing. This wrinkle exists because log and diff interpret ranges differently: log A..B is genuinely a set operation on the chain, while diff only ever compares two endpoints. There is also a three-dot form (A...B) that means different things to log and diff again — both involve where two lines of history forked, which needs Module 6's merge concepts; it is taught there. Until then: log A..B for lists, diff A B for changes, and no dots in your diffs.
🧪 Exercise 4.7 — steps vs sum

Use your hashes for Add serving info and Add basic bread recipe.

bash
cd ~/git-course/kitchen
git log --oneline fa851d0..HEAD     # the steps: commits after "Add serving info"
git diff --stat 98b1d78 HEAD        # the sum: net change since the first commit
Expected result — click to reveal
plain text
cff4379 Add copy of serving info
04776eb Add baking temperature and time
9a73162 Add kneading method
 baking.txt       | 1 +
 bread.txt        | 1 +
 method.txt       | 1 +
 recipe.txt       | 1 -
 serving-copy.txt | 1 +
 serving.txt      | 1 +
 6 files changed, 5 insertions(+), 1 deletion(-)

What to read out of it: the range lists three commits — everything after fa851d0, excluding it (A..B excludes A, includes B; remember it as "from A, to B"). The diff-stat is the net story of the whole history: recipe.txt | 1 - and bread.txt | 1 + — the rename shows as a remove-plus-add here even though rename detection is on by default — an endpoint diff only sees the two snapshots, and the first commit's recipe.txt (flour, water, salt) shares no identical line with HEAD's bread.txt (six ingredients now), so the content-similarity matching (Module 3) has nothing to pair. The rename is only visible in the steps, where content matched exactly — which is precisely why log --follow found it and this sum did not. Steps tell you how you got here; the sum tells you where you are relative to then. Incidents need both.

🎯 Interview questions — Part C

🎯 "How do you compare differences between commits, branches, or files?" — asked verbatim in Final Round AI's 90+ Git Interview Questions, Aug 2025

One command, three targets. Between the three trees: git diff (working tree vs index), git diff --staged (index vs HEAD) — Module 2. Between any two commits: git diff <A> <B>, naming them by hash or revision syntax (git diff HEAD~3 HEAD = "net change of the last three commits"); add --stat for a summary or -- <path> to scope to files. Between branches — a branch name resolves to its tip commit (Module 3: a branch is a pointer), so git diff main feature compares the two tips with the identical machinery; Module 5 makes branch names everyday objects. For a single file across time: git diff <A> <B> -- <file>, or git show <commit>:<file> to just read one side.

The details that separate candidates: an average answer lists git diff variants. A strong answer states the unifying rule — everything resolves to two trees being compared — so branches, tags, hashes, and HEAD~N are all the same case, and then flags the notation traps: for diff, A..B equals A B (the dots do nothing), while A...B means "B vs the common ancestor" — the form code-review tools actually use to show "what this branch adds" (mechanics in Modules 6 and 9). Knowing which diff a PR shows is a production-relevant detail most candidates have never examined.

Part D — Formatting history for humans and machines

D1. --format and --graph

Official docs: git-log manual

When log output feeds a report, a changelog, or a script, you dictate its shape: git log --format="..." builds each commit's line from placeholders — %h short hash, %H full hash, %an author name, %ar relative date ("2 days ago"), %ad absolute date, %s subject. And git log --graph draws the commit topology as ASCII art in the left margin — on your single-chain history it is a straight line of *s, which is precisely worth seeing once now: when Module 5 introduces branches, the moment that line forks will mean something.

The published interview-question corpus is thin on formatting specifically — it shows up inside broader git log answers (as in Part A's question) rather than as standalone questions, so this section carries no separate interview block: the skill here is fluency, not recall.

🧪 Exercise 4.8 — a changelog line and the shape of history
bash
cd ~/git-course/kitchen
git log -3 --format="%h  %an  %ar  %s"
git log --oneline --graph | head -4
Expected result — click to reveal
plain text
cff4379  Aisha Rahman  11 minutes ago  Add copy of serving info
04776eb  Aisha Rahman  12 minutes ago  Add baking temperature and time
9a73162  Aisha Rahman  12 minutes ago  Add kneading method
* cff4379 Add copy of serving info
* 04776eb Add baking temperature and time
* 9a73162 Add kneading method
* fa851d0 Add serving info

What to read out of it: the format string produced ready-to-paste report lines (your relative dates reflect when you actually did Module 2 — "2 days ago" is fine). In the graph, each * is a commit and the implicit vertical line connecting them is the parent chain — one unbroken lane, because nothing has ever forked. Take a mental photograph of this straight line: it is the "before" picture for Module 5, where a second lane appears and --graph becomes indispensable rather than decorative.

Part E — Production practice

E1. Symptom → cause → diagnosis → fix

Click the symptom you're seeing.

⚠️ Terminal "stuck" after git log, prompt gone

What is really happening: The output went to the pager (less) — Git does this automatically for long output.

Diagnose: Nothing — you are in the pager

The fix: q quits; use --no-pager or git config core.pager cat for scripts.

⚠️ git log -- <file> shows suspiciously little history

What is really happening: The file was renamed; a literal path filter only matches the current name.

Diagnose: git log --follow -- <file> and compare counts

The fix: Use --follow for file biographies, and keep renames in their own commits so inference works (Module 2).

⚠️ fatal: ambiguous argument '<x>': unknown revision or path not in the working tree

What is really happening: The token resolves as neither a commit name nor an existing path — a typo'd hash, an overshot HEAD~N, or a deleted file's path without a revision.

Diagnose: git rev-parse <x> to test resolution · git log --oneline | wc -l for depth

The fix: Fix the name; when passing a deleted file's path, give a revision where it existed and separate with --.

⚠️ git show <commit>:<path>fatal: path ... does not exist in ...

What is really happening: Paths exist per-commit; at that commit the file was absent or named differently.

Diagnose: git show --name-only <commit> to list what that tree holds

The fix: Pick the right commit, or the file's name as of that commit.

⚠️ --grep finds nothing though the code change definitely exists

What is really happening: --grep searches commit messages only, not file contents.

Diagnose: Confirm with git log --grep=<term> --oneline

The fix: Search content with pickaxe/git grep (Module 13); meanwhile filter by path with git log -- <file>.

⚠️ A script parsing git log broke after a Git upgrade or on a teammate's machine

What is really happening: Human-facing (porcelain) output formats are not a stable contract; locale/config also alter them.

Diagnose: Reproduce with the other Git version; check their format.*/alias config

The fix: Script against explicit --format= strings or plumbing (diff-tree, rev-parse) — never default log output.

E2. Capstone — four tickets

Ticket 1 — "What went out in yesterday's deploy?" Release manager needs, in ten minutes: the list of changes between the previous deploy (commit d4e5f6a) and yesterday's (b7c8d9e), which files were touched, and whether anything touched config/.

Worked answer: three commands, in the steps-vs-sum frame. The steps: git log --oneline d4e5f6a..b7c8d9e — every commit that went out (range excludes the old deploy itself, which is correct here). The sum: git diff --stat d4e5f6a b7c8d9e — net files-and-lines picture for the deploy email. The scoped question: git log --oneline d4e5f6a..b7c8d9e -- config/ — if this prints anything, config changed, and git diff d4e5f6a b7c8d9e -- config/ shows exactly how. The habit that made this a ten-minute job: deploys record their commit hash somewhere retrievable (CI logs, tags — Module 12 formalizes it).

Ticket 2 — "Who last touched the timeout value, and why?" A production incident traces to a timeout in app.conf. The on-call needs the change's author, date, and stated reason — without checking anything out.

Worked answer: git log -p -- app.conf and search the patches for the timeout line (/timeout inside the pager) — the newest commit whose patch shows that line changing is the culprit; its header gives author and date, its message the reason (this is where message discipline from Module 2 becomes incident tooling). Read the change in full with git show <hash>, and the file's before/after with git show <hash>~1:app.conf versus git show <hash>:app.conf — all read-only, working tree untouched. If the file was ever renamed, add --follow. Module 13 teaches the faster precision tools for exactly this hunt (blame, pickaxe); today's toolbox already gets it done.

Ticket 3 — "Generate a weekly change report for the platform team." Management wants, every Monday: last week's commits — hash, author, relative age, subject — plus a per-file change summary, as plain text a script can email.

Worked answer: two commands, script-safe by construction. The list: git log --since="7 days ago" --format="%h %an %ar %s" — an explicit format string, immune to Git-version and locale drift (E1's last row is the rule being applied). The summary: git diff --stat "HEAD@{7.days.ago}" HEAD works but depends on reflog state (Module 7); the robust version records last Monday's HEAD hash and runs git diff --stat <recorded> HEAD. Add --author filters per team if needed. The design point worth saying in review: report generators must use pinned format strings and stored hashes — the two anti-flakiness rules this module keeps landing on.

Ticket 4 — "The audit needs the config as of March 31." Compliance asks for payment.conf exactly as it stood at end of Q1, plus evidence of every change to it during Q1.

Worked answer: find the last commit touching the repo on/before the date: git log -1 --until="2026-03-31" --format="%H %ad %s" (add -- payment.conf to get the last change to that file instead — different question, ask which they mean; auditors usually want repo-state, engineers assume file-change). Then extract: git show <hash>:payment.conf > payment.conf.2026-03-31 — the file materialized without touching the working tree. The Q1 evidence: git log -p --since=2026-01-01 --until=2026-03-31 -- payment.conf — every change with full patches, author, dates. Caveat to state in the audit notes: --until filters by commit date metadata, which the committing machine wrote (Module 3: it is hashed content, so it cannot be silently altered later — but it could have been recorded wrong at creation); for legally-binding timestamps, cross-reference the host's push/audit logs (Modules 8–9).

E3. Documentation reference

TopicOfficial sourceWhat it covers
git loggit-log manualAll filters, formats, --follow, --graph
Viewing history (tutorial)Git Book §2.3 — Viewing the Commit HistoryGuided tour of log options with output examples
git showgit-show manualCommits, <rev>:<path>, format options
Revision syntaxgitrevisions manualHEAD~N, ^, ranges, @{...} — the complete grammar
git diff between revisionsgit-diff manualTwo-endpoint diffs, --stat, -M, dots semantics
git diff-tree (plumbing)git-diff-tree manualScript-stable per-commit file lists

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 does git log actually walk, and why is output newest-first?

It walks the parent chain: start at HEAD's commit, print it, follow its parent pointer, print that, and so on until a commit with no parent — the root commit. Output is newest-first because the chain only points backwards: each commit knows the one before it, never the one after. It is exactly the walk you did by hand in Module 3, done tirelessly.

2. Your terminal "hangs" after git log. What happened and what is the one key that fixes it?

Nothing hung — the output went to the pager (less), which Git pipes long output through automatically. Arrows and space scroll; the one key that fixes it is q, which quits the pager — not Ctrl-C. For scripts, --no-pager or git config core.pager cat disables it.

3. Three zoom levels for reading a commit's impact — name the flags from coarsest to finest.

Coarsest: --oneline — short hash plus subject only. Middle: --stat — which files changed and how many lines, with a +/- bar. Finest: -p — the full patch, the actual diff each commit introduced, computed on demand between the commit's snapshot and its parent's.

4. Why can git log -- <file> silently hide most of a file's history, and what flag repairs it?

The path filter matches the name literally, and a renamed file only existed under its current name from the rename onward — so the log truncates there, as if the file had no earlier life. --follow repairs it: it re-walks the history noticing the rename (blob-reuse inference from Module 3) and recovers the file's full biography. On any long-lived repo, file histories without --follow routinely lie by omission.

5. What does --grep search — and what does it not search?

--grep searches commit messages only. It never searches file contents — content search is pickaxe/git grep territory (Module 13). Meanwhile, the closest filter for "changes to this code" is a path filter: git log -- <file>.

6. Resolve from memory: HEAD~2. And what does ^ exist to distinguish, once commits can have two parents?

HEAD~2 is HEAD's grandparent — two steps up the parent chain (~ alone means ~1). ^ exists to choose between parents when a commit has more than one, which happens with merges (Module 6). On a single-parent chain, ~1 and ^ are identical.

7. git log A..B: which endpoint is excluded? State the range as a reachability sentence.

A is excluded; B is included — remember it as "from A, to B". The reachability sentence: commits reachable from B but not from A — walk back from B and stop where A's history begins.

8. "Steps vs sum": which command answers each, for the same two commits?

Steps — which commits happened between the two points — is git log A..B. Sum — the net change — is git diff A B: one diff between the two snapshots, as if all those commits were a single edit. Steps tell you how you got here; the sum tells you where you are relative to then. Incidents need both.

9. Why is git diff A..B a red herring, and which three-dot form should you not use yet?

Because for diff the two dots add nothing: git diff A..B means exactly the same as git diff A Bdiff only ever compares two endpoints, while log A..B is genuinely a set operation on the chain. The form to avoid for now is A...B (three dots): it means different things to log and diff again, and both involve where two lines of history forked — Module 6's merge concepts. Until then: log A..B for lists, diff A B for changes, no dots in your diffs.

10. A script parses default git log output. Name the two safer alternatives and the rule they embody.

Alternative one: an explicit --format= string (e.g. %h %an %ar %s), immune to Git-version and locale drift. Alternative two: plumbing commands like diff-tree and rev-parse, whose output is a stable contract. The rule: human-facing (porcelain) output formats are not stable — scripts pin format strings or use plumbing; humans get porcelain.

11. How do you print a file exactly as it was at a given commit, without touching the working tree?

git show <commit>:<path> — it prints the file's content as of that commit, read straight through the commit→tree→blob walk from Module 3, with zero risk to your current files. It is how you answer "what did the config look like before Tuesday's deploy?" safely, mid-incident.

12. In git show <commit>:<path> errors, what does "does not exist in '<commit>'" tell you that "file not found" would not?

It names the revision Git searched, telling you a path exists per commit, not globally — the file may well exist in other commits, just not in this one's tree. Yesterday's name needs yesterday's commit. And because there is no rename record to consult (Module 3), "renamed away" and "never existed" read identically: both are simply names this tree does not list.

E5. Sources

Interview questions in this module were captured verbatim from: Final Round AI — 90+ Git Interview Questions (Aug 11, 2025) and Edureka — Git Interview Questions (updated Nov 25, 2024). A corpus note: published interview questions cover git log/show/diff comparisons well, but revision syntax (HEAD~N, ranges) appears only inside answers rather than as standalone questions — so Part D carries no interview block, and Part C's block is the closest published question rather than an invented one. Technical claims were verified against the official Git documentation in E3; every command and output on this page was executed on Git 2.43.0 on Linux against the Module 2 kitchen repository. Commit hashes and relative dates in outputs will differ on your machine.

🗒️ Cheat sheet — Module 4

CommandWhat it does
git log · git log --onelineWalk the parent chain from HEAD, full · one line per commit
git log --stat · git log -pAdd per-file change summaries · add full patches
git log -N · --author=X · --grep=TEXT · --since/--untilNewest N · by author · by message text · by time window
git log -- <path> · git log --follow -- <file>Commits touching a path · same, traced through renames
git log A..BCommits reachable from B but not A ("from A, to B"; excludes A)
git log --graph · git log --format="%h %an %ar %s"ASCII topology · custom line per commit (script-safe)
git show <commit>One commit in full: header, message, patch (defaults to HEAD)
git show --name-only <c> · --name-status · --statFiles a commit changed: names · names + A/M/D/R100-style letters · with sizes
git show <commit>:<path>A file's content as of that commit, working tree untouched
git diff <A> <B> · git diff --stat <A> <B> · -MNet change between two snapshots · summary form · -M<n>% tunes rename-detection similarity (on by default)
git diff-tree --no-commit-id --name-only -r <c>Plumbing file list for scripts and hooks
HEAD~NN steps up the parent chain (~ = ~1; ^ selects among multiple parents — Module 6)

Key concepts: log = the parent chain, printed; every zoom level is derived on demand from snapshots · pager: q quits · path-filtered logs truncate at renames without --follow · --grep searches messages, never content · A..B = reachable from B, not from A — steps; diff A B — the sum · for diff, A..B equals A B; three-dot forms wait for Module 6 · revision names (HEAD~N, branch names) resolve to hashes and work everywhere hashes do · scripts pin --format= strings or use plumbing; humans get porcelain.

Next: Module 5 — Branching — your history has been one straight lane. Module 5 forks it: branches as the 41-byte pointers you met in Module 3, switching, and detached HEAD — taught as a feature instead of an accident.
Spotted a mistake or want something added? Send me a note.