Precisely Tracking Changes
If you only use git diff superficially, you quickly miss unwanted changes between the working directory, staging area, and commit. This article shows how to keep the three comparison levels straight, filter precisely with flags like stat, word-diff, and -w, and configure a capable diff tool for everyday use.
Table of Contents
- 1. Why git diff is indispensable in daily development
- 2. Working directory, staging area, and commit: the three comparison levels
- 3. git diff basics: showing unstaged changes
- 4. git diff --staged and --cached: checking the staging area
- 5. Useful flags: --stat, --word-diff, -w, and --color-words
- 6. Comparing branches and individual commits
- 7. Diffing specific files and paths
- 8. Configuring a better diff tool and pager
- 9. Common pitfalls and git diff in the code review workflow
- 10. Summary
- 11. FAQ
1. Why git diff is indispensable in daily development
git diff is the central tool for making code changes visible before they land irreversibly in the history. Anyone who instead blindly runs git add . followed by git commit risks shipping debug output, commented-out code, or accidentally modified configuration files. Especially in Magento projects with many generated and manually maintained files, such as di.xml, layout XML, or compiled Tailwind assets, a quick look with git diff before every commit is the simplest way to guarantee clean, traceable changes.
The real value of git diff is that it doesn't just show that something changed, it shows exactly what changed, line by line and, with the right flags, even word by word. For PHP and Magento developers juggling multiple feature branches, hotfixes, and pull requests every day, being comfortable with the different git diff variants isn't a nice-to-have, it's a core skill that catches mistakes before the review instead of after.
2. Working directory, staging area, and commit: the three comparison levels
Git tracks changes across three distinct states, and git diff compares different pairs of these states depending on how it's invoked. The working directory holds the actual files on disk, exactly as they're currently being edited. The staging area, also called the index, is an intermediate buffer that holds precisely the changes that will actually be included in the next git commit. The latest commit (HEAD) is the most recently saved snapshot of the repository.
Without this model in mind, git diff quickly becomes confusing, because the same command produces different results depending on the staging state. The plain git diff call compares the working directory against the staging area and therefore shows only unstaged changes. git diff --staged compares the staging area against the last commit. git diff HEAD compares the working directory directly against the last commit, combining both staged and unstaged changes. Once you separate these three comparisons cleanly, it's immediately obvious why a diff can suddenly look empty even though something was visibly changed.
3. git diff basics: showing unstaged changes
The simplest call, git diff with no further arguments, shows all changes in the working directory that haven't yet been added to the staging area with git add. This is the command you should run before every git add, to see exactly what will move into the next staging round. The output follows the unified diff format: lines with a minus were removed, lines with a plus were added, and the surrounding context stays visible unchanged, by default three lines before and after each change.
A common trap: once a file has already been staged, git diff shows nothing more for that file, even though it was objectively changed. The reason is that git diff without arguments exclusively shows unstaged changes relative to the staging area, not to the last commit. For newly created, still untracked files, git diff also produces no output, because Git doesn't know about these files yet. git add -N helps here, tracking a file as empty for diff purposes without actually staging its content.
# Show all unstaged changes in the working directory
git diff
# Example output for a modified PHP class
diff --git a/app/code/Mironsoft/SeoSuite/Model/MetaGenerator.php b/app/code/Mironsoft/SeoSuite/Model/MetaGenerator.php
index 3f21ab2..9c88e10 100644
--- a/app/code/Mironsoft/SeoSuite/Model/MetaGenerator.php
+++ b/app/code/Mironsoft/SeoSuite/Model/MetaGenerator.php
@@ -42,7 +42,7 @@ class MetaGenerator
public function generateTitle(ProductInterface $product): string
{
- return $product->getName();
+ return trim($product->getName()) . ' | ' . $this->storeName;
}
# Show only which files changed, without the actual diff content
git diff --name-only
# Show status letters instead of full diff (M, A, D, R)
git diff --name-status
4. git diff --staged and --cached: checking the staging area
git diff --staged (identical to git diff --cached, both spellings are equivalent) compares the staging area against the last commit, showing exactly what the next git commit will actually include. This is the most important command right before every commit, because it answers the question that actually matters: what's about to land in the history? A plain git diff alone isn't enough here, since it only shows the unstaged part and therefore completely hides changes that have already been staged.
A common combination in practice: first use git add -p to interactively split changes into small, logical chunks, then run git diff --staged to review exactly those chunks once more before committing. Anyone who prefers to see the whole picture at once can use git diff HEAD, which shows staged and unstaged changes together against the last commit without distinguishing between the two areas. For a disciplined commit history in Magento projects, where every commit serves a single logical purpose, git diff --staged is the command that shows up most often in the terminal.
# Stage a specific file for the next commit
git add app/code/Mironsoft/SeoSuite/Model/MetaGenerator.php
# Show exactly what will be committed (index vs. last commit)
git diff --staged
# Equivalent alternative syntax
git diff --cached
# Example output after staging the change from the section above
diff --git a/app/code/Mironsoft/SeoSuite/Model/MetaGenerator.php b/app/code/Mironsoft/SeoSuite/Model/MetaGenerator.php
index 3f21ab2..9c88e10 100644
--- a/app/code/Mironsoft/SeoSuite/Model/MetaGenerator.php
+++ b/app/code/Mironsoft/SeoSuite/Model/MetaGenerator.php
@@ -42,7 +42,7 @@ class MetaGenerator
public function generateTitle(ProductInterface $product): string
{
- return $product->getName();
+ return trim($product->getName()) . ' | ' . $this->storeName;
}
5. Useful flags: --stat, --word-diff, -w, and --color-words
git diff --stat delivers a compact summary instead of the full diff: for each file, the number of changed lines shown both as a count and as a small bar chart of plus and minus signs. This is the first command to run before a large review, to get an overview before diving into details. During a merge or rebase touching many files, --stat immediately shows whether a change is within the expected scope or whether unexpectedly many files were affected.
git diff --word-diff switches from line-based to word-based diffs and is especially valuable for prose, translation files, or long string constants, where a single changed line would otherwise be entirely marked red and green even though only one word changed. --color-words takes the same idea even further, coloring only the actually differing character sequences without the bracket-style word markers used by --word-diff. The -w flag (an alias for --ignore-all-space) hides pure whitespace changes, for example after an automatic reformat by PHP-CS-Fixer, showing only genuinely meaningful content changes.
# Compact summary: files changed, lines added/removed as a bar chart
git diff --stat
# Example output
app/code/Mironsoft/SeoSuite/Model/MetaGenerator.php | 12 +++++++-----
app/code/Mironsoft/SeoSuite/etc/di.xml | 3 +--
2 files changed, 8 insertions(+), 7 deletions(-)
# Word-level diff, useful for prose or translation files
git diff --word-diff app/code/Mironsoft/SeoSuite/i18n/de_DE.csv
# Even more precise word-level highlighting without the [-old-]{+new+} markers
git diff --color-words
# Ignore all whitespace changes, e.g. after an auto-formatter run
git diff -w
# Same effect, more explicit long option
git diff --ignore-all-space
6. Comparing branches and individual commits
git diff isn't limited to the current working state, it can compare any commits and branches against each other. git diff branchA branchB shows the changes needed to go from branchA to branchB, based directly on the latest commit of each branch. git diff branchA...branchB with three dots, on the other hand, compares against the common merge base of both branches and ignores commits that happened only on branchA since the branches diverged. This three-dot notation is almost always the right choice when reviewing a feature branch against main, because it shows exclusively what the feature branch actually contributes.
For comparing individual commits, git diff commit1 commit2 works with full or abbreviated commit hashes. Relative references like git diff HEAD~3 compare the current state against the state three commits ago, and are handy for quickly seeing what changed since a specific point in the recent history, for example before a hotfix deployment. git diff HEAD~1 HEAD shows exactly the last commit as a diff, an alternative to git show HEAD that combines more cleanly with additional git diff flags.
# Compare the tips of two branches directly
git diff main feature/checkout-redesign
# Compare against the common merge base (recommended for feature-branch reviews)
git diff main...feature/checkout-redesign
# Compare two specific commits by hash
git diff a1b2c3d 9f8e7d6
# Compare current state against three commits ago
git diff HEAD~3
# Show exactly the last commit as a diff
git diff HEAD~1 HEAD
# Compare a remote branch against local main before pulling
git fetch origin
git diff main origin/main
7. Diffing specific files and paths
In large repositories with hundreds of changed files after a merge, viewing the entire diff at once is rarely useful. git diff -- path/to/file.php restricts the output to a single file, while git diff -- app/code/Mironsoft/SeoSuite/ shows all changes within a directory. The double dash -- explicitly separates commit references from path arguments and prevents ambiguity, for example when a filename happens to look like a branch name.
Pathspecs allow much more precise filtering than simple directory arguments. With git diff -- '*.phtml', for example, you can show only template files regardless of which module they belong to. Magic pathspecs like git diff -- ':!vendor' explicitly exclude certain directories, which drastically shortens the diff for generated vendor folders or compiled assets in pub/static and keeps focus on the actually relevant, manually maintained code. This combination of path filtering and the flags from the previous section makes even huge merge diffs manageable within seconds.
8. Configuring a better diff tool and pager
Git's built-in diff output is functional but quickly becomes hard to read for longer diffs, especially in standard terminals without syntax highlighting. Tools like delta or diff-so-fancy sit as a pager between Git and the terminal and render the same diff output with syntax highlighting, side-by-side view, and noticeably clearer line numbering. Configuration happens via core.pager for general pagination and interactive.diffFilter, or delta's native integration, for colored diffs.
For visual comparisons, for example during merge conflicts or complex refactorings, git difftool with an external tool like Meld, KDiff3, or PhpStorm's built-in diff view is often more practical than terminal output. Configuration in diff.tool and difftool.<name>.cmd determines which program launches on git difftool, while git diff itself continues to deliver fast terminal output. The two approaches aren't mutually exclusive: git diff for a quick look, git difftool for a detailed, visual analysis of more complex changes.
# ~/.gitconfig - improved diff experience with delta and a graphical difftool
[core]
pager = delta
[interactive]
diffFilter = delta --color-only
[delta]
navigate = true
side-by-side = true
line-numbers = true
syntax-theme = Monokai Extended
[diff]
tool = phpstorm
colorMoved = default
[difftool "phpstorm"]
cmd = phpstorm diff \"$LOCAL\" \"$REMOTE\"
[difftool]
prompt = false
9. Common pitfalls and git diff in the code review workflow
The most common mistake is assuming git diff shows all changes. In reality it only shows unstaged changes, while already staged changes remain invisible until you explicitly add --staged. A second classic mistake happens during merge conflicts: git diff without arguments during an active merge shows the combined diff against both parents, which looks confusing to unaccustomed eyes. A third point concerns large, binary, or generated files, such as compiled CSS bundles, where a full text diff carries little information and --stat or a pathspec exclusion is more useful instead.
In a code review workflow, a fixed routine pays off: before staging, run git diff to see what changed at all. After staging, run git diff --staged to check what will actually be committed. Before pushing, run git diff main...HEAD to see your branch's full contribution against the merge base, exactly as a reviewer would see it in a pull request. These three steps catch most unintended changes before they ever have a chance to surface in a review.
| Scenario | Unsuitable command | Recommended command | Benefit |
|---|---|---|---|
| Checking unstaged changes | going straight to git commit -am |
git diff |
Makes changes visible before committing |
| Checking staged changes before commit | git diff (shows nothing here) |
git diff --staged |
Shows exactly the upcoming commit content |
| Overview of a large merge diff | reading the full diff unfiltered | git diff --stat |
Fast overview before the detailed analysis |
| Reviewing a translation text change | standard line diff | git diff --word-diff |
Only the actually changed word is highlighted |
| Reviewing after an auto-formatter run | git diff without whitespace filter |
git diff -w |
Only meaningful content changes stay visible |
| Reviewing a feature branch against main | git diff main feature/x |
git diff main...feature/x |
Compares against the merge base, no foreign main commits |
Adopting the recommendations from the table into your own routine noticeably reduces the number of surprising changes in pull requests. In particular, the distinction between git diff branchA branchB and git diff branchA...branchB is frequently overlooked in reviews and leads to a diff containing changes that aren't actually part of the feature at all, but simply landed on main in parallel.
Mironsoft
Git workflow consulting, code review tooling, and developer training for Magento teams
Ready for clean Git workflows in your Magento team?
We analyze existing Git practices in Magento projects, set up diff tools and pagers for your team, and train developers in efficient review workflows that catch mistakes before the merge instead of after.
Git workflow audit
Reviewing branching strategy, commit hygiene, and review processes
Tooling setup
Configuring delta, diff-so-fancy, and PhpStorm difftool team-wide
Developer training
Hands-on workshop on git diff, rebase, and conflict resolution
10. Summary
git diff in detail solves a recurring problem in daily development: without a clear understanding of the three comparison levels, working directory, staging area, and last commit, diff results feel random instead of predictable. git diff shows unstaged changes, git diff --staged shows what will actually be committed, and git diff HEAD combines both. Flags like --stat, --word-diff, and -w filter specifically for what actually matters in a given situation, instead of reading the full, unfiltered diff every single time.
For comparing branches and commits, the three-dot notation branchA...branchB is almost always the right choice for feature-branch reviews, while the two-dot notation gives a direct tip-to-tip comparison. A configured diff tool like delta with syntax highlighting and a side-by-side view makes longer diffs in the terminal considerably more readable and cuts down the time needed to simply understand a diff, before the actual substantive evaluation can even begin.
git diff in Detail, The Essentials at a Glance
Three comparison levels
git diff shows unstaged, git diff --staged shows the index, git diff HEAD shows both against the last commit.
Key flags
--stat for an overview, --word-diff for prose, -w to ignore whitespace.
Branches & commits
Compare against the merge base with branchA...branchB, use HEAD~3 for relative references.
Better tooling
Configure core.pager with delta, set diff.tool for graphical comparisons in the IDE.