it-swarm.com.ru

Использование 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

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

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 рассматривает его как одну строку.

Я обычно использую emacsclient для редактирования, например. совершать сообщения. Приведенная выше конфигурация mergetool, к сожалению, не использует emacsclient, и когда я попытался заставить его работать с emacsclient, я столкнулся с различными проблемами, включая тот факт, что emacsclient вернулся сразу же.

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

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

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

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

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

13
u-punkt

Вот моя установка, которая работает довольно хорошо, по крайней мере, с использованием 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)
8
harveyt

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

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

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

-pmr

6
pajato0

Код 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)
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

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

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

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

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

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

Затем прибывает Emacs Guru (Stefan Monnier) и говорит вам, что вы можете позвонить

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

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

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

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

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

2
saint

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

2
zbeekman

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

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

1
zbeekman

Это была ценная находка для меня. У меня есть небольшое дополнение, так как я использую 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's code here) ...
)
1
Geoff