Using ediff as git mergetool

前端 未结 12 490
天命终不由人
天命终不由人 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:16

    This was a valuable find for me. I have a small addition, since I use emacs desktop-save-mode:

    [mergetool "ediff"]
    cmd = emacs --no-desktop -eval \"(ediff-merge-files-with-ancestor \\\"$PWD/$LOCAL\\\" \\\"$PWD/$REMOTE\\\" \\\"$PWD/$BASE\\\" nil \\\"$PWD/$MERGED\\\")\"
    

    and added the "(when" clause below, because I prefer a multi-frame ediff normally:

    ;;
    ;; Setup for ediff.
    ;;
    (require 'ediff)
    
    (when (or (not desktop-save-mode) (member "--no-desktop" command-line-args))
          (defvar ediff-after-quit-hooks nil
           ... (rest of TauPan's code here) ...
    )
    
    0 讨论(0)
  • 2020-12-12 11:19

    Here's my setup, which works fairly well, using Emacs 23.3 at least. The trick I used was using (recursive-edit) in a hook such that emacsclient does not exit until an advised ediff-quit hook calls (exit-recursive-edit).

    I used an advisted ediff-quit to ensure the exit-recursive-edit is the very last thing done.

    There are also hooks to save the current frame and window state and restore it afterwards, and the hook makes the current frame fill the screen. You may wish to modify that, but I find merging full screen is the best way.

    I've not solved the issue of aborting the ediff and making emacsclient return a non-zero exit.

    Put in your gitconfig:

    [mergetool "ediff"]
           cmd = emacsclient --eval \"(git-mergetool-emacsclient-ediff \\\"$LOCAL\\\" \\\"$REMOTE\\\" \\\"$BASE\\\" \\\"$MERGED\\\")\"
           trustExitCode = true
    [mergetool]
        prompt = false
    [merge]
        tool = ediff
    

    Put in your .emacs or equivalent:

    ;;
    ;; Setup for ediff.
    ;;
    (require 'ediff)
    
    (defvar ediff-after-quit-hooks nil
      "* Hooks to run after ediff or emerge is quit.")
    
    (defadvice ediff-quit (after edit-after-quit-hooks activate)
      (run-hooks 'ediff-after-quit-hooks))
    
    (setq git-mergetool-emacsclient-ediff-active nil)
    
    (defun local-ediff-frame-maximize ()
      (let* ((bounds (display-usable-bounds))
         (x (nth 0 bounds))
         (y (nth 1 bounds))
         (width (/ (nth 2 bounds) (frame-char-width)))
         (height (/ (nth 3 bounds) (frame-char-height))))
        (set-frame-width (selected-frame) width)
        (set-frame-height (selected-frame) height)
        (set-frame-position (selected-frame) x y)))
    
    (setq ediff-window-setup-function 'ediff-setup-windows-plain)
    (setq ediff-split-window-function 'split-window-horizontally)
    
    (defun local-ediff-before-setup-hook ()
      (setq local-ediff-saved-frame-configuration (current-frame-configuration))
      (setq local-ediff-saved-window-configuration (current-window-configuration))
      (local-ediff-frame-maximize)
      (if git-mergetool-emacsclient-ediff-active
          (raise-frame)))
    
    (defun local-ediff-quit-hook ()
      (set-frame-configuration local-ediff-saved-frame-configuration)
      (set-window-configuration local-ediff-saved-window-configuration))
    
    (defun local-ediff-suspend-hook ()
      (set-frame-configuration local-ediff-saved-frame-configuration)
      (set-window-configuration local-ediff-saved-window-configuration))
    
    (add-hook 'ediff-before-setup-hook 'local-ediff-before-setup-hook)
    (add-hook 'ediff-quit-hook 'local-ediff-quit-hook 'append)
    (add-hook 'ediff-suspend-hook 'local-ediff-suspend-hook 'append)
    
    ;; Useful for ediff merge from emacsclient.
    (defun git-mergetool-emacsclient-ediff (local remote base merged)
      (setq git-mergetool-emacsclient-ediff-active t)
      (if (file-readable-p base)
          (ediff-merge-files-with-ancestor local remote base nil merged)
        (ediff-merge-files local remote nil merged))
      (recursive-edit))
    
    (defun git-mergetool-emacsclient-ediff-after-quit-hook ()
      (exit-recursive-edit))
    
    (add-hook 'ediff-after-quit-hooks 'git-mergetool-emacsclient-ediff-after-quit-hook 'append)
    
    0 讨论(0)
  • 2020-12-12 11:20

    I use the following script as mergetool which works quite well.

    #!/bin/bash
    
    # test args
    if [ ! ${#} -ge 3 ]; then
        echo 1>&2 "Usage: ${0} LOCAL REMOTE MERGED BASE"
        echo 1>&2 "       (LOCAL, REMOTE, MERGED, BASE can be provided by \`git mergetool'.)"
        exit 1
    fi
    
    # tools
    _EMACSCLIENT=/usr/local/bin/emacsclient
    _BASENAME=/bin/basename
    _CP=/bin/cp
    _EGREP=/bin/egrep
    _MKTEMP=/bin/mktemp
    
    # args
    _LOCAL=${1}
    _REMOTE=${2}
    _MERGED=${3}
    if [ -r ${4} ] ; then
        _BASE=${4}
        _EDIFF=ediff-merge-files-with-ancestor
        _EVAL="${_EDIFF} \"${_LOCAL}\" \"${_REMOTE}\" \"${_BASE}\" nil \"${_MERGED}\""
    else
        _EDIFF=ediff-merge-files
        _EVAL="${_EDIFF} \"${_LOCAL}\" \"${_REMOTE}\" nil \"${_MERGED}\""
    fi
    
    # console vs. X
    if [ "${TERM}" = "linux" ]; then
        unset DISPLAY
        _EMACSCLIENTOPTS="-t"
    else
        _EMACSCLIENTOPTS="-c"
    fi
    
    # run emacsclient
    ${_EMACSCLIENT} ${_EMACSCLIENTOPTS} -a "" -e "(${_EVAL})" 2>&1
    
    # check modified file
    if [ ! $(egrep -c '^(<<<<<<<|=======|>>>>>>>|####### Ancestor)' ${_MERGED}) = 0 ]; then
        _MERGEDSAVE=$(${_MKTEMP} --tmpdir `${_BASENAME} ${_MERGED}`.XXXXXXXXXX)
        ${_CP} ${_MERGED} ${_MERGEDSAVE}
        echo 1>&2 "Oops! Conflict markers detected in $_MERGED."
        echo 1>&2 "Saved your changes to ${_MERGEDSAVE}"
        echo 1>&2 "Exiting with code 1."
        exit 1
    fi
    
    exit 0
    

    To use it with `git mergetool' put the following in your git config:

    [merge]
            tool = ediff
    
    [mergetool "ediff"]
            cmd = /path/to/ediff-merge-script $LOCAL $REMOTE $MERGED $BASE
            trustExitCode = true
    

    Additionally, you should check (in the script) the paths of the tools used and if the poor man's console detection works for you.

    The script itself starts an emacs client (or emacs followed by an emacs client, -a "") and evals either ediff-merge-files-with-ancestor or ediff-merge-files if there's no base version (e.g. when merging two branches where the same path/file has been created independently).

    After the emacs client has finished the merged file is checked for conflict markers. Should those be found, your work will be saved away to a temporary file, the script will exit with code 1 and git will restore the pre-mergetool contents of the merged file.

    When there are no conflict markers present, the script exits with code 0 and git will regard the merge as successful.

    Important: Setting the mergetool option trustExitCode to true as well as the post-edit check for conflict markers will not work if you start emacsclient with the --no-wait option.

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

    Thanks, it also works in xemacs, however the quoting as in the reply by pmr doesn't seem to work whereas I think the quoting in all the other replies is fine:

    [mergetool "ediff"]
        cmd = xemacs -eval \"(ediff-merge-files-with-ancestor \\\"$PWD/$LOCAL\\\" \\\"$PWD/$REMOTE\\\" \\\"$PWD/$BASE\\\" nil \\\"$PWD/$MERGED\\\")\"
    [merge]
        tool = ediff
    

    I put this above code in ~/.gitconfig.

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

    For using Subversion's interactive merge tool instead of git see this post for some instructions to set this up.

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

    The elisp code in Viper3369's code (Using ediff as git mergetool) uses a function "display-usable-bounds" which doesn't exist. Since the hooks do a lot more than is strictly necessary, simply deleting all references to "display-usable-bounds" is sufficient to make it work for me. Good work! ;)

    (Edit: I think I should post the modified emacs-lisp code:

    ;;
    ;; Setup for ediff.
    ;;
    (require 'ediff)
    
    (defvar ediff-after-quit-hooks nil
      "* Hooks to run after ediff or emerge is quit.")
    
    (defadvice ediff-quit (after edit-after-quit-hooks activate)
      (run-hooks 'ediff-after-quit-hooks))
    
    (setq git-mergetool-emacsclient-ediff-active nil)
    
    
    (setq ediff-window-setup-function 'ediff-setup-windows-plain)
    (setq ediff-split-window-function 'split-window-horizontally)
    
    (defun local-ediff-before-setup-hook ()
      (setq local-ediff-saved-frame-configuration (current-frame-configuration))
      (setq local-ediff-saved-window-configuration (current-window-configuration))
      ;; (local-ediff-frame-maximize)
      (if git-mergetool-emacsclient-ediff-active
          (raise-frame)))
    
    (defun local-ediff-quit-hook ()
      (set-frame-configuration local-ediff-saved-frame-configuration)
      (set-window-configuration local-ediff-saved-window-configuration))
    
    (defun local-ediff-suspend-hook ()
      (set-frame-configuration local-ediff-saved-frame-configuration)
      (set-window-configuration local-ediff-saved-window-configuration))
    
    (add-hook 'ediff-before-setup-hook 'local-ediff-before-setup-hook)
    (add-hook 'ediff-quit-hook 'local-ediff-quit-hook 'append)
    (add-hook 'ediff-suspend-hook 'local-ediff-suspend-hook 'append)
    
    ;; Useful for ediff merge from emacsclient.
    (defun git-mergetool-emacsclient-ediff (local remote base merged)
      (setq git-mergetool-emacsclient-ediff-active t)
      (if (file-readable-p base)
          (ediff-merge-files-with-ancestor local remote base nil merged)
        (ediff-merge-files local remote nil merged))
      (recursive-edit))
    
    (defun git-mergetool-emacsclient-ediff-after-quit-hook ()
      (exit-recursive-edit))
    
    (add-hook 'ediff-after-quit-hooks 'git-mergetool-emacsclient-ediff-after-quit-hook 'append)
    
    0 讨论(0)
提交回复
热议问题