Skip to content

Viewing Git history with log, show, and diff

docs.scrimba.com

Say you open weather-app on a Monday morning and cannot remember exactly what you shipped on Friday. Or a teammate asks why the temperature conversion changed last week, and you want the real answer instead of a guess. The commit loop covers how a commit gets saved. This chapter covers reading that history back: what happened, who did it, and when. The command for that is git log, and here is everything it prints for a small project:

bash
$ git log
commit 4f2a1c9d8e7b6a5c4d3e2f1a0b9c8d7e6f5a4b3c
Author: Priya Nair <[email protected]m>
Date:   Tue Jul 14 09:12:03 2026 +0100

    Fix temperature conversion in Celsius-to-Fahrenheit formula

commit 5f3d8b21a90e7c6d5b4a3f2e1d0c9b8a7f6e5d4c
Author: Kwame Boateng <[email protected]m>
Date:   Mon Jul 13 16:45:22 2026 +0100

    Add five-day forecast to the dashboard

commit 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b
Author: Kwame Boateng <[email protected]m>
Date:   Mon Jul 13 11:03:47 2026 +0100

    Set up project structure

One command, and the whole story of the project is right there: three commits, newest first, each with who wrote it, when, and why.

Reading the log with git log

git log walks back through every commit reachable from where you are, most recent first. Each entry shows four things: a hash (the long string of letters and numbers identifying that exact commit), the author, the date, and the message its author wrote.

A commit is all four of those things bundled together: a snapshot of every tracked file at that moment, the message describing the change, who made it, and a link to the commit that came right before it, its parent commit. That parent link is what turns a pile of separate snapshots into an actual timeline. Read git log from top to bottom and you are reading that timeline backward, from now to the beginning.

The hash looks intimidating at first, but you rarely need the whole thing. 4f2a1c9d8e7b6a5c4d3e2f1a0b9c8d7e6f5a4b3c and 4f2a1c9 point at the same commit. Git only needs enough of the hash to tell it apart from every other commit in the repository, and the first seven or so characters are almost always enough. You will see the short form everywhere, including the commands in the rest of this chapter.

The default git log output is thorough but tall: three commits already took up your whole terminal. A few shapes make it more useful day to day.

bash
$ git log --oneline
4f2a1c9 Fix temperature conversion in Celsius-to-Fahrenheit formula
5f3d8b2 Add five-day forecast to the dashboard
1a2b3c4 Set up project structure

--oneline collapses each commit to its short hash and message, one line per commit, which is the shape you will reach for most. --graph draws the shape of the history alongside the log as a set of lines and dots. On a project with no branches yet it draws a plain straight line, but keep it in your fingers early: the moment branches show up (the next chapter) is exactly when that picture starts to earn its keep.

You can also filter the log instead of scrolling through it. git log --author="Kwame" shows only commits written by Kwame, useful when you want to see one person's work on a shared project. git log -- src/index.js shows only commits that touched that specific file, ignoring everything that never went near it:

bash
$ git log --oneline -- src/index.js
4f2a1c9 Fix temperature conversion in Celsius-to-Fahrenheit formula
1a2b3c4 Set up project structure

That second commit, the forecast one, never touched src/index.js, so it does not show up. Both filters combine with --oneline cleanly.

The filters above answer "which commits touched this file". The question that actually sends you into history on a bad day is sharper: when did this exact piece of code appear, or disappear? That is what git log -S, known as the pickaxe, is for. Give it a string, and it keeps only the commits where the number of occurrences of that string changed, meaning the commits that added or removed it:

bash
$ git log --oneline -S "showLoadingSpinner"
7d1e8c3 Add loading spinner while forecast data loads

One commit, out of the whole history: the moment that call entered the codebase. This is the fastest way to answer "when did this bug line get introduced" without checking anything out. The counting rule has one consequence worth knowing: a commit that only moves the line within a file leaves the occurrence count unchanged, so -S skips it. When you want every commit whose diff so much as touches the text, moves included, use git log -G with a regular expression instead. Reach for -S first; it is the sharper tool on the question you usually have.

JunoReading the loggit log lists every commit, newest first, with its hash, author, date, and message. A commit is that snapshot plus its message plus a link to the commit before it. You almost never need the full hash, the first several characters are enough for Git to know exactly which commit you mean.
JunoReading the loggit log --oneline is the shape you will use daily, one line per commit. Add --author or a trailing -- path to filter down to one person's work or one file's history, and reach for --graph once branches enter the picture.
JunoReading the log Every commit's hash is a fingerprint of its own contents plus its parent's hash, so two commits never collide by accident and a short prefix is safe to type. And when the question is "when did this line appear or vanish", git log -S "text" walks the whole history for you and keeps only the commits that added or removed it. Between that and the parent link, this chapter is most of my debugging toolkit.

Inspecting one commit with git show

git log tells you a commit happened. git show <hash> tells you exactly what it did:

bash
$ git show 4f2a1c9
commit 4f2a1c9d8e7b6a5c4d3e2f1a0b9c8d7e6f5a4b3c
Author: Priya Nair <[email protected]m>
Date:   Tue Jul 14 09:12:03 2026 +0100

    Fix temperature conversion in Celsius-to-Fahrenheit formula

