Using ediff as git mergetool

前端 未结 12 489
天命终不由人
天命终不由人 2020-12-12 10:22

I would like to be able to use ediff with \"git mergetool\".

I found some patches that alter the source code, which I don\'t want to do. Instead, I\'d like to add ed

相关标签:
12条回答
  • 2020-12-12 11:01

    Combining my favorite ideas from above. This configuration uses emacsclient and require therefore that an emacs is already running.

    This also works for git difftool - it will invoke ediff-files. (When git difftool calls then the ancestor will be equal to the merged.)

    In .gitconfig:

    [mergetool "ec-merge"]
            prompt = false
            cmd = ec-merge "$LOCAL" "$REMOTE" "$BASE" "$MERGED"
            trustExitCode = true
    [merge]
            tool = ec-merge
    [difftool]
            prompt = false
    

    In ~/bin/ec-merge (make sure ~/bin is in your PATH):

    #!/bin/bash
    
    set -e
    
    LOCAL=$(readlink -f "$1")
    REMOTE=$(readlink -f "$2")
    BASE=$(readlink -f "$3")
    MERGED=$(readlink -f "$4")
    
    emacsclient --eval "(jcl-git-merge \"$LOCAL\" \"$REMOTE\" \"$BASE\" \"$MERGED\")"
    
    ! egrep -q '^(<<<<<<<|=======|>>>>>>>|####### Ancestor)' "$MERGED"
    

    In .emacs:

    (server-start)
    
    (defvar jcl-save-and-kill-buffers-before-merge nil
      "Normally if emacs already visits any of the concerned files (local,
    remote, base or merged) ediff will ask it shall save and kill the
    buffer.  If you always want to answer yes to this then set this 
    to non-nil.")
    
    (defun jcl-git-merge (local remote ancestor merged)
      (when jcl-save-and-kill-buffers-before-merge
        (dolist (file (list local remote ancestor merged))
          (setq file (file-truename file))
          (let ((old-buffer (and file (find-buffer-visiting file))))
            (when old-buffer
              (with-current-buffer old-buffer
                (save-buffer))
              (kill-buffer old-buffer)))))
      (prog1
          (if (string-equal ancestor merged)
              (progn
                (ediff-files local remote (list 'jcl-exit-recursive-edit-at-quit))
                (format "ediff compared %s and %s" local remote))
            (if ancestor
                (ediff-merge-files-with-ancestor local remote ancestor
                                                 (list 'jcl-exit-recursive-edit-at-quit)
                                                 merged)
              (ediff-merge-files local remote (list 'jcl-exit-recursive-edit-at-quit merged)))
            (format "ediff merged %s" merged))
        (recursive-edit)))
    
    (defun jcl-exit-recursive-edit-at-quit ()
      (add-hook 'ediff-quit-hook (lambda () (throw 'exit nil)) t t))
    

    Normally if emacs already visits any of the concerned files (local, remote, base or merged) ediff will ask it shall save and kill the buffer. If you like me always want to answer yes to this then add also this to your .emacs:

    (setq jcl-save-and-kill-buffers-before-merge t)
    
    0 讨论(0)
  • 2020-12-12 11:02

    Aside from the git vs bzr issue I identified in my comment above, I was able to confirm that you need to escape the parens as in

     cmd = emacs --eval "\\(ediff-merge-files-with-ancestor \"$LOCAL\" \"$REMOTE\" \"$BASE\" nil \"$MERGED\"\\)"
    

    Note the double backslash characters. I kind of understand that they are needed (rather than a single one) to get through both the sh/bash quoting AND the emacs startup quoting mechanisms. I'll leave it to someone with a better grasp of Emacs and shell quoting to explain the gory details.

    -pmr

    0 讨论(0)
  • 2020-12-12 11:02

    Here's a variant of tarsius's setup. It handles when the ancestor file $BASE doesn't exist, and it allows you to abort the merge without trashing git's state about the conflict (by not automatically saving on exit). It also has newlines backslashed so that you can keep the formatting.

    [mergetool.ediff]
        cmd = emacs --eval \" \
    (progn \
      (setq ediff-quit-hook 'kill-emacs) \
      (if (file-readable-p \\\"$BASE\\\") \
          (ediff-merge-files-with-ancestor \\\"$LOCAL\\\" \\\"$REMOTE\\\" \
                                           \\\"$BASE\\\" nil \\\"$MERGED\\\") \
          (ediff-merge-files \\\"$LOCAL\\\" \\\"$REMOTE\\\" nil \\\"$MERGED\\\")))\"
    
    0 讨论(0)
  • 2020-12-12 11:07

    There is a way to use the ediff-merge-files-with-ancestor function with emacsclient.

    The simplest one (for the GNU/Linux user) is to do a shell read from a pipe after the emacsclient call. An hook added in append to ediff-quit-hook (it must be run after ediff-cleanup-mess otherwise ediff session is not terminated properly) will shot a character in the pipe through shell-command.

    A more refined one will use a semaphore.

    And here arrives the Unix power user.

    Then arrives the Emacs Guru (Stefan Monnier) and tells you that you can call

    emacsclient --eval '(progn (ediff-merge-files-wit.......) (recursive edit))'

    after adding

    (throw 'exit )

    somewhere at the end of ediff-quit-hook. No named pipe, no semaphores, just Emacs LISP. Simple, elegant and does not require weird tests to avoid using pipes or semaphores when they are not used.

    Thank you Stefan!

    0 讨论(0)
  • 2020-12-12 11:09

    I use a a more complicated command. As far as I remember I got it from this thread http://kerneltrap.org/mailarchive/git/2007/6/28/250230 (probably the same as what you are referring to).

    [mergetool.ediff]
        cmd = emacs --eval \"\
    (progn\
      (defun ediff-write-merge-buffer ()\
        (let ((file ediff-merge-store-file))\
          (set-buffer ediff-buffer-C)\
          (write-region (point-min) (point-max) file)\
          (message \\\"Merge buffer saved in: %s\\\" file)\
          (set-buffer-modified-p nil)\
          (sit-for 1)))\
      (setq ediff-quit-hook 'kill-emacs\
            ediff-quit-merge-hook 'ediff-write-merge-buffer)\
      (ediff-merge-files-with-ancestor \\\"$LOCAL\\\" \\\"$REMOTE\\\"\
                                       \\\"$BASE\\\" nil \\\"$MERGED\\\"))\"
    

    Note that I have split this across several lines to increase readability and escaped the newline with \ so git config considers it as a single line.

    I usually use emacsclient to edit e.g. commit messages. The above mergetool configuration unfortunately does not use emacsclient, and when I tried to get it to work with emacsclient I ran in to various problems including the fact that emacsclient returned right away.

    But you just reminded me of that issue, so I might work on fixing that problem soon. However if someone else already found a solution that would be great of course ;-)

    0 讨论(0)
  • 2020-12-12 11:13

    This is a nice discussion about doing this usuing mercurial. It looks as though they have a wrapper script which alleviates the emacsclient issue: https://www.mercurial-scm.org/wiki/MergingWithEmacs

    0 讨论(0)
提交回复
热议问题