使用ediff作为git mergetool

我希望能够用“git mergetool”来使用ediff。

我发现一些修改源代码的补丁,我不想这样做。 相反,我想用我的.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 

我究竟做错了什么?

我使用了一个更复杂的命令。 据我记得我从这个线程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编辑例如提交消息。 上面的mergetoolconfiguration不幸的是不使用emacsclient,当我试图让它与emacsclient一起工作时,我遇到了各种问题,包括emacsclient立即返回的事实。

但是你提醒我这个问题,所以我可能会尽快解决这个问题。 但是,如果别人已经find了一个当然会很好的解决scheme;-)

我使用以下脚本作为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 

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

脚本本身启动一个emacs客户端(或emacs后跟一个emacs客户端, -a "" ),并且如果没有基础版本(例如当合并两个分支时合并ediff-merge-files-with-ancestorediff-merge-files相同的path/文件已经独立创build)。

在emacs客户端完成之后,合并的文件被检查冲突标记。 如果这些被发现,你的工作将被保存到一个临时文件,脚本将退出代码1,git将恢复合并文件的pre-mergetool内容。

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

重要提示:如果使用--no-wait选项启动emacsclient ,则将mergetool选项trustExitCode设置为true以及对冲突标记进行编辑后检查将不起作用。

这是我的设置,工作得很好,至less使用Emacs 23.3。 我使用的技巧是在一个钩子中使用(recursion编辑),这样emacsclient不会退出,直到一个build议的ediff-quit钩子调用(exit-recursive-edit)。

我使用了一个build议ediff-quit来确保退出recursion编辑是最后一件事情。

还有钩子来保存当前的帧和窗口状态并在之后恢复,钩子使当前帧填充屏幕。 你不妨修改一下,但是我发现合并全屏是最好的方法。

我还没有解决中止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) xy))) (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) 

除了我在上面的评论中发现的git vs bzr问题,我能够确认你需要逃避parens

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

请注意双反斜杠字符。 我理解,他们需要(而不是单一的)通过sh / bash引用和emacs启动引用机制。 我会把它留给一个更好地掌握Emacs和shell引用来解释血淋淋的细节的人。

-pmr

谢谢,它也可以在xemacs中使用,但是pmr 的回复中的引用似乎不起作用,而我认为所有其他回复中的引用都是正确的:

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

我把这个代码放在~/.gitconfig

这是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\\\")))\" 

Viper3369代码中的elisp代码( 使用ediff作为git mergetool )使用不存在的函数“display-usable-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) 

对于使用Subversion的交互式合并工具,而不是git看到这个职位的一些说明来设置。

有一种方法可以在emacsclient中使用ediff-merge-files-with-ancestor函数。

最简单的一个(对于GNU / Linux用户)是在emacsclient调用之后从pipe道读取一个shell。 在ediff-quit-hook后面添加一个钩子(它必须在ediff-cleanup-mess之后运行,否则ediff会话没有正确终止)将通过shell-command在pipe道中拍摄一个字符。

更精致的将使用信号量。

而且这里到了Unix的超级用户。

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

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

加完后

(扔出口)

在ediff-quit-hook结尾的某处。 没有命名pipe道,没有信号量,只是Emacs LISP。 简单,优雅,不需要奇怪的testing,以避免不使用pipe道或信号量时,不使用。

谢谢Stefan!

这对我来说是一个很有价值的发现。 我有一个小的补充,因为我使用emacs桌面保存模式:

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

并添加了下面的“(when”子句,因为我通常更喜欢多帧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) ... ) 

这是一个很好的讨论关于如何使用这个mercurial。 它看起来好像他们有一个包装脚本,减轻了emacsclient问题: https ://www.mercurial-scm.org/wiki/MergingWithEmacs

从上面结合我最喜欢的想法。 这个configuration使用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)) tt)) 

通常情况下,如果emacs已经访问了任何有关的文件(本地,远程,基地或合并),ediff会要求它保存并杀死缓冲区。 如果你喜欢我总是想回答这个,然后join这个你的.emacs:

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