Merging with diff3: the “three-way merge”
blog.jcoglan.com
blog.jcoglan.com
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.
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) : )
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
D
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
ediff-merge-with-ancestor-job))
split-window-function
merge-window-share merge-window-lines
(buf-Ancestor (with-current-buffer control-buffer
ediff-ancestor-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))
(delete-other-windows)
(set-window-dedicated-p (selected-window) nil)
(split-window-vertically)
(ediff-select-lowest-window)
(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)
(split-window-horizontally)
(when (eq (selected-window) wind-B)
(other-window 1))
(switch-to-buffer buf-Ancestor)
(setq wind-Ancestor (selected-window)))
(balance-windows-area)
(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-select-lowest-window)
(minimize-window)
(ediff-setup-control-buffer control-buffer)
))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!
git log -p HEAD...MERGE_HEAD --path/to/file
https://www.git-scm.com/docs/git-logAnother 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.
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.
> And give me the revision/commit id's for each
It could show the branch names! `change on master`, `change on myfeature` or whatever.
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.
[merge]
conflictstyle = diff3So 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) :
https://jneem.github.io/pijul/
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 ?) :
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.
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!
https://GitHub.com/samsquire/text-diff
It's not complete and the colouring is probably not the right way round.
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.
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.
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.
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