Использование ediff как git mergetool

Я хотел бы иметь возможность использовать ediff с "git mergetool".

Я нашел некоторые исправления, которые изменяют исходный код, который я не хочу делать. Вместо этого я хотел бы добавить поддержку ediff с моим .gitconfig.

Я знаю, что 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

Что я делаю неправильно?

Ответ 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 для редактирования, например. фиксировать сообщения. Вышеупомянутая конфигурация mergetool, к сожалению, не использует emacsclient, и когда я пытался заставить ее работать с emacsclient, я столкнулся с различными проблемами, включая тот факт, что emacsclient вернул сразу.

Но вы просто напомнили мне об этой проблеме, поэтому я мог бы быстро решить эту проблему. Однако если кто-то уже нашел решение, которое было бы здорово, -)

Ответ 2

Я использую следующий script как 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

Кроме того, вы должны проверить (в script) пути используемых инструментов и если для вас работает обнаружение консоли бедных людей.

Сам script запускает клиент emacs (или emacs, за которым следует клиент emacs, -a "") и оценивает либо ediff-merge-files-with-ancestor, либо ediff-merge-files, если нет базовой версии (например, при объединении двух ветвей, где один и тот же путь/файл был создан независимо).

После того, как клиент emacs закончил, объединенный файл проверяется на наличие маркеров конфликта. Если они будут найдены, ваша работа будет сохранена во временном файле, script выйдет с кодом 1 и git восстановит содержимое pre-mergetool объединенного файла.

Если маркеров конфликта не существует, script завершает с кодом 0 и git будет считать слияние успешным.

Важно: Установка параметра mergetool trustExitCode в true, а также проверка после редактирования для маркеров конфликта не будет работать, если вы запустите emacsclient с опцией --no-wait.

Ответ 3

Здесь моя настройка, которая работает достаточно хорошо, по крайней мере, используя Emacs 23.3. Трюк, который я использовал, заключался в использовании (рекурсивного редактирования) в крючке, так что 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)

Ответ 4

Помимо проблемы с git vs bzr, которую я указал в своем комментарии выше, я смог подтвердить, что вам нужно сбежать от parens, как в

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

Обратите внимание на двойные символы обратной косой черты. Я понимаю, что они необходимы (а не один), чтобы пройти как котировки sh/ bash, так и механизмы запуска автозапуска emacs. Я оставлю его кому-то с лучшим пониманием Emacs и цитирования оболочки, чтобы объяснить детали gory.

-pmr

Ответ 5

Спасибо, он также работает в 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.

Ответ 6

Здесь вариант настройки 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\\\")))\"

Ответ 7

Код elisp в коде Viper3369 (Использование ediff как git mergetool) использует функцию "display-usable-bounds", которая не существует. Так как крючки делают намного больше, чем это строго необходимо, просто удалить все ссылки на "отображаемые в использовании ограничения" достаточно, чтобы заставить его работать для меня. Хорошая работа!;)

(Edit: Мне кажется, я должен опубликовать модифицированный код 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)

Ответ 8

Для использования инструмента интерактивного слияния Subversion вместо git см. этот пост для некоторых инструкций по его настройке.

Ответ 9

Существует способ использования функции ediff-merge-files-with-ancestor с emacsclient.

Самый простой (для пользователя GNU/Linux) - это сделать оболочку, считанную из канала после вызова emacsclient. Крюк, добавленный в append к обходу ediff-quit-hook (он должен запускаться после ediff-cleanup-mess, иначе uniff session не заканчивается должным образом) застревает символ в канале через команду shell.

Более утонченный будет использовать семафор.

И вот прибывает пользователь Power Unix.

Затем приходит Гуру Emacs (Stefan Monnier) и говорит вам, что вы можете позвонить

emacsclient --eval '(progn (ediff-merge-files-wit.......) (рекурсивное редактирование))'

после добавления

(throw 'exit)

где-то в конце ediff-quit-hook. Нет именованной трубы, без семафоров, только Emacs LISP. Простой, элегантный и не требует странных тестов, чтобы избежать использования труб или семафоров, когда они не используются.

Спасибо, Стефан!

Ответ 10

Это была ценная находка для меня. У меня есть небольшое дополнение, так как я использую emacs desktop-save-mode:

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

и добавил раздел "(когда" ниже, потому что я предпочитаю многокадровый 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 code here) ...
)

Ответ 11

Это хорошая дискуссия о том, как это сделать, используя ртуть. Похоже, что у них есть обертка script, которая устраняет проблему emacsclient: https://www.mercurial-scm.org/wiki/MergingWithEmacs

Ответ 12

Сочетание моих любимых идей сверху. Эта конфигурация использует 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)