web-dev-qa-db-fra.com

Utiliser ediff comme git mergetool

Je voudrais pouvoir utiliser ediff avec "git mergetool".

J'ai trouvé des correctifs qui altèrent le code source, ce que je ne veux pas faire. Au lieu de cela, j'aimerais ajouter le support ediff avec mon .gitconfig.

Je sais que git a un support intégré pour emerge, mais je préfère ediff.

J'ai essayé d'ajouter ces lignes à mon .gitconfig:

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

Mais quand j'essaye d'exécuter ceci avec "git mergetool --tool = ediff", j'obtiens ceci:

eval: 1: Syntax error: "(" unexpected

Qu'est-ce que je fais mal?

49
alternative

J'utilise une commande plus compliquée. Pour autant que je m'en souvienne, je l'ai obtenu de ce fil http://kerneltrap.org/mailarchive/git/2007/6/28/2502 (probablement le même que celui auquel vous faites référence).

[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\\\"))\"

Notez que j'ai divisé cela sur plusieurs lignes pour augmenter la lisibilité et échappé à la nouvelle ligne avec \ donc git config le considère comme une seule ligne.

J'utilise habituellement emacsclient pour éditer par exemple valider les messages. La configuration de mergetool ci-dessus n'utilise malheureusement pas emacsclient, et lorsque j'ai essayé de le faire fonctionner avec emacsclient, j'ai rencontré divers problèmes, notamment le fait que emacsclient est retourné immédiatement.

Mais vous venez de me rappeler ce problème, donc je pourrais peut-être travailler à résoudre ce problème bientôt. Cependant, si quelqu'un d'autre a déjà trouvé une solution, ce serait bien sûr ;-)

30
tarsius

J'utilise le script suivant comme mergetool qui fonctionne assez bien.

#!/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

Pour l'utiliser avec `git mergetool ', mettez ce qui suit dans votre configuration git:

[merge]
        tool = ediff

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

De plus, vous devriez vérifier (dans le script) les chemins des outils utilisés et si la détection de la console du pauvre fonctionne pour vous.

Le script lui-même démarre un client emacs (ou emacs suivi d'un client emacs, -a "") et evals soit ediff-merge-files-with-ancestor ou ediff-merge-files s'il n'y a pas de version de base (par exemple lors de la fusion de deux branches où le même chemin/fichier a été créé indépendamment).

Une fois le client emacs terminé, le fichier fusionné est vérifié pour les marqueurs de conflit. Si ceux-ci sont trouvés, votre travail sera enregistré dans un fichier temporaire, le script se terminera avec le code 1 et git restaurera le contenu pré-fusionné du fichier fusionné.

Lorsqu'il n'y a aucun marqueur de conflit présent, le script se termine avec le code 0 et git considérera la fusion comme réussie.

Important: La définition de l'option mergetool trustExitCode sur true ainsi que la vérification post-modification des marqueurs de conflit ne fonctionneront pas si vous démarrez emacsclient avec les --no-wait option.

13
u-punkt

Voici ma configuration, qui fonctionne assez bien, en utilisant au moins Emacs 23.3. L'astuce que j'ai utilisée consistait à utiliser (recursive-edit) dans un hook de telle sorte qu'emacsclient ne se ferme pas avant qu'un hook ediff-quit conseillé n'appelle (exit-recursive-edit).

J'ai utilisé un ediff-quit conseillé pour m'assurer que la sortie-recursive-edit est la toute dernière chose faite.

Il existe également des crochets pour enregistrer l'état actuel du cadre et de la fenêtre et le restaurer ensuite, et le crochet fait que le cadre actuel remplit l'écran. Vous voudrez peut-être modifier cela, mais je trouve que la fusion en plein écran est la meilleure façon.

Je n'ai pas résolu le problème de l'abandon de l'ediff et de faire en sorte qu'emacsclient retourne une sortie non nulle.

Mettez votre gitconfig:

[mergetool "ediff"]
       cmd = emacsclient --eval \"(git-mergetool-emacsclient-ediff \\\"$LOCAL\\\" \\\"$REMOTE\\\" \\\"$BASE\\\" \\\"$MERGED\\\")\"
       trustExitCode = true
[mergetool]
    Prompt = false
[merge]
    tool = ediff

Mettez votre .emacs ou équivalent:

;;
;; 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

Mis à part le problème git vs bzr que j'ai identifié dans mon commentaire ci-dessus, j'ai pu confirmer que vous devez échapper aux parens comme dans

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

Notez les caractères de double barre oblique inverse. Je comprends en quelque sorte qu'ils sont nécessaires (plutôt qu'un seul) pour passer à la fois par les mécanismes de citation sh/bash ET les mécanismes de citation de démarrage emacs. Je laisse le soin à quelqu'un ayant une meilleure compréhension d'Emacs et de Shell de citer les détails sanglants.

