Skip to main content

Fourdiff: A new way to resolve merge conflicts with ease and confidence

A few years ago I was trying to resolve a particularly horrible merge conflict, and as a way to escape the horror, I started thinking: could there be a better way? A way that would let me really understand what I'm doing, instead of trying to guess my way out of the mess? Thanks God, I found such a way, and I've been using it ever since. I think it can help others as well. Would you like to give it a try?

To try it on Ubuntu (including WSL), install uv, some packages required to build pygobject, and meld-fourdiff:

curl -LsSf https://astral.sh/uv/install.sh | sh
sudo apt update && sudo apt install -y --no-install-recommends libgirepository1.0-dev gcc libcairo2-dev pkg-config gir1.2-gtk-3.0 gir1.2-gtksource-4
uv tool install --python 3.10 meld-fourdiff

Make sure that meld-fourdiff works properly, by running meld-fourdiff and making sure you see a window, and add this section to your ~/.gitconfig:

[mergetool "fourdiff"]
    cmd = meld-fourdiff "$BASE" "$REMOTE" "$LOCAL" "$MERGED"

I want to share with you a hack I made about two years ago, which I'm using to resolve merge conflicts (and cherry-pick conflicts, and revert conflicts) in my job. Before I discovered this idea, I have hated merges, I never understood what was going on. Now I resolve conflicts with confidence. It's actually really hard for me to use any other tool.

It looks like this. After running git merge and getting merge conflicts, I run git mergetool and see this (except for the arrows): In every merge, the goal is to take the difference between two revisions (the "base revision" and the "remote revision") and apply it to the current revision (the "local revision"). From left to right we see: 1. the remote revision 2. the base revision 3. the local revision (without any changes applied) and 4. the merged revision, which is what we have in our working directory.

The first lines show a successful merge. We can see that the change between BASE and REMOTE is the same change between LOCAL and merged. Namely, the change is to remove the name argument and remove the print() line. I can visually verify that the changes along the left arrow look the same as the changes along the right arrow.

Then, we have a merge conflict. Git doesn't know how to apply the change, and leaves the job to us. The right pane shows the git merge conflict format. To find the conflicts, click the right pane, press Ctrl-F, and search for <<<.

To better understand what's going on, I can press Ctrl-T, and the view switches to show the difference between BASE and LOCAL. This difference is the cause of the merge conflict:

We see that the change between BASE and LOCAL is that we added another argument to the function, so it now adds 3 numbers instead of 2 (Please excuse my stupid example). Now that I understand what causes the merge conflict, I can apply the change with confidence, by pressing again Ctrl-T to switch back to the original view, and editing the file on the right. I get this:

Again, I can visually verify that the change applied from LOCAL to MERGED looks the same as the change from BASE to REMOTE.

Perhaps it would help to show how the conflict looks with existing tools. If I run git mergetool -t meld, I get this:

On one side we have the arguments name, a, b, c and a print. On the other side we have only a, b and no print. What should I take? The truth is that It's actually impossible to resolve the conflict using only LOCAL and REMOTE1.

Installation

This is known to work on Ubuntu 20.04. You just need to clone my fork of meld, the excellent diff viewer, and configure git to use it to resolve merge conflicts.

Run this:

cd ~/
git clone -b fourdiff https://github.com/noamraph/meld.git
sudo apt install meld  # to install dependencies

And add this to ~/.gitconfig:

[mergetool "fourdiff"]
    cmd = ~/meld/bin/meld "$REMOTE" "$BASE" "$LOCAL" "$MERGED"
[merge]
    tool = fourdiff

If you want to test it with the example I showed above, run this:

cd /tmp
git clone https://github.com/noamraph/conflict-example.git
cd conflict-example/
git merge origin/remove-greetings

You should get a merge conflict. Run this and the fourdiff should appear:

git mergetool

Final thoughts

I find this to be a simple and very effective concept. I hope the meld developers would agree to add this feature, and I hope others will add it as well.

My initial thought was to arrange the panes in this order: BASE, REMOTE, LOCAL, MERGED. This would have made both the original change and the new change apply from left to right. However, this would have made the second view, showing the difference between BASE and LOCAL, confusing.

If you're a developer of a diff viewer and you want to add this to your app, the actual implementation is quite simple: there are always three 2-way diff widgets behind the scenes: BASE-REMOTE, REMOTE-LOCAL, LOCAL-MERGED. When one REMOTE is scrolled the other REMOTE is scrolled, and the same goes with LOCAL and LOCAL. This keeps all the panes in sync. The user can switch between showing BASE-REMOTE and LOCAL-MERGED, and showing only REMOTE-LOCAL.

  1. I could in theory compare the middle column to both sides, understand how it changed relative to both sides, and figure out how to apply both changes. But the visual diff doesn't help this at all, and after editing the middle there's no way to check this, and all this wouldn't work at all if you do a revert or a cherry-pick. ↩

Metadata

Release files for meld-fourdiff 0.0.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for meld-fourdiff 0.0.5
File Size Uploaded Provenance Actions
meld_fourdiff-0.0.5.tar.gz 1.6 MB Without provenance Details for meld_fourdiff-0.0.5.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for meld-fourdiff 0.0.5
File Interpreter ABI Platform Actions
meld_fourdiff-0.0.5-py3-none-any.whl Python 3 none any Details for meld_fourdiff-0.0.5-py3-none-any.whl

Total release size: 2.7 MB

Release files / meld_fourdiff-0.0.5.tar.gz

Download URL meld_fourdiff-0.0.5.tar.gz
Size 1.6 MB
Tags Source
SHA-256 checksum
How to use checksums
f49dc00ef2711dcd8acb64f175a32635678ccc424536ad30923de74e82e50f36
BLAKE2b-256 checksum
How to use checksums
56df8fc013e4b166d3f86a41d87189e72b2b23bddde2a669ac0fe4da10a25390
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / meld_fourdiff-0.0.5-py3-none-any.whl

Download URL meld_fourdiff-0.0.5-py3-none-any.whl
Size 1.1 MB
Tags Python 3
SHA-256 checksum
How to use checksums
00fbbba30dcfebd26fe530fb1dccd8b9b423a4e88cff3aa1d9c956f9227862c7
BLAKE2b-256 checksum
How to use checksums
5298122a34a6890fa362373eebb20feaa888227f2d15b534ca78864de2c55955
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for meld-fourdiff 0.0.5

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page