Context & Why You Need zdiff3
Every developer knows the dread of breaking into a cold sweat over a 100+ line merge conflict: “Did Developer A delete this, or did Developer B add it? What was the original value in the first place?” Unfortunately, Git’s default behavior is to simply pit two code blocks against each other: the current branch (HEAD) and the incoming branch. You get zero context about what the file looked like before both sides started editing.
Losing context means having to guess. My team once experienced an unforgettable incident: a developer resolved a conflict by keeping our branch’s code and overwriting the incoming branch. As a result, newly implemented coupon validation logic vanished entirely from production on the night of a major sale. The whole team had to stay up until 3:00 AM tracing commits and rolling out an emergency hotfix.
Here is the standard 2-way diff that Git presents by default:
<<<<<<< HEAD
const timeout = 5000;
=======
const timeout = 3000;
const retries = 3;
>>>>>>> feature/retry-logic
Looking at this, can you tell what the initial timeout value was? Did HEAD increase it from 1000 to 5000, or did the feature branch decrease it from 5000 to 3000? There’s no way to know. To find out, you are forced to run git log or git blame to dig through the history. It’s a huge waste of time.
To address this problem, Git has long offered the diff3 mode. This mechanism injects a ||||||| base block in the middle, representing the Common Ancestor before the branches diverged.
Unfortunately, the classic diff3 is quite unrefined. It often drags identical lines from both sides into the conflict region, making the conflict marker unnecessarily long and cluttered with noise.
Starting with version 2.35 (released in early 2022), Git introduced zdiff3 (Zealous diff3) to overcome this limitation. The word “Zealous” reflects its aggressive approach: Git actively trims matching lines at the start and end of the conflict block. The resulting display isolates only the lines that are truly in conflict, alongside their corresponding base state. After adopting zdiff3 across our 12-engineer team, accidental logic overwrites during releases dropped by more than 80%.
Checking Your Git Version
This feature is natively available starting with Git 2.35. Check the version installed on your machine:
git --version
If your version is older than 2.35 (for example, Ubuntu 20.04 LTS defaults to Git 2.25), you will need to update Git:
# Ubuntu / Debian
sudo add-apt-repository ppa:git-core/ppa -y
sudo apt update
sudo apt install git -y
# macOS (Homebrew)
brew install git
# Arch Linux
sudo pacman -S git
As long as you are running Git 2.35 or newer, you are good to go. No external plugins are required.
How to Enable zdiff3 in Git
Depending on your workflow, you can enable it globally across your machine or configure it on a per-repository basis.
1. Global Configuration (Recommended)
Run this command once in your terminal to apply it across all projects on your machine:
git config --global merge.conflictstyle zdiff3
2. Configure Directly via .gitconfig
If you prefer editing your user configuration file at ~/.gitconfig, simply add these two lines:
[merge]
conflictstyle = zdiff3
3. Apply to a Specific Repository
When working within a specific company repository with unique requirements, just drop the --global flag:
cd /path/to/your-project
git config merge.conflictstyle zdiff3
4. Real-world Comparison: default vs zdiff3
Suppose our original file defines a billing calculation function:
// common ancestor
function calculateTotal(price, tax) {
const subtotal = price * 1.1;
return subtotal + tax;
}
The main branch updates the tax rate from 10% to 15%:
function calculateTotal(price, tax) {
const subtotal = price * 1.15;
return subtotal + tax;
}
The feature branch renames subtotal to amount and bumps the tax rate to 20%:
function calculateTotal(price, tax) {
const amount = price * 1.2;
return amount + tax;
}
When merging the two branches, zdiff3 cleanly consolidates the conflict as follows:
function calculateTotal(price, tax) {
<<<<<<< HEAD
const subtotal = price * 1.15;
||||||| base
const subtotal = price * 1.1;
=======
const amount = price * 1.2;
>>>>>>> feature
return subtotal + tax; // Needs adjustment to the new variable name after merge
}
Thanks to the ||||||| base block, everything is immediately clear:
- The original base line was
subtotal = price * 1.1. - The HEAD branch only bumped the multiplier to 1.15.
- The feature branch bumped the multiplier to 1.2 and renamed the variable to
amount.
You can see the exact origin of each change from both sides, allowing you to make the correct merge decision in under 10 seconds.
Verification and Hands-on Testing
Once configured, run the following command to verify that Git has registered the setting:
git config --get merge.conflictstyle
If the output prints zdiff3, you’re set.
Create a 30-Second Test Scenario
You can run this quick test script in the /tmp directory to see zdiff3 in action firsthand:
# 1. Create a test repository
mkdir /tmp/test-zdiff3 && cd /tmp/test-zdiff3
git init
git config merge.conflictstyle zdiff3
# 2. Create the base commit (Ancestor)
echo -e "alpha\nbravo\ncharlie" > test.txt
git add test.txt
git commit -m "Base commit"
# 3. Create branch A and modify the bravo line
git checkout -b branch-a
echo -e "alpha\nbravo-modified-A\ncharlie" > test.txt
git commit -am "Branch A update"
# 4. Return to main, create branch B, and modify the same location
git checkout main
git checkout -b branch-b
echo -e "alpha\nbravo-modified-B\ncharlie" > test.txt
git commit -am "Branch B update"
# 5. Trigger the merge conflict
git merge branch-a
Open test.txt, and you will see a clean, well-isolated conflict:
alpha
<<<<<<< HEAD
bravo-modified-B
||||||| base
bravo
=======
bravo-modified-A
>>>>>>> branch-a
charlie
The lines alpha and charlie remain intact outside the conflict marker. You can clearly trace how both branches diverged from the original bravo line.
Seamless Compatibility with VS Code and JetBrains
Modern IDEs like VS Code, Cursor, and the JetBrains suite (IntelliJ, WebStorm) natively support this 3-way layout. In VS Code, opening a conflicted file configured with zdiff3 automatically activates the 3-Way Merge Editor. The Common Ancestor appears right in the center, flanked by Current and Incoming changes. You can simply tick the boxes for the lines you want to keep without the risk of accidentally wiping out your teammate’s work.