diff --git a/src/index.js b/src/index.js
index 3f2c1a9..7b8e2d4 100644
--- a/src/index.js
+++ b/src/index.js
@@ -12,7 +12,7 @@ function celsiusToFahrenheit(celsius) {
-  return celsius * (9 / 5 + 32)
+  return (celsius * 9) / 5 + 32

The top half is the same metadata git log shows. Below it is the actual change: a line starting with - is a line the commit removed, a line starting with + is a line it added, and unmarked lines are context that stayed the same. Here that reads clearly: the old line grouped 32 inside the parentheses, so it got added before the multiplication ever happened. The fix regroups the parentheses around celsius * 9 so 32 is added last, the operator-precedence bug Priya's message describes.

Add a path to see only part of a larger commit: git show 4f2a1c9 -- src/index.js shows only that file's slice of the change, which matters once a single commit touches several files and you only care about one of them.

JunoInspecting one commitgit show <hash> shows one commit in full: who made it, when, and the actual lines it changed. Removed lines start with a minus sign, added lines start with a plus sign. It is the fastest way to answer "what did this commit actually do".
JunoInspecting one commitgit show <hash> gives you the metadata and the full diff for one commit. Point it at a specific path too, git show <hash> -- path/to/file, when a commit touched several files and you only need one file's slice of it.
JunoInspecting one commitgit show is comparing a commit's saved snapshot against its parent's, and printing only the lines that differ. Once you start reading diffs this way, a commit stops being a mystery box and starts being two snapshots with a difference between them.

What git diff shows, and the trip-up almost everyone hits

git log and git show read committed history. git diff reads what has not been committed yet: the changes sitting in your working directory right now, compared against your last commit.

bash
$ git diff
diff --git a/README.md b/README.md
index 1c2d3e4..9f8a7b6 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
 # weather-app
+A five-day weather dashboard built with vanilla JavaScript.

That is the edit you made to README.md, shown before you have staged or committed anything. Here is the part that catches nearly everyone in their first week: run git add . to stage that same change, then run git diff again, and it shows nothing at all.

bash
$ git add .
$ git diff

Nothing printed. The edit did not disappear, and nothing went wrong. Plain git diff compares your working directory against the staging area, and once you have staged everything, those two now match, so there is no difference left to show there. To see changes waiting in the staging area, use git diff --staged instead.

bash
$ git diff --staged
diff --git a/README.md b/README.md
index 1c2d3e4..9f8a7b6 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
 # weather-app
+A five-day weather dashboard built with vanilla JavaScript.

Same change, now visible, because --staged compares the staging area against your last commit instead.

Plain git diff and git diff --staged are answering two different questions, and it helps to name them precisely. Plain git diff compares your working directory to the staging area: it shows edits you have made but not yet staged. git diff --staged compares the staging area to your last commit: it shows exactly what would go into your next commit if you ran git commit right now. Neither one shows both at once, which is exactly why a fully staged change makes plain git diff go quiet. You will sometimes see --cached in older tutorials and docs; it means the same thing as --staged.

Running both before you commit is a good habit: plain git diff confirms nothing important got left unstaged, and git diff --staged confirms exactly what is about to be saved.

JunoSeeing what changedgit diff shows edits in your working directory that are not staged yet. Once you stage everything with git add, plain git diff goes quiet. That is expected, your changes are still there. Use git diff --staged to see what is waiting to be committed.
JunoSeeing what changed Plain git diff is working directory against staging area, git diff --staged is staging area against your last commit, two different comparisons. Running both before you commit tells you what is unstaged and what is about to be saved. Older material sometimes says --cached, same flag, different name.
JunoSeeing what changed Both diffs are the same operation applied to different pairs of snapshots: working tree against staging area, or staging area against the last commit. Naming which two things you are comparing is the whole trick to never being confused by an empty git diff again.

Why Git history forms a graph

Every commit you have seen so far points at exactly one parent, and reading git log top to bottom has felt like reading a straight line. That holds for a solo project working on one line of work. It stops holding the moment branches and merges enter the picture, covered next in Branches, because a project's real history can split into separate lines and come back together.

This is what the --graph flag from earlier is actually for. On weather-app right now it draws a single column, because there is only one line of commits to draw. Once a branch forks off and later merges back, --graph starts drawing the fork and the rejoin as separate columns that meet, which is far easier to read than trying to picture it from a flat list of hashes.

Run git show on a merge commit and, by default, it prints only the metadata: no diff at all. Add -m and it prints a separate diff for each parent in turn. Both behaviors trace back to the same shape: Git's history is a DAG, a directed acyclic graph. "Directed" means every link points one way, a commit back to its parent, never forward. "Acyclic" means those links never loop, so walking them always moves toward the start of the project, never back to a commit you already passed. A normal commit has exactly one parent, which is why a solo project's log reads as a straight line. A merge commit is the exception: it carries two parent hashes instead of one, and with two parents, plain git show has no single "the diff" to print until -m tells it to compare against each parent separately. Branches and merging, the next two chapters, build directly on this same graph.

JunoHistory as a graph Right now every commit you have seen points at one parent, so the log reads like a straight line. That changes once branches split off and merge back together, which you will meet next. The shape underneath, commits linked to the commit before them, is what makes any of that possible.
JunoHistory as a graph A solo line of commits looks straight because each one has a single parent. The --graph flag is what makes the shape visible once branching starts, drawing forks and merges instead of a flat list. Keep it in your toolkit even before you need it.
JunoHistory as a graph Git's history is a directed acyclic graph: parent pointers only ever point backward, and they never loop. A merge commit is the one place a commit gets two parents instead of one, which is why plain git show prints no diff there until you add -m to pick a parent. Branches and Merging teach you the rest of this same graph, from different angles.