使用 ediff 作为 git mergetool
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1817370/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using ediff as git mergetool
提问by alternative
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?
我究竟做错了什么?
采纳答案by tarsius
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).
我使用了一个更复杂的命令。据我所知,我是从这个线程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 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.
我通常使用 emacsclient 来编辑例如提交消息。不幸的是,上面的 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 ;-)
但你刚刚让我想起了那个问题,所以我可能会尽快解决这个问题。但是,如果其他人已经找到了一个很好的解决方案,当然;-)
回答by u-punkt
I use the following script as mergetool which works quite well.
我使用以下脚本作为合并工具,效果很好。
#!/bin/bash
# test args
if [ ! ${#} -ge 3 ]; then
echo 1>&2 "Usage: [merge]
tool = ediff
[mergetool "ediff"]
cmd = /path/to/ediff-merge-script $LOCAL $REMOTE $MERGED $BASE
trustExitCode = true
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=
_REMOTE=
_MERGED=
if [ -r ] ; then
_BASE=
_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 配置中:
[mergetool "ediff"]
cmd = emacsclient --eval \"(git-mergetool-emacsclient-ediff \\"$LOCAL\\" \\"$REMOTE\\" \\"$BASE\\" \\"$MERGED\\")\"
trustExitCode = true
[mergetool]
prompt = false
[merge]
tool = ediff
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).
脚本本身开始Emacs的客户端(或Emacs后跟一个emacs的客户端,-a ""
)和evals要么ediff-merge-files-with-ancestor
或者ediff-merge-files
如果没有基础版本(如合并在同一路径/文件已经被独立创造的两个分支时)。
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.
在 emacs 客户端完成后,将检查合并文件是否存在冲突标记。如果找到这些,您的工作将被保存到一个临时文件中,脚本将以代码 1 退出,git 将恢复合并文件的 pre-mergetool 内容。
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.
重要提示:设置合并工具选项trustExitCode
来true
以及后编辑检查冲突都将无法工作,如果你开始emacsclient
使用的--no-wait
选项。
回答by harveyt
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).
这是我的设置,至少使用 Emacs 23.3,效果很好。我使用的技巧是在钩子中使用 (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:
;;
;; 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)
Put in your .emacs or equivalent:
放入您的 .emacs 或等效文件:
cmd = emacs --eval "\(ediff-merge-files-with-ancestor \"$LOCAL\" \"$REMOTE\" \"$BASE\" nil \"$MERGED\"\)"
回答by pajato0
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 问题之外,我还能够确认您需要像在
;;
;; 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)
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.
注意双反斜杠字符。我有点理解它们需要(而不是一个)来通过 sh/bash 引用和 emacs 启动引用机制。我会把它留给更了解 Emacs 和 shell 引用的人来解释血腥的细节。
-pmr
-pmr
回答by TauPan
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! ;)
Viper3369 代码中的 elisp 代码(Using ediff as git mergetool)使用了一个不存在的函数“display-usable-bounds”。由于钩子的作用远远超出了绝对必要的范围,因此只需删除对“display-usable-bounds”的所有引用就足以使其对我有用。干得好!;)
(Edit: I think I should post the modified emacs-lisp code:
(编辑:我想我应该发布修改后的 emacs-lisp 代码:
[mergetool "ediff"]
cmd = xemacs -eval \"(ediff-merge-files-with-ancestor \\"$PWD/$LOCAL\\" \\"$PWD/$REMOTE\\" \\"$PWD/$BASE\\" nil \\"$PWD/$MERGED\\")\"
[merge]
tool = ediff
回答by Nei
Thanks, it also works in xemacs, however the quoting as in the reply by pmrdoesn't seem to work whereas I think the quoting in all the other replies is fine:
谢谢,它也适用于 xemacs,但是 pmr回复中的引用似乎不起作用,而我认为所有其他回复中的引用都很好:
[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\\")))\"
I put this above code in ~/.gitconfig
.
我把上面的代码放在~/.gitconfig
.
回答by tresi
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.
这是 tarsius 设置的一个变体。它在祖先文件 $BASE 不存在时进行处理,并且它允许您中止合并而不破坏 git 关于冲突的状态(通过不在退出时自动保存)。它还具有反斜杠换行符,以便您可以保留格式。
[mergetool "ec-merge"]
prompt = false
cmd = ec-merge "$LOCAL" "$REMOTE" "$BASE" "$MERGED"
trustExitCode = true
[merge]
tool = ec-merge
[difftool]
prompt = false
回答by saint
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. An hook added in appendto ediff-quit-hook (it mustbe run after ediff-cleanup-mess otherwise ediff session is not terminated properly) will shot a character in the pipe through shell-command.
最简单的方法(对于 GNU/Linux 用户)是在 emacsclient 调用之后从管道中读取 shell。在添加了一个钩子附加到ediff-退出挂机(它必须ediff-清理-乱七八糟否则ediff会话没有正常结束之后运行)将通过壳命令拍摄的字符在管。
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. 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.
在 ediff-quit-hook 末尾的某个地方。没有命名管道,没有信号量,只有 Emacs LISP。简单、优雅并且不需要奇怪的测试来避免在不使用时使用管道或信号量。
Thank you Stefan!
谢谢斯蒂芬!
回答by zbeekman
回答by Johan Claesson
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. (When git difftool calls then the ancestor will be equal to the merged.)
这也适用于 git difftool - 它会调用 ediff 文件。(当 git difftool 调用时,祖先将等于合并的。)
In .gitconfig:
在 .gitconfig 中:
#!/bin/bash
set -e
LOCAL=$(readlink -f "")
REMOTE=$(readlink -f "")
BASE=$(readlink -f "")
MERGED=$(readlink -f "")
emacsclient --eval "(jcl-git-merge \"$LOCAL\" \"$REMOTE\" \"$BASE\" \"$MERGED\")"
! egrep -q '^(<<<<<<<|=======|>>>>>>>|####### Ancestor)' "$MERGED"
In ~/bin/ec-merge (make sure ~/bin is in your PATH):
在 ~/bin/ec-merge 中(确保 ~/bin 在您的 PATH 中):
(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))
In .emacs:
在 .emacs 中:
(setq jcl-save-and-kill-buffers-before-merge 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:
通常,如果 emacs 已经访问了任何相关文件(本地、远程、基础或合并),则 ediff 会要求它保存并终止缓冲区。如果你喜欢我总是想对此回答是,然后将它添加到你的 .emacs 中:
##代码##