[HN Gopher] Merging with diff3: the "three-way merge"
       ___________________________________________________________________
        
       Merging with diff3: the "three-way merge"
        
       Author : fortran77
       Score  : 81 points
       Date   : 2023-01-28 14:36 UTC (8 hours ago)
        
 (HTM) web link (blog.jcoglan.com)
 (TXT) w3m dump (blog.jcoglan.com)
        
       | munhitsu wrote:
       | [dead]
        
       | forrestthewoods wrote:
       | 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.
        
       | natmaka wrote:
       | Emacs 'ediff' is quite convenient.
       | https://www.gnu.org/software/emacs/manual/html_node/ediff/Me...
        
         | TacticalCoder wrote:
         | 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.
        
           | User23 wrote:
           | 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.
        
             | TacticalCoder wrote:
             | > 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) : )
        
               | Razengan wrote:
               | Literally scrolling
        
           | Icathian wrote:
           | 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.
        
             | TacticalCoder wrote:
             | 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                  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)             ))
        
       | IshKebab wrote:
       | 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!
        
         | js2 wrote:
         | 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
         | 
         | https://www.git-scm.com/docs/git-log
         | 
         | 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.
        
         | mambru wrote:
         | IIRC vscode with gitlens extension shows blame on hover, also
         | for the conflicting lines.
        
       | acdha wrote:
       | 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.
        
       | b3morales wrote:
       | 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.
        
       | samsquire wrote:
       | I implemented this in Python based on his guide to the Myers
       | algorithm.
       | 
       | https://GitHub.com/samsquire/text-diff
       | 
       | It's not complete and the colouring is probably not the right way
       | round.
        
       | mkoubaa wrote:
       | 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
        
         | spyremeown wrote:
         | 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.
        
         | bmn__ wrote:
         | <https://apps.kde.org/kdiff3> is partially a text editor, try
         | it out and develop an appreciation for it.
        
         | softfalcon wrote:
         | 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!
        
       | tome wrote:
       | Ah, I wrote an article on resolving diff3 conflicts by hand:
       | http://h2.jaguarpaw.co.uk/posts/git-rebase-conflicts/
        
       | secondcoming wrote:
       | p4merge is one of the first tools I download when I have to set
       | up a new environment.
        
         | darekkay wrote:
         | 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.
        
       | politician wrote:
       | Shoutout to kdiff3.
        
         | joncp wrote:
         | Totally. The newest version is 8 years old and it still works
         | great.
        
           | pdhborges wrote:
           | Kdiff3 is still developed. You can download newer versions
           | here: https://download.kde.org/stable/kdiff3/
        
       | BaculumMeumEst wrote:
       | 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
        
       | BlueTemplar wrote:
       | 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) :
       | 
       | 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 ?) :
       | 
       | https://tahoe-lafs.org/~zooko/badmerge/simple.html
       | 
       | https://pijul.org/faq
        
       | ChrisCinelli wrote:
       | 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.
        
         | loeg wrote:
         | Yeah, it's one of the first things I change in .gitconfig or
         | .hgrc:                 [merge]        conflictstyle = diff3
        
       | kmarc wrote:
       | \git mergetool\ runs your favourite editor/difftool to do a 3way
       | merge. I found vim to be a perfect tool for doing this
        
       ___________________________________________________________________
       (page generated 2023-01-28 23:00 UTC)