Why History Is Worth Reading
Everything in this lesson exists to answer questions you will genuinely have. The login page worked last week and does not work now — what changed in between? A line of code makes no sense — who wrote it, and what were they doing at the time? Your teammate says they pushed the fix — did they, and is it in the version you have?
None of these commands change anything. git log, git diff, git show and git blame are all read-only, so you can experiment with them freely. That makes this the safest lesson in the course to just try things in.
One practical note before the commands: many of them open a pager, so the output scrolls in a program rather than dumping to the terminal. Move with the arrow keys or the space bar, and press q to quit. A surprising number of people conclude their terminal has hung when it is simply waiting inside a pager for q.
git log # full history, newest first
git log --oneline # one line per commit
git log -5 # only the five most recent
git log --stat # each commit plus which files it touched
# Press q to leave the pager - Commit hashes are long, but Git accepts any unambiguous prefix. If
git log --onelineshowsa81c9f2, thengit show a81c9f2works, and often even the first four characters are enough. Copy the short form from--onelineoutput rather than typing forty characters by hand.
Searching History Instead of Scrolling
Once a project has a few hundred commits, scrolling is hopeless. git log takes filters, and they combine, so you can narrow a history down to the handful of commits that matter.
--grep searches commit messages, which is another reason honest messages pay off — git log --grep="login" finds nothing useful in a history of commits called "update". --author filters by who made the commit, and it matches on part of a name or email, so --author=ananya is enough. --since and --until accept both dates and plain English such as "2 weeks ago" or "yesterday".
The most powerful filter is -S, known as the pickaxe. It searches the content of changes rather than messages, and finds commits where the number of occurrences of a string changed. So git log -S"calculateGst" shows the commit that introduced calculateGst and the commit that removed it. When somebody asks "when did this function disappear?", this is the command that answers it, and almost nobody knows it exists.
Adding -- filename at the end restricts the log to commits that touched that file. The two dashes tell Git that what follows is a path and not a branch name, which matters when a file and a branch share a name.
# By message
git log --grep="login"
# By author (partial match on name or email)
git log --author="ananya"
# By date — plain English works
git log --since="2 weeks ago"
git log --since="2026-06-01" --until="2026-06-30"
# By file — everything that ever touched this file
git log --oneline -- src/auth.js
# Follow a file through renames
git log --follow -- src/auth.js
# The pickaxe: commits where this text was added or removed
git log -S"calculateGst" --oneline
# Filters combine
git log --author="ananya" --since="1 month ago" --oneline -- src/ Seeing the Shape of the Project
A plain git log shows commits as a flat list, which hides the fact that branches exist. Three flags together fix that and produce the single most useful history command in Git: git log --oneline --graph --all --decorate.
Each flag does one thing. --oneline compresses each commit to a single row. --graph draws the branch structure down the left as ASCII lines, so you can see where a branch split off and where it merged back. --all includes every branch, not just the one you are on — without it you are looking at your current branch only, which is why beginners think their other work has vanished. --decorate labels commits with the branch and tag names pointing at them, and modern Git turns it on by default.
Learn to read that output. A line splitting into two means a branch was created. Two lines joining means a merge. Seeing where main, your feature branch and origin/main each sit relative to one another tells you at a glance whether you are ahead of the server, behind it, or diverged — which is the information you need before every push.
This command is long enough that everyone shortens it. Setting it as an alias called lg is one of the first things experienced users do on a new machine.
git log --oneline --graph --all --decorate
# * 7d2f1a9 (HEAD -> main) Merge branch 'feature/cart'
# |\
# | * 4b8e0c3 (feature/cart) Add quantity selector
# | * 9a1d5f7 Add cart page
# |/
# * 2c6b8e1 (origin/main) Fix footer links
# * 9f4c2a1 Initial commit
# Make it a one-word command
git config --global alias.lg "log --oneline --graph --all --decorate"
git lg
# A custom format, if you want dates and authors compactly
git log --pretty=format:"%h %ad %an %s" --date=short -10 - In the graph above,
origin/mainsits two commits behindmain. That means you have two local commits GitHub has never seen. Reading that one detail correctly prevents the most common panic in group projects: "my code isn't on GitHub".
git diff and the Three Areas Again
git diff confuses people because it seems to show different things at different times. It does not — it always compares two specific places, and the flags decide which two. Once you map it back onto the three areas from the first lesson, it becomes completely predictable.
git diff on its own compares your working directory against the staging area: the edits you have made but not yet added. git diff --staged compares the staging area against the last commit: exactly what your next commit will record. git diff HEAD compares your working directory against the last commit, ignoring the staging area entirely, which is the "everything I have changed since my last commit" view.
That explains the classic surprise: you run git add ., then run git diff, and it prints nothing. Nothing is wrong. There is no difference between your files and the staging area because you just made them identical. The changes are in git diff --staged.
Reading a diff takes a little practice. Lines beginning with - were removed, lines beginning with + were added, and a changed line appears as one of each. The @@ -12,7 +12,8 @@ header gives the line numbers and how many lines of context are shown on each side. Everything without a marker is unchanged context, included so you can tell where you are.
git diff # working directory vs staging area
git diff --staged # staging area vs last commit (what you'd commit)
git diff HEAD # working directory vs last commit (everything since)
# Between any two commits
git diff 9f4c2a1 a81c9f2
# Between branches: what does feature/cart have that main doesn't?
git diff main..feature/cart
# Just one file
git diff -- src/auth.js
# Summary instead of full text
git diff --stat
# src/auth.js | 14 ++++++++------
# src/login.js | 3 +++
# 2 files changed, 11 insertions(+), 6 deletions(-)
# Reading the output
# @@ -40,6 +40,7 @@ function login(user) {
# const input = getForm();
# - if (user.password = input.password) {
# + if (user.password === input.password) {
# + logAttempt(user.id);
# return true; git diffignores untracked files completely, because Git has nothing to compare them against. A brand-new file shows up ingit statusas untracked but produces no diff until yougit addit. That is expected, not a bug.
git show and git blame: Investigating One Change
git show displays a single commit in full — its message, its metadata and its complete diff. When git log has told you which commit is interesting, git show tells you what it did. It also has a lesser-known form: git show <commit>:<path> prints a file exactly as it was at that commit, which is the quickest way to look at an old version of something without touching your working directory at all.
git blame annotates a file line by line with the commit, author and date that last changed each line. The name is unfortunate — its real use is archaeology, not accusation. You find a strange condition in a payment function, run blame, get a commit hash, run git show on it, and read a message explaining that this exact condition was added because the gateway rejects amounts with more than two decimal places. That is a five-second answer to a question that could otherwise cost an hour.
Blame has one honest limitation. A commit that only reformatted the file, or renamed a variable throughout, becomes the "last change" for every line it touched, hiding the real author underneath. git blame -w ignores whitespace-only changes, which helps. If the result still looks wrong, use git log -S to find where the logic actually came from.
On large files, restrict blame to the lines you care about with -L. Blaming a 3,000-line file in the terminal is rarely what you want, and GitHub's web interface offers the same view with clickable commits if you prefer to read it there.
# One commit in full
git show a81c9f2
git show HEAD # the commit you are on
git show HEAD~2 # two commits back
# A file exactly as it was at some commit
git show HEAD~5:src/auth.js
# Who last touched each line, and in which commit
git blame src/auth.js
# a81c9f2 (Ananya Sharma 2026-07-14 11:02:31 +0530 42) if (amount > 0) {
# 3b7e0d4 (Rahul Verma 2026-06-30 18:47:09 +0530 43) charge(amount);
# Only lines 40-60, ignoring whitespace-only changes
git blame -L 40,60 -w src/auth.js
# Then read the commit blame pointed at
git show 3b7e0d4 - A useful workflow to remember as one chain:
git log --onelineto find candidates,git showto inspect one,git blamewhen you are starting from a suspicious line rather than a suspicious commit. Nearly every real debugging session in a Git repository is some combination of those three.