-pmr

6
pajato0

Le code elisp dans le code de Viper3369 ( tiliser ediff comme git mergetool ) utilise une fonction "display-usable-bounds" qui n'existe pas. Étant donné que les crochets font beaucoup plus que ce qui est strictement nécessaire, il suffit de supprimer toutes les références aux "limites d'affichage utilisables" pour que cela fonctionne pour moi. Bon travail! ;)

(Edit: je pense que je devrais poster le code emacs-LISP modifié:

;;
;; 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

Merci, cela fonctionne également dans xemacs, mais la citation comme dans la réponse de pmr ne semble pas fonctionner alors que je pense que la citation dans toutes les autres réponses est correcte:

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

J'ai mis ce code ci-dessus dans ~/.gitconfig.

4
Nei

Voici une variante de la configuration de tarsius. Il gère quand le fichier ancêtre $ BASE n'existe pas, et il vous permet d'interrompre la fusion sans mettre à la corbeille l'état de git sur le conflit (en n'enregistrant pas automatiquement à la sortie). Il a également des retours à la ligne backslashed afin que vous puissiez conserver la mise en forme.

[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

Il existe un moyen d'utiliser la fonction ediff-merge-files-with-ancestor avec emacsclient.

La plus simple (pour l'utilisateur GNU/Linux) est de faire une lecture Shell depuis un tube après l'appel emacsclient. Un hook ajouté dans append à ediff-quit-hook (il doit être exécuté après ediff-cleanup-mess sinon la session ediff ne se termine pas correctement) tirera un personnage dans le pipe via Shell-commande.

Un plus raffiné utilisera un sémaphore.

Et ici arrive l'utilisateur avancé Unix.

Arrive ensuite le gourou Emacs (Stefan Monnier) et vous dit que vous pouvez appeler

emacsclient --eval '(progn (ediff-merge-files-wit .......) (édition récursive))'

après avoir ajouté

(lancer la sortie)

quelque part à la fin de ediff-quit-hook. Pas de pipe nommée, pas de sémaphores, juste Emacs LISP. Simple, élégant et ne nécessite pas de tests étranges pour éviter d'utiliser des tuyaux ou des sémaphores lorsqu'ils ne sont pas utilisés.

Merci Stefan!

2
saint

Pour utiliser l'outil de fusion interactif de Subversion au lieu de git, voir this post pour quelques instructions pour configurer cela.

2
zbeekman

Combiner mes idées préférées d'en haut. Cette configuration utilise emacsclient et nécessite donc qu'un emacs soit déjà en cours d'exécution.

Cela fonctionne également pour git difftool - il invoquera les fichiers ediff. (Lorsque git difftool appelle, l'ancêtre sera égal à la fusion.)

Dans .gitconfig:

[mergetool "ec-merge"]
        Prompt = false
        cmd = ec-merge "$LOCAL" "$REMOTE" "$BASE" "$MERGED"
        trustExitCode = true
[merge]
        tool = ec-merge
[difftool]
        Prompt = false

Dans ~/bin/ec-merge (assurez-vous que ~/bin est dans votre 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"

Dans .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))

Normalement, si emacs visite déjà l'un des fichiers concernés (local, distant, de base ou fusionné), ediff lui demandera de sauvegarder et de tuer le tampon. Si vous m'aimez toujours vouloir répondre oui à cela, ajoutez également ceci à votre .emacs:

(setq jcl-save-and-kill-buffers-before-merge t)
1
Johan Claesson

Il s'agit d'une discussion intéressante sur la façon de procéder en utilisant Mercurial. Il semble qu'ils aient un script wrapper qui atténue le problème emacsclient: https://www.Mercurial-scm.org/wiki/MergingWithEmacs

1
zbeekman

Ce fut une découverte précieuse pour moi. J'ai un petit ajout, car j'utilise le mode de sauvegarde de bureau emacs:

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

et ajouté la clause "(quand" ci-dessous, car je préfère un ediff multi-images normalement:

;;
;; 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