web-dev-qa-db-ja.com

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

何が悪いのですか?

49
alternative

より複雑なコマンドを使用します。私が覚えている限り、私はこのスレッドからそれを得ました http://kerneltrap.org/mailarchive/git/2007/6/28/2502 (おそらくあなたが参照しているものと同じです)。

[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はそれを1行と見なしていることに注意してください。

私は通常、編集にemacsclientを使用します。メッセージをコミットします。上記のmergetool構成は残念ながらemacsclientを使用していません。emacsclientで動作させようとすると、emacsclientがすぐに戻ってしまうなど、さまざまな問題が発生しました。

しかし、あなたはその問題を思い出させてくれただけなので、私はすぐにその問題の修正に取り組むかもしれません。ただし、他の誰かがすでにすばらしい解決策をすでに見つけている場合は、;-)

30
tarsius

次のスクリプトを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構成に次のように入力します。

[merge]
        tool = ediff

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

さらに、使用するツールのパスを確認し(スクリプトで)、貧しい人のコンソールの検出が機能するかどうかを確認する必要があります。

スクリプト自体がemacsクライアント(またはemacsに続いてemacsクライアント-a "")とevals ediff-merge-files-with-ancestorまたはediff-merge-filesベースバージョンがない場合(たとえば、同じパス/ファイルが個別に作成されている2つのブランチをマージする場合)。

Emacsクライアントが終了すると、マージされたファイルの競合マーカーがチェックされます。それらが見つかった場合、作業は一時ファイルに保存され、スクリプトはコード1で終了し、gitはマージされたファイルのマージ前ツールの内容を復元します。

競合マーカーが存在しない場合、スクリプトはコード0で終了し、gitはマージが成功したと見なします。

重要: mergetoolオプションtrustExitCodetrueに設定すると、競合マーカーの編集後チェックは、emacsclient--no-waitオプション。

13
u-punkt

これは、少なくともEmacs 23.3を使用してかなりうまく機能する私のセットアップです。私が使用したトリックは、emacsclientがアドバイスされたediff-quitフックコール(exit-recursive-edit)を呼び出すまで終了しないように、フックで(recursive-edit)を使用することでした。

私は、推奨されるediff-quitを使用して、exit-recursive-editが最後に行われるようにしました。

現在のフレームとウィンドウの状態を保存して後で復元するフックもあります。フックは現在のフレームを画面いっぱいにします。あなたはそれを変更したいかもしれませんが、私は全画面をマージすることが最善の方法だと思います。

私はエディフを中止して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)
8
harveyt

上記のコメントで特定したgit vs bzrの問題は別として、次のように括弧をエスケープする必要があることを確認できました

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

二重のバックスラッシュ文字に注意してください。 sh/bashのクォートとemacsのスタートアップのクォートのメカニズムの両方を通過するためには(1つではなく)それらが必要であることを理解しています。残酷な詳細を説明するために引用するEmacsとShellをよく理解している人に任せます。

-pmr

6
pajato0

Viper3369のコードのelispコード( Git mergetoolとしてediffを使用 )は、存在しない関数「display-usable-bounds」を使用しています。フックは必要以上に多くのことを行うので、「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)
4
TauPan

おかげで、それは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

4
Nei

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\\\")))\"
3
tresi

Emacsclientでediff-merge-files-with-ancestor関数を使用する方法があります。

最も簡単な方法(GNU/Linuxユーザーの場合)は、emacsclient呼び出しの後にパイプからシェル読み取りを行うことです。 ediff-quit-hookにappendで追加されたフック(それはmust ediff-cleanup-messの後に実行されると、ediffセッションが適切に終了しない)は、シェルコマンドを介してパイプします。

より洗練されたものはセマフォを使用します。

そして、ここにUnixパワーユーザーがやってきた。

その後、Emacs達人(Stefan Monnier)に到着し、

emacsclient --eval '(progn(ediff-merge-files-wit .......)(再帰編集))'

追加後

(「出口」を投げる)

どこかでediff-quit-hookの終わりに。名前付きパイプもセマフォもありません。EmacsLISPだけです。シンプルでエレガントであり、パイプやセマフォが使用されていないときにそれらを使用しないようにするための奇妙なテストは必要ありません。

ステファンありがとう!

2
saint

Gitの代わりにSubversionのインタラクティブなマージツールを使用する場合、これを設定するためのいくつかの手順について this postを参照してください。

2
zbeekman

上から私のお気に入りのアイデアを組み合わせる。この構成はemacsclientを使用するため、emacsがすでに実行されている必要があります。

これはgit difftoolでも機能します-ediff-filesを呼び出します。 (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)
1
Johan Claesson

これはMercurialを使用してこれを行うことについての素晴らしい議論です。 emacsclientの問題を緩和するラッパースクリプトがあるように見えます: https://www.Mercurial-scm.org/wiki/MergingWithEmacs

1
zbeekman

これは私にとって貴重な発見でした。 emacsデスクトップ保存モードを使用しているため、少し追加しました。

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

通常はマルチフレームediffを好むため、以下の「(when)句を追加しました。

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