Использование ediff в качестве git mergetool - PullRequest
49 голосов
/ 30 ноября 2009

Я хотел бы иметь возможность использовать 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

Что я делаю не так?

Ответы [ 12 ]

29 голосов
/ 30 ноября 2009

Я использую более сложную команду. Насколько я помню, я получил это из этой темы 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 вернулся сразу.

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

13 голосов
/ 23 ноября 2011

Я использую следующий скрипт в качестве 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 "") и пропускает либо ediff-merge-files-with-ancestor, либо ediff-merge-files, если нет базовой версии (например, при объединении двух ветвей, в которых один путь / файл был создан независимо).

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

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

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

8 голосов
/ 22 декабря 2010

Вот моя установка, которая работает довольно хорошо, по крайней мере, с использованием Emacs 23.3. Уловка, которую я использовал, использовала (recursive-edit) в ловушке так, чтобы emacsclient не выходил до тех пор, пока рекомендованный обработчик ediff-quit не вызывает (exit-recursive-edit).

Я использовал рекомендованное ediff-quit, чтобы гарантировать, что exit-recursive-edit - это самое последнее, что было сделано.

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

Я не решил проблему прерывания 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)
6 голосов
/ 30 ноября 2009

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

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

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

-pmr

4 голосов
/ 02 марта 2011

Спасибо, это также работает в 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 голосов
/ 06 января 2011

Код elisp в коде Viper3369 ( Использование ediff в качестве git mergetool ) использует функцию «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)
3 голосов
/ 26 октября 2010

Вот вариант настройки Тарсиуса. Он обрабатывает, когда файл предка $ 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\\\")))\"
2 голосов
/ 27 октября 2014

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

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

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

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

Затем прибывает Гуру Emacs (Стефан Монье) и говорит, что вы можете позвонить

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

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

(бросить 'выход)

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

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

2 голосов
/ 29 апреля 2011

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

1 голос
/ 01 марта 2015

Сочетание моих любимых идей сверху. Эта конфигурация использует 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 уже посещает какой-либо из соответствующих файлов (локальный, удаленный, базовый или объединенный) Эдифф попросит его сохранить и убить буфер. Если я тебе нравлюсь, всегда хочу ответить «да», добавь и это к вашему .emacs:

(setq jcl-save-and-kill-buffers-before-merge t)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...