简体   繁体   English

使用ediff作为git mergetool

[英]Using ediff as git mergetool

I would like to be able to use ediff with "git mergetool". 我希望能够使用ediff与“git mergetool”。

I found some patches that alter the source code, which I don't want to do. 我找到了一些改变源代码的补丁,我不想这样做。 Instead, I'd like to add ediff support with my .gitconfig. 相反,我想在我的.gitconfig中添加ediff支持。

I know git has builtin support for emerge, but I prefer ediff. 我知道git已经内置了对emerge的支持,但我更喜欢ediff。

I attempted to add these lines to my .gitconfig: 我试图将这些行添加到我的.gitconfig:

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

But when I try to run this with "git mergetool --tool=ediff", I get this: 但是当我尝试使用“git mergetool --tool = ediff”运行时,我得到了这个:

eval: 1: Syntax error: "(" unexpected

What am I doing wrong? 我究竟做错了什么?

I use aa 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). 据我记得,我从这个帖子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\\\"))\"

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. 请注意,我已将其拆分为多行以提高可读性并使用\\转义换行符,因此git config将其视为单行。

I usually use emacsclient to edit eg commit messages. 我通常使用emacsclient来编辑例如提交消息。 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. 遗憾的是,上面的mergetool配置不使用emacsclient,当我试图让它与emacsclient一起工作时,我遇到了各种问题,包括emacsclient立即返回的事实。

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 ;-) 但是,如果其他人已经找到了一个当然很棒的解决方案;-)

I use the following script as mergetool which works quite well. 我使用以下脚本作为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

To use it with `git mergetool' put the following in your git config: 要将它与`git mergetool'一起使用,请在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 (eg when merging two branches where the same path/file has been created independently). 脚本本身启动emacs客户端(或emacs后跟emacs客户端, -a "" )并且如果没有基本版本(例如合并两个分支时),则使用ediff-merge-files-with-ancestorediff-merge-files其中相同的路径/文件已独立创建)。

After the emacs client has finished the merged file is checked for conflict markers. emacs客户端完成后,检查合并文件是否有冲突标记。 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. 如果找到这些文件,您的工作将被保存到临时文件中,脚本将以代码1退出,git将恢复合并文件的预合并工具内容。

When there are no conflict markers present, the script exits with code 0 and git will regard the merge as successful. 当没有冲突标记存在时,脚本以代码0退出,git将合并视为成功。

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. 重要:如果使用--no-wait选项启动emacsclient ,则将mergetool选项trustExitCode设置为true以及冲突标记的编辑后检查将不起作用。

Here's my setup, which works fairly well, using Emacs 23.3 at least. 这是我的设置,它运行得相当好,至少使用Emacs 23.3。 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). 我使用的技巧是在钩子中使用(递归编辑),这样emacsclient就不会退出,直到建议的ediff-quit钩子调用(exit-recursive-edit)。

I used an advisted ediff-quit to ensure the exit-recursive-edit is the very last thing done. 我使用了一个建议的ediff-quit来确保退出 - 递归 - 编辑是最后完成的事情。

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. 我没有解决中止ediff并使emacsclient返回非零退出的问题。

Put in your gitconfig: 放入你的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: 放入你的.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)

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 除了我在上面的评论中发现的git vs bzr问题之外,我能够确认你需要逃避这些问题。

 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. 我有点明白,他们需要(而不是单一的)来完成sh / bash引用和emacs启动引用机制。 I'll leave it to someone with a better grasp of Emacs and shell quoting to explain the gory details. 我会留给那些更好地掌握Emacs和shell引用来解释血腥细节的人。

-pmr -pmr

The elisp code in Viper3369's code ( Using ediff as git mergetool ) uses a function "display-usable-bounds" which doesn't exist. Viper3369代码中的elisp代码( 使用ediff作为git mergetool )使用了一个不存在的函数“display-usable-bounds”。 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. 由于钩子比完全必要的更多,所以简单地删除对“display-useful-bounds”的所有引用就足以使它对我有用。 Good work! 干得好! ;) ;)

(Edit: I think I should post the modified emacs-lisp code: (编辑:我想我应该发布修改后的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)

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: 谢谢,它也适用于xemacs,但是pmr 的回复中的引用似乎不起作用,而我认为所有其他回复中的引用都很好:

[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 . 我把上面的代码放在~/.gitconfig

Here's a variant of tarsius's setup. 这是tarsius设置的变体。 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). 它处理祖先文件$ BASE不存在时,它允许您中止合并而不会破坏git关于冲突的状态(通过不在退出时自动保存)。 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\\\")))\"

There is a way to use the ediff-merge-files-with-ancestor function with emacsclient. 有一种方法可以将ediff-merge-files-with-ancestor函数与emacsclient一起使用。

The simplest one (for the GNU/Linux user) is to do a shell read from a pipe after the emacsclient call. 最简单的一个(对于GNU / Linux用户)是在emacsclient调用之后从管道执行shell读取。 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. 附加到ediff-quit-hook的钩子(它必须在ediff-cleanup-mess之后运行,否则ediff会话没有正确终止)将通过shell命令在管道中射出一个字符。

A more refined one will use a semaphore. 更精致的将使用信号量。

And here arrives the Unix power user. 这里到了Unix高级用户。

Then arrives the Emacs Guru (Stefan Monnier) and tells you that you can call 然后到达Emacs Guru(Stefan Monnier)并告诉你可以打电话

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

after adding 添加后

(throw 'exit ) (抛出'出口)

somewhere at the end of ediff-quit-hook. 在ediff-quit-hook结束的某个地方。 No named pipe, no semaphores, just Emacs LISP. 没有命名管道,没有信号量,只有Emacs LISP。 Simple, elegant and does not require weird tests to avoid using pipes or semaphores when they are not used. 简单,优雅,不需要奇怪的测试,以避免在不使用时使用管道或信号量。

Thank you Stefan! 谢谢Stefan!

对于使用Subversion的交互式合并工具而不是git,请参阅帖子以获取设置此内容的一些说明。

Combining my favorite ideas from above. 结合我最喜欢的想法。 This configuration uses emacsclient and require therefore that an emacs is already running. 此配置使用emacsclient,因此需要已运行emacs。

This also works for git difftool - it will invoke ediff-files. 这也适用于git difftool - 它将调用ediff文件。 (When git difftool calls then the ancestor will be equal to the merged.) (当git difftool调用时,祖先将等于合并。)

In .gitconfig: 在.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 / 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"

In .emacs: 在.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. 通常,如果emacs已经访问了任何相关文件(本地,远程,基本或合并),ediff将要求它保存并终止缓冲区。 If you like me always want to answer yes to this then add also this to your .emacs: 如果你喜欢我总是想对此回答是,那么也将此添加到你的.emacs:

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

This was a valuable find for me. 这对我来说是一个有价值的发现。 I have a small addition, since I use emacs desktop-save-mode: 我有一个小的补充,因为我使用emacs桌面保存模式:

[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: 并添加了“(当”下面的条款,因为我更喜欢多帧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) ...
)

This is a nice discussion about doing this usuing mercurial. 这是一个很好的讨论关于做这个使用mercurial。 It looks as though they have a wrapper script which alleviates the emacsclient issue: https://www.mercurial-scm.org/wiki/MergingWithEmacs 它看起来好像有一个包装脚本,可以缓解emacsclient问题: https ://www.mercurial-scm.org/wiki/MergingWithEmacs

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM