instead of relying only on the final diff
A pull request's default view almost always shows just the aggregated diff between the target branch and the last commit. That hides how a change actually came together, whether it landed in one clean, atomic commit or only reached its final shape after several rounds of fixes. This article shows how git log, git show, git range-diff and a targeted interactive rebase let you actually review commit history before a merge, instead of trusting the final result alone.
Table of Contents
- 1. Why the final diff falls short during review
- 2. git log as the starting point for a history overview
- 3. Inspecting individual commits in isolation
- 4. Reading merge commits and first-parent history correctly
- 5. Distinguishing two-dot and three-dot diffs on feature branches
- 6. Comparing commits across rebase boundaries with git range-diff
- 7. Cleaning up history with interactive rebase before review
- 8. Commit-by-commit review in practice: a checklist
- 9. Log formats, aliases and editor integration for efficient review
- 10. Summary
- 11. FAQ
1. Why the final diff falls short during review
A pull request's default view usually shows only the aggregated diff between the target branch and the last commit of the feature branch. Anyone reviewing only that view sees the outcome, not how it got there. Whether a line was written in one clean, atomic commit or only reached its final form after three rounds of fixes disappears completely inside the aggregated diff.
On larger changes especially, the final diff often blends several unrelated concerns together: a refactor, a bug fix and a new feature all show up as one confusing mass of changes. A review that walks through the individual commits instead can judge each intent in isolation and assess risk far more precisely than a glance at the final result alone.
Another downside of a pure diff review: bugs that were introduced in an early commit and fixed again in a later one stay invisible in the final diff but show up clearly in the full history. That matters, because such patterns often reveal that the author had to test an idea live in the code, which can point to insufficient planning or an unclear grasp of the requirements.
2. git log as the starting point for a history overview
Before diving into individual commits, a quick overview of the history's structure is worth the time. Combining --oneline, --graph and --stat shows at a glance how many commits a feature branch contains, how they branch relative to each other, and which files each one touches, all without displaying a single diff hunk yet.
Commit count alone is already a useful signal: ten small, clearly scoped commits are usually faster and easier to review precisely than three enormous commits that each touch hundreds of lines across several modules at once. Skimming the commit list before the deeper review also lets you decide deliberately where to start the closer inspection.
# Compact overview of every commit in a feature branch
git log --oneline --graph --stat main..feature/checkout-redesign
# Commit messages and touched files only, without diff content
git log --stat --no-patch main..feature/checkout-redesign
3. Inspecting individual commits in isolation
For the actual content review, git show is the central tool: it prints commit message, author, date and the full diff of a single commit in isolation, without blending in context from neighboring commits. That lets you walk commit by commit through the history, judging each step as a self-contained unit.
Alternatively, git log -p delivers the same information for an entire range of commits at once, which is useful when the whole series should be worked through from start to finish in one sitting. Combined with --reverse, commits appear in chronological rather than reverse order, matching how the change actually came into being and making it easier to follow along.
# Inspect a single commit in isolation, including its diff
git show a1b2c3d
# Walk the entire commit series chronologically, commit by commit
git log -p --reverse main..feature/checkout-redesign
4. Reading merge commits and first-parent history correctly
Once a feature branch itself contains merges from the target branch, the picture changes: a plain git log then also shows foreign commits from the target branch that have nothing to do with the actual change. The --first-parent option narrows the history down to the commits that genuinely belong to the feature branch and hides the merged-in commits from the target branch.
For merge commits themselves, git show displays only a summary without a diff by default, since a merge commit technically has two parents. The -m option shows the diff of a merge commit against each parent separately, which helps clarify what concrete changes a merge actually introduces relative to the main line.
# Only the feature branch's own commits, without merged-in foreign commits
git log --first-parent --oneline main..feature/checkout-redesign
# Show the diff of a merge commit against each parent separately
git show -m 9f8e7d6
5. Distinguishing two-dot and three-dot diffs on feature branches
A common stumbling block during manual review is confusing git diff main..feature with git diff main...feature. The two-dot form compares the current state of main directly with the current state of feature, while the three-dot form instead uses the common ancestor of both branches, the so-called merge base, as its starting point and shows only the changes that happened in the feature branch since that point.
If main has moved on since the feature branch was created, the two forms produce noticeably different results: the two-dot form mixes foreign changes from main into the review, while the three-dot form shows exclusively the changes relevant to the review. For a clean review, the three-dot form is therefore almost always the right choice.
# Blends changes from main into the feature changes
git diff main..feature/checkout-redesign
# Shows only what happened on the feature branch since it diverged
git diff main...feature/checkout-redesign
6. Comparing commits across rebase boundaries with git range-diff
If a feature branch gets rebased again during an ongoing review, for instance to address review comments, every subsequent commit gets a new commit ID, even if the actual content stays identical. A plain repeated diff then appears to show everything as new, even though only a few lines may have actually changed content-wise.
git range-diff solves exactly this problem: it compares two versions of a commit series by content, automatically matches corresponding commits to each other, and shows which commits stayed unchanged, which changed content-wise, and which were added or removed. For reviewers who need to re-check a reworked version of a branch, that saves considerable time compared to walking through every commit again from scratch.
# Compare the old and new version of the commit series,
# after a rebase onto a more recent state of main
git range-diff main old-feature-branch@{1} feature/checkout-redesign
7. Cleaning up history with interactive rebase before review
A review gets noticeably easier once the history has already been cleaned up beforehand. Interactive rebase with git rebase -i lets authors merge pure fixup commits like fixup! or squash! into their original commit before the branch is even submitted for review.
git commit --fixup immediately links a fixup commit to its matching original commit, and git rebase -i --autosquash then sorts and merges those fixes into the right place automatically. The result is a history in which every remaining commit genuinely represents a self-contained, review-worthy idea, instead of exposing a sequence of trial and error.
# Link a fixup commit directly to an earlier commit
git commit --fixup a1b2c3d
# Before review: automatically sort and merge fixup commits into place
git rebase -i --autosquash main
8. Commit-by-commit review in practice: a checklist
A short checklist helps make the process systematic: does each commit make sense and stand on its own? Does the code remain in a working state after every single commit, so a later bisection with git bisect does not fail on a broken intermediate step? Does the commit message explain the why of the change, not just the obvious what?
It is also worth asking whether related changes are actually bundled into one commit or unnecessarily scattered across several. Both extremes, a single giant commit for everything and excessive splitting into dozens of micro commits, make review harder, just for opposite reasons.
9. Log formats, aliases and editor integration for efficient review
Recurring log invocations can be saved permanently as a git alias, so a short command like git lg delivers the familiar, carefully configured output instead of retyping long option chains every time. Custom format strings via --pretty=format: also let you display exactly the information relevant to a given review, such as author, relative date and commit message on a single line.
Most modern IDEs, including PhpStorm, additionally offer a graphical commit history with clickable individual commits, side-by-side diffs and blame integration, which makes switching between the command line and visual inspection easier. For deeper reviews, though, the command line often remains the faster choice, because range expressions like three-dot diffs or range-diff can be expressed there more precisely and repeatably.
# Save a permanent alias for a clear log output
git config --global alias.lg "log --graph --oneline --decorate --abbrev-commit"
# Custom format: short hash, relative date, author, subject line
git log --pretty=format:"%h %ad %an %s" --date=relative main..feature/checkout-redesign
| Criterion | Plain final-diff review | Commit-by-commit review | Recommendation |
|---|---|---|---|
| Detects mixed concerns | No, everything looks like one change | Yes, each commit judged separately | Commit by commit |
| Shows fixes for temporary bugs | No, invisible in the final result | Yes, visible in the history | Commit by commit |
| Effort on a small, atomic branch | Very low | Somewhat higher, but manageable | Either works |
| Effort on a large, unstructured branch | Low, but not very informative | High, but surfaces real risk | Commit by commit |
| Useful for later git bisect runs | No effect on the history | Direct, since every commit was checked | Commit by commit |
Mironsoft
Git workflows, branching strategies, and CI hooks
Chaotic Git history and unclear branching rules across the team?
We set up clean Git workflows, clarify branching strategies for the team, and automate quality checks via Git hooks and CI pipelines so the history stays traceable.
Workflow Audit
Review the existing branching strategy and merge practice for weak spots.
Hook Automation
Set up pre-commit and pre-push hooks for linting, tests, and commit conventions.
Team Training
Teach rebase, cherry-pick, and conflict resolution hands-on across the team.
10. Summary
Commit History Review
Core problem
A pull request's final diff shows only the outcome, not the path there, hiding possible intermediate bugs.
Key tools
git log with --graph and --stat for the overview, git show for individual commits, git range-diff after rebases.
Before the review
Clean up history with interactive rebase and --autosquash so each commit is a self-contained idea.
Rule of thumb
Use a three-dot diff against the merge base instead of a two-dot diff to exclude foreign changes from the comparison.