Your application worked yesterday, but today something is broken. Learn to investigate Git history with `git log`, `git show`, and `git diff` to discover what changed, inspect commits, and trace bugs without guessing.
Reading Git History Like a Detective
Your application was working yesterday.
Today, the login page crashes.
You haven't the faintest idea what changed, and your first instinct is to start editing files until the problem disappears.
Don't.
Before changing anything, investigate.
Git records the history of your project, and that history can tell you what changed, who changed it, and how the current code differs from an earlier version.
You just need to know where to look.
In this article, we'll use three commands to investigate a repository like detectives examining a crime scene:
git log
git show
git diff
No magnifying glass required. Although a decent terminal font helps.
1. Start with git log: What happened?
Every commit in Git records a snapshot of your project along with metadata such as its author, timestamp, message, and relationship to earlier commits.
The first step in investigating a problem is understanding the recent history.
Run:
git log
Git displays the commit history of the current branch, starting with the latest commit by default.
You'll see information similar to this:
commit 7d9b1e8...
Author: Developer A
Date: Mon Oct 12 10:30:00 2026
Fix login validation
commit c3f4e2a...
Author: Developer B
Date: Sun Oct 11 16:15:00 2026
Refactor authentication middleware
commit 9a8b7c6...
Author: Developer A
Date: Sun Oct 11 11:00:00 2026
Add login page
These are illustrative entries; your repository will have different hashes, names, dates, and messages.
Notice that the latest commit appears first.
If the login page stopped working today, the recent commits give you a useful starting point. Perhaps the validation change introduced a bug. Perhaps the middleware refactor changed how requests are handled.
You don't know yet, but now you have leads.
Make the history easier to read
The default output can be quite verbose. For a quick overview, use:
git log --oneline
You might see:
7d9b1e8 Fix login validation
c3f4e2a Refactor authentication middleware
9a8b7c6 Add login page
Each line shows an abbreviated commit hash and its message.
This is often the best starting point when you're trying to understand the sequence of changes.
Visualize branches and merges
When working with multiple branches, a flat list can hide important relationships.
Try:
git log --oneline --graph --decorate --all
The options mean:
--oneline keeps each commit compact.
--graph draws the relationships between commits.
--decorate shows branch and tag references.
--all includes commits reachable from the references Git knows about, rather than only the current branch's history.
A simplified graph might look like this:
* 7d9b1e8 Merge feature/login
|\
| * a1b2c3d Add login validation
* | c3f4e2a Refactor authentication
|/
* 9a8b7c6 Add login page
Now you can see that the login feature developed on a separate branch before being merged.
History isn't just a list of commits. It's a record of how your project evolved.
2. Narrow the investigation
A large project can have thousands of commits. Reading all of them is rarely the right approach.
Instead, narrow the search.
Show only the most recent commits
git log -5 --oneline
This shows the five most recent commits reachable from the current branch.
Look at a specific file's history
Suppose the problem is in login.js.
Run:
git log --oneline -- login.js
Git lists commits that changed that path.
This is a much more focused investigation than reading the history of the entire project.
Search commit messages
If you remember that someone recently changed authentication, try:
git log --oneline --grep="authentication"
This searches commit messages for the specified text. By default, the matching is case-insensitive.
It won't search the source code itself, but it can help you find commits whose descriptions mention the relevant work.
Find commits by author
git log --author="Developer A" --oneline
This filters the history by author information.
Remember that Git's author field records the identity associated with the commit. It isn't necessarily proof of who physically typed every line of code.
3. Use git show: What did this commit actually do?
You've identified a suspicious commit:
7d9b1e8 Fix login validation
The message sounds relevant, but commit messages don't tell the whole story.
To inspect the commit, run:
git show 7d9b1e8
Replace the example hash with a real commit hash from your repository.
Git displays the commit metadata and, for a typical commit, the patch showing the changes it introduced relative to its parent.
You might discover that the developer changed a validation condition:
- if (!username || !password) {
+ if (!username && !password) {
Look closely.
The original condition rejects a request if either the username or password is missing. The new condition rejects it only if both are missing.
That could explain why a request with an empty password now gets past this particular check.
A single character can change the behavior of an application. Commit history helps you spot exactly where that happened.
Inspect a commit without the full patch
To see only the summary of files changed:
git show --stat 7d9b1e8
For a concise list of changed files and their change types:
git show --name-status 7d9b1e8
These commands help you understand the scope of a change before diving into the details.
Compare a commit with its parent
For an ordinary commit, git show normally displays the patch relative to its parent. You can explicitly compare the commit with its first parent using:
git diff 7d9b1e8^ 7d9b1e8
This compares the first parent of the commit with the commit itself.
For a merge commit, there can be more than one parent, so the comparison you want may require a different parent selection. We'll return to that idea when examining complex histories.
4. Meet git diff: What changed between two states?
Sometimes you don't want to inspect a specific commit. You simply want to compare two versions of your code.
That's where git diff comes in.
The command compares content between Git states, such as your working directory, staging area, commits, and branches.
Inspect unstaged changes
Imagine you've edited login.js but haven't staged it yet.
Run:
git diff
Git compares your working tree with the index, showing changes that haven't been staged.
For example:
- const timeout = 5000;
+ const timeout = 1000;
You can immediately see that the timeout was reduced from five seconds to one second.
If a test started failing after this change, you've found something worth investigating.
Inspect staged changes
Now stage the file:
git add login.js
Run:
git diff
You may see no output for that file because the change is now staged.
To inspect what you've staged, use:
git diff --staged
This compares the index with the current commit, showing the changes that would normally be included in the next commit.
The distinction is important:
git diff shows unstaged changes.
git diff --staged shows staged changes.
Before committing, these commands help you verify that your changes are intentional.
Compare two commits
Suppose you want to understand how the project changed between two commits.
Run:
git diff 9a8b7c6 7d9b1e8
This compares the snapshots represented by the two commits.
Git displays the changes required to transform the first snapshot into the second.
You can also compare branches:
git diff main feature/login
This compares the content at the tips of the two branches. It does not necessarily show only the changes introduced by the feature branch since it diverged from main.
If you want to inspect the changes introduced by a branch relative to its merge base with main, use:
git diff main...feature/login
For this three-dot form, Git compares the merge base of the two branches with the tip of feature/login.
That distinction is especially useful when reviewing feature branches.
5. Understand the diff symbols
When you inspect a patch, you'll frequently see lines beginning with + and -.
For example:
function greet(name) {
- return "Hello, " + name;
+ return `Hello, ${name}!`;
}
Here's how to read it:
Lines beginning with - are removed from the compared version.
Lines beginning with + are added in the compared version.
Lines without either prefix provide context around the changes.
This example replaces string concatenation with a template literal and adds an exclamation mark to the greeting.
The diff doesn't mean that Git stored the commit as a simple list of these lines. Git records snapshots using its object model; a diff is a way of presenting the differences between two states.
That distinction matters because a diff is a view of the changes, not the complete internal representation of Git history.
6. Put the commands together: Investigate a bug
Let's walk through a realistic debugging scenario.
A login page worked yesterday. Today, requests with missing passwords are being accepted by the validation layer.
Instead of immediately changing the code, investigate.
Step 1: Check the recent history
git log -10 --oneline
You notice a recent commit named:
7d9b1e8 Fix login validation
Step 2: Inspect the suspicious commit
git show 7d9b1e8
You see that a condition changed from || to &&.
That is a strong lead.
Step 3: Compare the relevant file across versions
git diff 7d9b1e8^ 7d9b1e8 -- login.js
This narrows the comparison to login.js and shows the changes between the commit and its first parent.
Step 4: Verify your conclusion
Inspect the surrounding code and tests. Confirm that the changed condition actually explains the behavior.
A suspicious diff is evidence, not proof. The bug could also involve another part of the request-processing pipeline.
Step 5: Fix the issue safely
Once you've confirmed the cause, create an appropriate fix, run the relevant tests, and commit the change with a descriptive message.
The key lesson is simple: Git history lets you investigate systematically instead of guessing.
7. A few useful history shortcuts
Here are some commands worth keeping nearby.
GoalCommand
Compact history
git log --oneline
Recent commits
git log -5 --oneline
Branch graph
git log --oneline --graph --decorate --all
Inspect a commit
git show <commit>
Show changed files
git show --stat <commit>
History of a file
git log --oneline -- <file>
Unstaged changes
git diff
Staged changes
git diff --staged
Compare commits
git diff <old> <new>
Search commit messages
git log --grep="keyword" --oneline
Replace placeholders such as <commit> and <file> with actual values from your repository.
One additional command is worth knowing: git blame <file> shows the commit and author information associated with the lines in a file's current version. Despite its name, it is best used to trace the history of a line, not to assign blame to a person.
8. Common misconceptions
"Git log shows every commit in the repository."
By default, it shows commits reachable from the current starting point, usually the current branch. Use --all to include histories reachable from other references Git knows about.
"Git diff shows every change I've made."
Not in one invocation. Plain git diff shows unstaged changes. Use git diff --staged for staged changes, and specify commits or branches to compare other states.
"Git show only displays commit messages."
No. It can display metadata, patches, and other Git objects. For a typical commit, it shows the commit details and its changes relative to a parent.
"A commit message tells me exactly what changed."
A message describes the intent of a commit. Inspect the actual patch to understand the implementation.
"Git history always tells me who caused a bug."
History helps identify when a change was introduced and the author recorded in the commit. It doesn't establish intent, fault, or the complete context behind the change.
The takeaway
You don't need to memorize the entire Git command catalogue to become effective at investigating a repository.
Start with these three tools:
git log tells you what the history looks like.
git show helps you inspect a particular commit.
git diff compares changes between two states.
Together, they let you answer three essential debugging questions:
What changed?
Which commit introduced the change?
How does the current code differ from the earlier version?
Once you get comfortable reading history, Git becomes more than a tool for saving work. It becomes a practical investigation tool for understanding how your application evolved.
What's next?
We've learned how to investigate changes and identify suspicious commits. But what happens when you make a change you want to undo?
Should you use git restore, git revert, or git reset?
They sound similar, but they operate on different parts of Git's state and history.
In the next article, we'll explore those differences and learn how to recover from common Git mistakes without losing valuable work.
Next: Undoing Git Mistakes Without Panic.

