Diff and Merge: Compare Code Effectively
How to use diff to compare files and code. Tools, commands and strategies for effective merges.
Understanding Diff and Merge
Comparing code and merging changes is fundamental to software development. Whether you're reviewing pull requests, debugging issues, or resolving conflicts, understanding how diff and merge algorithms work makes you a more effective developer. This guide covers the theory, tools, and practical techniques for comparing and merging code effectively.
How Diff Algorithms Work
The Longest Common Subsequence (LCS)
Most diff tools use the LCS algorithm to find similarities between files. The algorithm identifies the longest sequence of lines that appear in both versions, then marks everything else as additions or deletions.
Original:
1. function hello() {
2. console.log("Hello");
3. return true;
4. }
Modified:
1. function hello() {
2. console.log("Hello, World");
3. console.log("Goodbye");
4. return true;
5. }
Diff result:
function hello() {
- console.log("Hello");
+ console.log("Hello, World");
+ console.log("Goodbye");
return true;
}
Types of Diff Output
Unified Diff
The most common format, showing context with +/- markers:
--- a/file.js
+++ b/file.js
@@ -1,4 +1,5 @@
function hello() {
- console.log("Hello");
+ console.log("Hello, World");
+ console.log("Goodbye");
return true;
}
Side-by-Side Diff
Shows old and new versions in parallel columns, making it easier to compare large changes visually.
Word-Level Diff
Highlights specific word changes within lines, useful for prose or configuration files:
- console.log("Hello");
+ console.log("Hello, [World]");
Git Diff Commands
Basic Comparisons
# Compare working directory with last commit
git diff
# Compare staged changes
git diff --staged
# Compare two commits
git diff abc123..def456
# Compare branches
git diff main..feature-branch
# Compare specific file
git diff HEAD~3 -- src/app.js
Useful Diff Options
# Word-level diff (useful for prose)
git diff --word-diff
# Show only file names
git diff --name-only
# Show stats (files changed, insertions, deletions)
git diff --stat
# Ignore whitespace changes
git diff -w
# Show function context
git diff -p
Comparing Branches Before Merge
# See what will be merged
git diff main...feature-branch
# List commits that will be merged
git log main..feature-branch --oneline
# Show files that will change
git diff main...feature-branch --name-only
Three-Way Merge
Understanding Three-Way Merge
When merging branches, Git uses three versions:
- Base: The common ancestor of both branches
- Ours: The current branch (usually main)
- Theirs: The branch being merged
Base (common ancestor)
/ \
Ours Theirs
(main) (feature-branch)
\ /
Merged Result
How Merge Conflicts Occur
Conflicts happen when both branches modify the same lines differently from the base:
// Base version
function greet(name) {
return "Hello, " + name;
}
// Ours (main)
function greet(name) {
return `Hello, ${name}!`; // Changed to template literal
}
// Theirs (feature)
function greet(name) {
return "Hi, " + name; // Changed greeting word
}
// Git can't automatically merge - both changed the same line
Resolving Merge Conflicts
Conflict Markers
Git marks conflicts with special markers:
<<<<<<< HEAD
return `Hello, ${name}!`;
=======
return "Hi, " + name;
>>>>>>> feature-branch
Resolution Strategies
# Accept current branch version
git checkout --ours filename.js
# Accept incoming branch version
git checkout --theirs filename.js
# Manual edit (most common)
# 1. Open file, remove conflict markers
# 2. Keep the code you want
# 3. Stage and commit
git add filename.js
git commit -m "Resolve merge conflict in filename.js"
Using Merge Tools
# Configure merge tool
git config --global merge.tool vscode
# Launch merge tool
git mergetool
# Popular merge tools:
# - VS Code (built-in)
# - IntelliJ/WebStorm
# - Beyond Compare
# - Meld
# - KDiff3
Practical Diff Workflows
Code Review Workflow
# Before reviewing a PR, see the full diff
git fetch origin
git diff main..origin/feature-branch
# Review specific types of changes
git diff main..origin/feature-branch -- "*.js" # Only JS files
git diff main..origin/feature-branch -- "src/" # Only src directory
# Check for debug code
git diff main..origin/feature-branch | grep -E "console\.log|debugger"
Finding When a Bug Was Introduced
# Use git bisect with diff
git bisect start
git bisect bad HEAD # Current version has bug
git bisect good v1.0.0 # Known good version
# Git will checkout commits for you to test
# After finding the bad commit:
git show abc123 # See what changed
git diff abc123^..abc123 # Diff against parent commit
Comparing Configuration Changes
# Diff JSON/YAML config files meaningfully
git diff --no-index old-config.json new-config.json
# Or use specialized tools
npx json-diff old.json new.json
Advanced Diff Techniques
Diffing Specific Code Patterns
# Find function signature changes
git diff -G "function.*\(" main..feature
# Find changes to specific functions
git diff main..feature -L :functionName:file.js
# Show only lines matching pattern
git diff main..feature | grep -A3 -B3 "TODO"
Creating Patches
# Create a patch file
git diff > my-changes.patch
# Create patch from commits
git format-patch -1 HEAD # Last commit
git format-patch main..HEAD # All commits since main
# Apply a patch
git apply my-changes.patch
git am < commit.patch # Apply with commit info
Semantic Diff Tools
For language-aware diffing that understands code structure:
- difftastic: Structural diff that understands syntax
- semantic-diff: AST-based comparison
- GitHub's semantic diff: Built into PR reviews
Online Diff Tools
For quick comparisons without command line:
- THEJORD Diff Checker - Compare text and code online
- JSON Formatter - For comparing JSON structures
- Markdown Converter - Preview markdown differences
IDE Integration
VS Code
// settings.json
{
"diffEditor.renderSideBySide": true,
"diffEditor.ignoreTrimWhitespace": false,
"diffEditor.wordWrap": "on"
}
// Compare files
// 1. Select file in explorer
// 2. Right-click → "Select for Compare"
// 3. Select second file
// 4. Right-click → "Compare with Selected"
IntelliJ/WebStorm
// Compare with clipboard
Ctrl+Shift+V (after copying first text)
// Compare files
Select two files → Right-click → Compare Files
// Compare with branch
VCS → Git → Compare with Branch
Best Practices
For Clean Diffs
- Make atomic commits: Each commit should do one thing
- Separate formatting changes: Don't mix code changes with reformatting
- Write meaningful commit messages: Helps understand why changes were made
- Keep pull requests small: Easier to review and merge
For Easier Merges
- Merge main frequently: Keep feature branches up to date
- Rebase before merge: Creates linear history
- Resolve conflicts early: Don't let them accumulate
- Use feature flags: Merge incomplete features safely
For Code Reviews
- Review in context: Use diff tools that show surrounding code
- Check tests: Ensure changes are tested
- Look for patterns: Similar bugs might exist elsewhere
- Consider performance: New code might have performance implications
Troubleshooting
Binary File Conflicts
# Git can't merge binary files
# Choose one version:
git checkout --ours image.png
# or
git checkout --theirs image.png
git add image.png
Large Diffs
# Focus on specific parts
git diff main..feature -- "*.js" | head -500
# Use stat for overview first
git diff --stat main..feature
# Exclude certain files
git diff main..feature -- . ':!package-lock.json'
Whitespace Issues
# Ignore whitespace in diff
git diff -w
# Fix whitespace before commit
git diff --check # Find whitespace errors
git stripspace # Remove trailing whitespace
Conclusion
Mastering diff and merge is essential for collaborative development. Key takeaways:
- Understand how LCS algorithms find differences
- Use
git diffoptions to filter and focus on relevant changes - Learn three-way merge to resolve conflicts effectively
- Keep commits atomic and branches updated to minimize conflicts
- Use IDE tools and merge tools for complex conflicts
For more developer tools, explore our free online tools. For Git documentation, see git-diff documentation and Git Branching Guide.