Hacker News new | past | comments | ask | show | jobs | submit login
Merging with diff3: the “three-way merge” (jcoglan.com)
127 points by fortran77 7 months ago | hide | past | favorite | 53 comments

It is. I've got a Emacs setup to show 3-ways merge in ediff which differs from the default. I show, side by side, three columns: "variant A", "common ancestor", "variant B" and then, below, the result.

I do really enjoy my wide screen while doing that. I'm literally moving to a 360 columns mode when I do that: 120 columns for each of variant A, common ancestor and variant B. It's the only time I make Emacs that wide.

I like to have the common ancestor in the middle, so I can quickly see where both "variant A" and "variant B" are coming from.

So I'm using Emacs's ediff to do 3-ways merge while showing four versions of the code.

That’s really cool. As an aside I’m convinced there’s no such thing as too much screen space for any kind of cognitive work. Letting the optic nerve offload work from your short term memory is a huge gain.

I’ll still occasionally print out source and lay it out on a table to annotate by hand when I’m learning a complex code base.

> I’ll still occasionally print out source and lay it out on a table to annotate by hand when I’m learning a complex code base.

I've got fond memories of printing programs on continuous form paper (the one with holes on the side that the printer would use to advance the paper) and then reading it to find my bug(s) : )

Literally scrolling

Mind sharing that? Merge resolver view is the only thing I'm still opening vscode for and I'd be very happy to finally kill that off.

I basically just slightly modified the code in ediff-setup-windows-plain-merge from ediff-wind.el.

Not too sure how to paste code on HN, here's the modified version which works for me. The orginal ediff-setup-windows-plain-merge may depend on your Emacs version though, so buyer beware (things may have changed and need to be revisited).

Instead of showing:

        A      B
        C      D
    Ediff control panel
I show:

        A    B   C
        Ediff control panel
As for Git AFAICT all I have is git config --global merge.conflictStyle diff3.

