【问题标题】:Using ediff as git mergetool使用 ediff 作为 git mergetool
【发布时间】:2010-12-21 12:03:15
【问题描述】:

我希望能够将 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

我做错了什么?

【问题讨论】:

  • 使用“bzr ...”看起来不对,因为您在谈论 git。乍一看我猜你的意思是“git mergetool ...”我可能是对的吗?
  • 谢谢,已更正。我刚刚从 bzr 切换到 git。旧习惯。
  • 已经快 2 年了,这个问题仍然有意见/赞成 :)
  • 如果你更喜欢使用命令行,我建议你看看whatworks4me.wordpress.com/2011/04/13/…

标签: git emacs merge


【解决方案1】:

我使用了一个更复杂的命令。据我记得我是从这个帖子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 来编辑 e.g.提交消息。不幸的是,上面的 mergetool 配置没有使用 emacsclient,当我试图让它与 emacsclient 一起工作时,我遇到了各种问题,包括 emacsclient 立即返回。

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

【讨论】:

  • 如果您想知道如何使用 emacsclient 进行这项工作,我很想知道。
  • @ShreevatsaR 我已经对这种方法失去了兴趣,因为我现在正在使用 magit,所以我担心这不会发生。你也应该试试 magit。它确实支持 ediff,并且让使用 git 变得更加有趣。
  • 好吧,我现在 维护 Magit :-) 但恐怕我不得不说它对 Ediff 的支持并不是那么好。我会在下一个版本之后继续努力。
  • 一个更好的 IMO 替代方案是将脚本放入,例如 ~/.emacs.d/mergetool.el 并用 (getenv "LOCAL") 替换 $LOCAL 等在 .gitconfig 中使用以下命令作为 mergetool 命令:/usr/bin/env LOCAL=$LOCAL REMOTE=$REMOTE BASE=$BASE MERGED=$MERGED \ emacs --load ~/.emacs.d/mergetool。 el @tarsius:真的很期待! Magit 超级棒。
  • 链接已损坏。
【解决方案2】:

我使用以下脚本作为合并工具,效果很好。

#!/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 配置中:

[merge]
        tool = ediff

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

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

脚本本身启动一个 emacs 客户端(或 emacs 后跟一个 emacs 客户端,-a ""),如果没有基本版本(例如,合并两个具有相同路径/文件的分支已独立创建)。

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

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

重要提示:如果您使用emacsclient 选项启动emacsclient,则将合并工具选项trustExitCode 设置为true 以及冲突标记的编辑后检查将不起作用。

【讨论】:

  • $LOCAL $REMOTE $MERGED $BASE -> \"$LOCAL\" \"$REMOTE\" \"$MERGED\" \"$BASE\"
  • 也可能if [ ! $(git --no-pager diff --check ${_MERGED}) = 0 ]; then 会使检查更加稳健。
【解决方案3】:

这是我的设置,至少使用 Emacs 23.3 运行良好。我使用的技巧是在钩子中使用 (recursive-edit),这样 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)

【讨论】:

  • 要使 emacsclient 返回非零退出代码,您可以在此处采用类似我的回答:superuser.com/questions/295156/… 根据我的经验,杀死与缓冲区关联的客户端是不够的;相反,我将我的循环概括为向 all 附加的客户端发送错误退出命令。 (这是一个简单的概括:只需将 server-buffer-clients 替换为 server-clients。)
  • 试用包with-editor。它设计用于将emacsemacsclient 用作$EDITOR,并允许您将C-c C-c 用于零退出代码或C-c C-k 用于非零退出代码。
  • *ERROR*: Symbol’s function definition is void: display-usable-bounds
【解决方案4】:

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

中那样转义括号
 cmd = emacs --eval "\\(ediff-merge-files-with-ancestor \"$LOCAL\" \"$REMOTE\" \"$BASE\" nil \"$MERGED\"\\)"

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

-pmr

【讨论】:

    【解决方案5】:

    Viper3369 代码 (Using ediff as git mergetool) 中的 elisp 代码使用了一个不存在的函数“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)
    

    【讨论】:

      【解决方案6】:

      谢谢,它也适用于 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.

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

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

          【讨论】:

          • 与颠覆合并?听起来像是灾难的秘诀:D
          • 我的意思不是使用 subversion 合并 git 内容,而是“这是为 subversion 设置的方法;您也许可以将此信息应用于您自己的情况。”此外,在此处搜索设置交互式 ediff 合并工具,因此其他正在使用 subversion 的人可能会发现这很有用。
          【解决方案9】:

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

          最简单的方法(对于 GNU/Linux 用户)是在 emacsclient 调用之后从管道中读取 shell。在 append 中添加的钩子到 ediff-quit-hook(它必须在之后运行 ediff-cleanup-mess 否则 ediff 会话未正确终止)将通过 shell-command 在管道中射击一个字符。

          更精细的会使用信号量。

          Unix 高级用户来了。

          然后到达 Emacs Guru (Stefan Monnier) 并告诉您可以致电

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

          添加后

          (抛出'退出)

          在 ediff-quit-hook 末尾的某个地方。没有命名管道,没有信号量,只有 Emacs LISP。简单、优雅并且不需要奇怪的测试来避免在不使用管道或信号量时使用它们。

          谢谢斯特凡!

          【讨论】:

            【解决方案10】:

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

            【讨论】:

            • 很高兴我的问题能有所帮助;感谢您的改进/更改。
            【解决方案11】:

            结合上面我最喜欢的想法。此配置使用 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)
            

            【讨论】:

              【解决方案12】:

              这是关于使用 mercurial 进行此操作的一个很好的讨论。看起来好像他们有一个包装脚本来缓解 emacsclient 问题:https://www.mercurial-scm.org/wiki/MergingWithEmacs

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2013-12-18
                • 1970-01-01
                • 2014-07-20
                • 2016-03-11
                • 1970-01-01
                • 1970-01-01
                • 2014-10-27
                相关资源
                最近更新 更多