使用ediff作为git mergetool

时间:2009-11-30 00:53:50

标签: git emacs merge

我希望能够将ediff与“git mergetool”一起使用。

我找到了一些改变源代码的补丁,我不想这样做。相反,我想在我的.gitconfig中添加ediff支持。

我知道git已经内置了对emerge的支持,但我更喜欢ediff。

我尝试将这些行添加到我的.gitconfig:

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

但是当我尝试使用“git mergetool --tool = ediff”运行时,我明白了:

eval: 1: Syntax error: "(" unexpected

我做错了什么?

12 个答案:

答案 0 :(得分:29)

我使用了一个更复杂的命令。据我所知,我从这个帖子http://kerneltrap.org/mailarchive/git/2007/6/28/250230得到了它(可能和你所说的一样)。

[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\\\"))\"

请注意,我已将其拆分为多行以提高可读性并使用\转义换行符,因此git config将其视为一行。

我通常使用emacsclient进行编辑,例如提交消息。遗憾的是,上面的mergetool配置不使用emacsclient,当我试图让它与emacsclient一起工作时,我遇到了各种问题,包括emacsclient立即返回。

但是你刚才提醒我这个问题,所以我可能会尽快修复这个问题。但是,如果其他人已经找到了一个当然很棒的解决方案; - )

答案 1 :(得分:13)

我使用以下脚本作为mergetool,效果很好。

#!/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

要与`git mergetool'一起使用,请在git config中添加以下内容:

[merge]
        tool = ediff

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

此外,您应该检查(在脚本中)所使用工具的路径以及穷人的控制台检测是否适合您。

脚本本身启动emacs客户端(或emacs后跟emacs客户端,-a "")并且如果没有基本版本(例如合并两个分支时)则会ediff-merge-files-with-ancestorediff-merge-files。其中相同的路径/文件是独立创建的。)

emacs客户端完成后,检查合并文件是否有冲突标记。如果找到这些文件,您的工作将被保存到临时文件中,脚本将以代码1退出,git将恢复合并文件的预合并工具内容。

当没有冲突标记存在时,脚本将以代码0退出,git会将合并视为成功。

重要提示:如果您使用trustExitCode启动true,则将合并工具选项emacsclient设置为--no-wait以及对冲突标记进行编辑后检查将无效{{1}}选项。

答案 2 :(得分:8)

这是我的设置,它运行得相当好,至少使用Emacs 23.3。我使用的技巧是在钩子中使用(递归编辑),这样emacsclient就不会退出,直到建议的ediff-quit钩子调用(exit-recursive-edit)。

我使用了一个推荐的ediff-quit来确保退出 - 递归 - 编辑是最后一件事。

还有钩子来保存当前帧和窗口状态并在之后恢复它,钩子使当前帧填满屏幕。您可能希望修改它,但我发现合并全屏是最好的方法。

我没有解决中止ediff并让emacsclient返回非零退出的问题。

加入你的gitconfig:

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

加入你的.emacs或同等文件:

;;
;; 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)

答案 3 :(得分:6)

除了我在上面的评论中指出的git vs bzr问题之外,我能够确认你需要像

一样逃避这些问题。
 cmd = emacs --eval "\\(ediff-merge-files-with-ancestor \"$LOCAL\" \"$REMOTE\" \"$BASE\" nil \"$MERGED\"\\)"

请注意双反斜杠字符。我有点明白,他们需要(而不是单一的)来完成sh / bash引用和emacs启动引用机制。我会留给那些更好地掌握Emacs和shell引用来解释血腥细节的人。

-pmr

答案 4 :(得分:4)

Viper3369代码中的elisp代码(Using ediff as git mergetool)使用了一个不存在的“display-useful-bounds”函数。由于钩子比完全必要的更多,所以简单地删除对“display-useful-bounds”的所有引用就足以使它对我有用。干得好! ;)

(编辑:我想我应该发布修改后的emacs-lisp代码:

;;
;; 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)

答案 5 :(得分:4)

谢谢,它也适用于xemacs,但是the reply by pmr中的引用似乎不起作用,而我认为所有其他回复中的引用都很好:

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

我将上面的代码放在~/.gitconfig

答案 6 :(得分:3)

这是tarsius设置的变体。它处理祖先文件$ BASE不存在时,它允许您中止合并而不会破坏git关于冲突的状态(通过不在退出时自动保存)。它还有换行的换行符,以便您可以保留格式。

[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\\\")))\"

答案 7 :(得分:2)

要使用Subversion的交互式合并工具而不是git,请参阅this帖子以获取一些说明进行设置。

答案 8 :(得分:2)

有一种方法可以将ediff-merge-files-with-ancestor函数与emacsclient一起使用。

最简单的(对于GNU / Linux用户)是在emacsclient调用之后从管道执行shell读取。在追加中添加到ediff-quit-hook的钩子(必须之后才能运行 ediff-cleanup-mess否则ediff会话没有正确终止)将通过shell命令在管道中射出一个字符。

更精致的将使用信号量。

这里有Unix高级用户。

然后到达Emacs Guru(Stefan Monnier)并告诉你可以打电话

emacsclient --eval'(progn(ediff-merge-files-wit .......)(递归编辑))'

添加

(抛出'退出)

在ediff-quit-hook结束时的某个地方。没有命名管道,没有信号量,只有Emacs LISP。简单,优雅,不需要奇怪的测试,以避免在不使用时使用管道或信号量。

谢谢Stefan!

答案 9 :(得分:1)

这对我来说是一个有价值的发现。我有一个小的补充,因为我使用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\\\")\"

并添加了“(当”下面的条款,因为我更喜欢多帧ediff:

;;
;; 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) ...
)

答案 10 :(得分:1)

这是一个很好的讨论,使用mercurial这样做。它看起来好像有一个包装脚本,可以缓解emacsclient问题:https://www.mercurial-scm.org/wiki/MergingWithEmacs

答案 11 :(得分:1)

结合我最喜欢的想法。此配置使用 emacsclient因此要求emacs已经运行。

这也适用于git difftool - 它将调用ediff文件。 (什么时候 git difftool调用然后祖先将等于合并。)

在.gitconfig中:

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

在〜/ bin / ec-merge中(确保〜/ bin在你的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"

在.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))

通常情况下,如果emacs已经访问了任何相关文件(本地, 远程,基地或合并)ediff将要求它保存并杀死 缓冲。如果你喜欢我总是想对此回答是,那就添加 这也是你的.emacs:

(setq jcl-save-and-kill-buffers-before-merge t)