Compare it to your version of ediff-setup-windows-plain-merge to see what I changed:

    (defun ediff-setup-windows-plain-merge (buf-A buf-B buf-C control-buffer)
      ;; skip dedicated and unsplittable frames
      (ediff-destroy-control-frame control-buffer)
      (let ((window-min-height 1)
            (with-Ancestor-p (with-current-buffer control-buffer
            merge-window-share merge-window-lines
            (buf-Ancestor (with-current-buffer control-buffer
            wind-A wind-B wind-C wind-Ancestor)
        (with-current-buffer control-buffer
          (setq merge-window-share ediff-merge-window-share
                ;; this lets us have local versions of ediff-split-window-function
                split-window-function ediff-split-window-function))
        (set-window-dedicated-p (selected-window) nil)
        (ediff-setup-control-buffer control-buffer)
        ;; go to the upper window and split it betw A, B, and possibly C
        (other-window 1)
        (setq merge-window-lines
              (max 2 (round (* (window-height) merge-window-share))))
        (switch-to-buffer buf-A)
        (setq wind-A (selected-window))
        (split-window-vertically (max 2 (- (window-height) merge-window-lines)))
        (if (eq (selected-window) wind-A)
            (other-window 1))
        (setq wind-C (selected-window))
        (switch-to-buffer buf-C)
        (select-window wind-A)
        (funcall split-window-function)
        (if (eq (selected-window) wind-A)
            (other-window 1))
        (switch-to-buffer buf-B)
        (setq wind-B (selected-window))
        (when (and ediff-show-ancestor with-Ancestor-p)
          (select-window wind-B)
          (when (eq (selected-window) wind-B)
            (other-window 1))
          (switch-to-buffer buf-Ancestor)
          (setq wind-Ancestor (selected-window)))
        (with-current-buffer control-buffer
          (setq ediff-window-A wind-A
                ediff-window-B wind-B
                ediff-window-C wind-C
                ediff-window-Ancestor wind-Ancestor))
        (ediff-setup-control-buffer control-buffer)

Awesome, I'll play with this a bunch on Monday. Thank you!

The problem with 3-way merge (when it comes to rebasing and merging at least) is that you lose all the information about why "theirs" changed.

You made a modification to the code. They made a conflicting modification. You want to know what they hell they were doing so you know how to rejig your edit. The normal tool to use here would be `git blame` but that just doesn't work. At least in any tool I have used.

If anyone knows of one where it does work I'd love to know!

When I'm confused by a 3-way diff (I personally prefer FileMerge/opendiff on macOS) and need more info I like to use git log to examine the relevant commits using the "..." operator which returns the symmetric difference between two commits (in this case both sides of the merge) and their merge base:

    git log -p HEAD...MERGE_HEAD --path/to/file

Another trick is doing multiple merges which each include fewer commits. Sometimes the problem is that you're just trying to merge too much at a time. The downside of this is that in files with a lot of churn you may have to deal with conflicts that you might otherwise not. So it's a bit of a two-edged sword.

IIRC vscode with gitlens extension shows blame on hover, also for the conflicting lines.

The new 3 way merge editor in VS Code tries to load the documents for base, yours and theirs from the corresponding commit URI, so that extensions such as gitlens can show the line history for each document - they don't even have to know about the merge editor. (disclaimer - I work on VS Code and its 3 way merge editor)

> for base, yours and theirs

I really hate that relative terminology. Call them "working copy", "merging commit" and "common ancestor" or something similarly absolute. And give me the revision/commit id's for each, so it's crystal clear in case I want to check up manually.

Not a dig on the vscode extension, but 3-way diff tools in general.

Yeah I 100% agree. Especially as Git gets it backwards in some cases! It's not even consistent about the names. Sometime I see "current" and "incoming" which is not really any better.

> And give me the revision/commit id's for each

It could show the branch names! `change on master`, `change on myfeature` or whatever.

The 3 way merge editor actually shows the branch name!

Maybe I'll give it another got but to be honest I found the new merge editor even more confusing than just reading zdiff3. Sorry!

That's what I'm using. Unfortunately it doesn't. :-/

diff3 is the way to go. When there are not trivial changes, it is hard to resolve conflicts if you do not know where the conflicting changes come from.

I knew quite a few people that do not know that this option even exists. In my opinion it could make the life of engineers a lot better if this was the default.

Yeah, it's one of the first things I change in .gitconfig or .hgrc:

   conflictstyle = diff3

The git zdiff3 conflictstyle is a newer better version of diff3.


Then, at least for VCS, there are Darcs and Pijul, which use a theory of patches.

So Pijul manages to have lossless merges by actually storing a directed graph (though of course, you will still need to decide how to flatten that into a displayed file) :


And because it uses more information about the history, it is able to do smarter merges (if I am not mistaken, even compared to the OP ?) :



The diff listings are a bit confusing because the line numbers look like they're part of the text. For example in the first diff, '2. salmon 3. tomatoes' is right next to '4. salmon 5. tomatoes' but those aren't highlighted as a change.

Am I the only one that prefers manually resolving conflicts in a text editor? It sucks but I can't imagine a GUI that sucks less

Try any of the recommendations here (my poison is Meld). It's actually one of the few tasks where I really, really do prefer a GUI over text.

<https://apps.kde.org/kdiff3> is partially a text editor, try it out and develop an appreciation for it.

I have and use kdiff3 regularly for other purposes

A triple-panel editor has made this much less scary for me over the years. Jetbrains do it really well, I think I've used VS Code for this too.

You actually need four panels; common ancestor, yours, theirs, and finally the merge-result.

This is how Kdiff3 and Beyond compare does it and it is far superior to just having three panels.

A lot of other tools leave out the base or combine it with the result-panel, making it very hard to see what has changed from each side once you start resolving conflicts.

A 4K monitor is also a must.

I think there's a terminology confusion here. I call kdiff3 and BC a three-panel display, because it shows the original and the two changed versions as three panels next to each other. Since the result is shown separately below, I don't include it in the panel count. I only realized that strictly speaking you could also count it as a panel from your post.

Nope! I’ve done this for years now. It made the most sense to me.

Some people are at a loss as to how I can resolve a conflict without any tools (other than git + text edit) though, feels like a super power!

> The central pane is a fully-functional editor where the results of resolving conflicts are displayed. Initially, the contents of this pane are the same as the base revision of the file, that is, the revision from which both conflicting versions are derived.


I like using Perforce's three-way merge. The only thing confusing is Git's ridiculous naming for remote and local.

Same for me. Always used a text editor.

I implemented this in Python based on his guide to the Myers algorithm.


It's not complete and the colouring is probably not the right way round.

Araxis Merge has been my go-to merge tool for 15 years. Everyone I work with pretty much uses either Araxis or Beyond Compare. BC has some nice features, but I've always found the Araxis UI easier to use.

It'd be nice if there was a good, free merge tool. But so far every one I've tried is inferior. And companies are usually happy to pay for a productivity tool as critical as merge tools.

I've found Meld pretty good for free, but I only use it for hobby stuff so I've never needed to try out the 3-way aspect. But for side-by-side folder and file diffs I've found it decent enough relative to Araxis. And, like Araxis, both sides are editable, something not all diff tools get right. So you can experiment, and/or set up a blank diff and paste stuff in to see what you get.

Main problem I've had is that it's slow for large files. I think it's written in Python.

An outsize annoyance (which is objectively just a minor UX nit) is that the diff setup dialog displays names only, not full paths. Which is silly, because a very common use case for diff tools is diffing two files that have the same name, so I notice this on about 75% of uses.

Meld is decent. A few folks on my team used it for a bit. Unfortunately it had some issues where its merges were actually wrong and broke things. :(

kdiff3 never failed me, and having separate original, theirs, yours and result panels, I find it much clearer to work with than Meld. Often it even auto-resolves merges that Git couldn't.

Shoutout to kdiff3.

Totally. The newest version is 8 years old and it still works great.

Kdiff3 is still developed. You can download newer versions here: https://download.kde.org/stable/kdiff3/

My favorite merge tool remains Source Gear’s DiffMerge but it hadn’t been updated in years. I like some of what Kaleidoscope does but it’s not as good at automatic conflict resolution.

p4merge is one of the first tools I download when I have to set up a new environment.

What I like about p4merge is that it displays four panels: local/remote/result (what most 3-way merge tools use), but also the common ancestor of local and remote ("base"). It really helps with some more tricky merges.

With that said, after using p4merge for years, I now tend to rely on the built-in merge tool in IntelliJ IDEA. Especially the "magic wand" is very handy.

This was a saviour. I wanted to merge a previously done extensive change, say B for the original file A and also a different kind of change, say C for the same original file A. I had to merge both the changes. Diff3 came as a saviour.

Ah, I wrote an article on resolving diff3 conflicts by hand: http://h2.jaguarpaw.co.uk/posts/git-rebase-conflicts/

https://github.com/Peaker/git-mediate is an amazing secret weapon for making quick work of 3-way conflict resolution.

i always want to use vimdiff with git mergetool but i’ve never been able to figure out how to use it when i launch the 3way split. i know there are commands to choose between local/base/remote for each conflict i dont know where my cursor is supposed to be or where i should be using those commands from.

i always end up going back to emacs and just using smerge, with its super straightforward “go to next conflict” and “choose upper or lower” commands

ˋgit mergetoolˋ runs your favourite editor/difftool to do a 3way merge. I found vim to be a perfect tool for doing this

I am just going to say one thing: pijul

Looks interesting but not quite finished.

kdiff3 is a very good gui tool for that.

Applications are open for YC Winter 2024

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact