Lesson 2 - Inspecting Changes

Welcome to Inspecting Changes

In the last lesson you learned to read your history. Now you’ll learn to read your changes — line by line, before they ever become a commit. The tool for that is git diff, and it answers a question you’ll ask many times a day: “what exactly did I change?” Used well, it lets you review your edits before staging them, confirm what you’re about to commit, and compare any two points in your project’s past.

The catch is that git diff speaks a specific dialect — the unified diff format — and it compares different things depending on the flags you give it. Once you can read the format and know which two versions a given command is comparing, git diff becomes one of the most useful commands in Git.

We’ll keep working with the SkyLog repository from earlier in the course.

By the end of this lesson, you will be able to:

  • Read the unified diff format: the diff --git header, @@ hunk headers, and +/-/context lines
  • Use git diff to see unstaged changes (working directory vs. staging area)
  • Use git diff --staged to see what you’re about to commit (staging area vs. last commit)
  • Compare any two commits — for one file or as a whole-project summary with --stat

Let’s begin.


Reading the Unified Diff Format

Before we worry about which versions Git is comparing, let’s learn to read its output. Suppose you’ve added one line to the bottom of observations.md and haven’t staged it yet. Running git diff shows this:

$ git diff
diff --git a/observations.md b/observations.md
index 34ca847..0121552 100644
--- a/observations.md
+++ b/observations.md
@@ -1,2 +1,3 @@
 - Orion Nebula, naked eye, 9:40pm
 - Jupiter and four moons through binoculars
+- [2026-03-06 21:10] Andromeda Galaxy, averted vision

Every diff has the same shape. Read it top to bottom:

  • diff --git a/observations.md b/observations.md — the header. It names the file being compared. By convention a/ is the “before” version and b/ is the “after” version.
  • index 34ca847..0121552 100644 — Git’s internal blob hashes for the before and after content, plus the file mode (100644 is a normal file). You can ignore this line in practice. Your index hashes will differ from these — they’re computed from your file’s exact content.
  • --- a/observations.md and +++ b/observations.md — the two sides of the comparison: --- marks the old version, +++ marks the new version.
  • @@ -1,2 +1,3 @@ — a hunk header. A “hunk” is a contiguous block of change. This one reads: in the old file, this block starts at line 1 and spans 2 lines (-1,2); in the new file it starts at line 1 and spans 3 lines (+1,3). The file grew by one line.
  • The lines below the hunk header are the change itself:
    • A line starting with a space is a context line — unchanged, shown so you can see the surrounding code. The two - Orion/Jupiter lines here are context.
    • A line starting with + was added.
    • A line starting with - would have been removed (there are none in this diff).

So this diff says, in plain English: “observations.md gained one new line at the end — the Andromeda entry — while the two existing lines stayed the same.” Once you can read a hunk this way, every git diff, git show, and code review on GitHub becomes legible.


git diff — Your Unstaged Changes

Plain git diff compares your working directory (the files as they sit on disk) against the staging area (what you’ve git add-ed). In other words, it shows changes you have made but not yet staged. That’s exactly the diff you just read:

$ git diff
diff --git a/observations.md b/observations.md
index 34ca847..0121552 100644
--- a/observations.md
+++ b/observations.md
@@ -1,2 +1,3 @@
 - Orion Nebula, naked eye, 9:40pm
 - Jupiter and four moons through binoculars
+- [2026-03-06 21:10] Andromeda Galaxy, averted vision

This is the command you’ll run constantly: edit some files, then git diff to review your work before you decide what to stage. It’s a low-stakes habit that catches typos, stray debug lines, and accidental edits before they ever enter a commit.

One detail worth internalizing now: if there are no unstaged changes, git diff prints nothing at all. A blank result isn’t an error — it means your working directory matches your staging area. Many beginners run git diff, see nothing, and assume something broke. It usually means you’ve already staged everything (more on that next).


git diff --staged — What You’re About to Commit

The moment you stage the change, plain git diff goes quiet — because the working directory now matches the staging area, and that’s all plain git diff compares:

$ git add observations.md
$ git diff

Nothing prints. The change didn’t vanish; it just moved into the staging area, which plain git diff doesn’t look at. To see staged changes, use git diff --staged (its older alias git diff --cached does the same thing). This compares the staging area against the last commit (HEAD) — that is, it shows you exactly what your next commit will contain:

$ git diff --staged
diff --git a/observations.md b/observations.md
index 34ca847..0121552 100644
--- a/observations.md
+++ b/observations.md
@@ -1,2 +1,3 @@
 - Orion Nebula, naked eye, 9:40pm
 - Jupiter and four moons through binoculars
+- [2026-03-06 21:10] Andromeda Galaxy, averted vision

The diff is identical to before — same file, same hunk, same added line — but the comparison is different. Earlier it was “working directory vs. staging”; now it’s “staging vs. last commit.” Running git diff --staged right before git commit is the best way to confirm you’re committing what you intend to, and nothing more.

Plain git diff only shows unstaged changes

This trips up almost everyone at first: plain git diff shows only unstaged changes. The instant you git add a file, that change disappears from git diff and moves into git diff --staged. If git diff prints nothing but git status says you have staged changes, that’s expected — switch to git diff --staged to see what’s about to be committed.


Comparing Commits, and the --stat Summary

git diff isn’t limited to your current edits — it can compare any two points in history. Give it two commit references and it shows what changed between them. The references can be full hashes, short hashes, HEAD, or relative names like HEAD~2 (meaning “two commits before HEAD”).

To see how a single file evolved across the last two commits, name the file after a -- separator (the -- tells Git “what follows is a path, not another revision”):

$ git diff HEAD~2 HEAD -- skylog.py
diff --git a/skylog.py b/skylog.py
index 48fbde4..ed6b676 100644
--- a/skylog.py
+++ b/skylog.py
@@ -1,8 +1,10 @@
 import sys
+from datetime import datetime
 
 def add(observation):
+    stamp = datetime.now().strftime("%Y-%m-%d %H:%M")
     with open("observations.md", "a") as f:
-        f.write(f"- {observation}\n")
+        f.write(f"- [{stamp}] {observation}\n")
     print(f"Logged: {observation}")
 
 if __name__ == "__main__":

Read it the same way as before: the import of datetime was added, a stamp variable was introduced, and the f.write(...) line was changed — a - line removed and a + line added in its place — to include the timestamp. The unchanged lines around them are context. This is the diff that built up the timestamp feature in SkyLog.

When you don’t need every line but want the shape of a change across many files, --stat prints a per-file summary instead of full hunks. Here’s everything that changed across the last three commits:

$ git diff --stat HEAD~3 HEAD
 README.md       |  2 ++
 observations.md |  2 ++
 skylog.py       | 11 +++++++++++
 3 files changed, 15 insertions(+)

Each row names a file, the number of changed lines, and a bar of +/- markers showing the proportion of insertions to deletions. The footer totals it up: three files touched, fifteen lines inserted, none removed. --stat is the fast way to answer “how big is this change, and what does it touch?” before you dive into the details.


Practice Exercises

Exercise 1: Read the hunk header

You run git diff and see the hunk header @@ -1,2 +1,3 @@. In plain words, what is this telling you about the old and new versions of the file?

Hint

-1,2 describes the old file: the changed block starts at line 1 and spans 2 lines. +1,3 describes the new file: it starts at line 1 and spans 3 lines. The block grew by one line — consistent with a single added (+) line and no removals.

Exercise 2: Empty output

You make an edit, run git add on the file, then run plain git diff and nothing prints. Is something wrong, and what command would actually show your change?

Hint

Nothing is wrong. Plain git diff compares the working directory to the staging area, and once you’ve staged the file those match — so there’s nothing to show. Use git diff --staged (or git diff --cached) to see the staged change you’re about to commit.

Exercise 3: Diff one file across two commits

You want to see exactly how skylog.py changed between the commit two before the latest and the latest commit. What command do you run?

Hint

git diff HEAD~2 HEAD -- skylog.py. The two revisions name the start and end points; the -- separates revisions from paths so Git treats skylog.py as a filename. Drop the -- skylog.py to compare every file between those two commits, or add --stat for a summary.


Summary

git diff shows you exactly what changed, line by line, in Git’s unified diff format: a diff --git header naming the file, an index line you can ignore, ---/+++ marking the old and new sides, and @@ ... @@ hunk headers giving the line ranges. Inside a hunk, a leading space is an unchanged context line, + is an added line, and - is a removed line. Which two versions get compared depends on the command: plain git diff compares the working directory to the staging area (your unstaged changes, and it prints nothing when there are none), while git diff --staged compares the staging area to the last commit (what you’re about to commit). Point git diff at two commits — like git diff HEAD~2 HEAD -- skylog.py — to compare history, and add --stat for a per-file summary instead of full hunks.

Key Concepts

  • Unified diff formatdiff --git header, @@ hunk headers, and +/-/context lines.
  • git diff — working directory vs. staging area (unstaged changes); prints nothing if there are none.
  • git diff --staged (a.k.a. --cached) — staging area vs. last commit (what your next commit holds).
  • git diff <a> <b> -- <file> — compare two commits, optionally limited to one file (-- separates revisions from paths).
  • git diff --stat — per-file summary of changed lines instead of full diffs.

Why This Matters

Reviewing a diff before you stage or commit is the single best habit for keeping your history clean: it’s how you catch the stray print, the half-finished edit, or the file you changed by accident. Knowing the difference between git diff and git diff --staged keeps you in control of what lands in each commit, and being able to diff any two commits lets you investigate exactly when and how something changed. Reading the unified diff format also pays off everywhere else — it’s the same format git show uses and the same one GitHub renders in every pull request.


Next Steps

Continue to Lesson 3 - Ignoring Files

Tell Git which files to leave alone with .gitignore — keep secrets, build output, and clutter out of your history.

Back to Module Overview

Return to the Working with History module overview


Continue Building Your Skills

You can now read Git’s diff format fluently and compare the three things that matter most — your working files, your staged changes, and any two commits in history. That precision is what lets you commit deliberately instead of by accident. Next you’ll learn to keep the wrong files out of Git entirely, using .gitignore to ignore secrets, build artifacts, and everyday clutter.