config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

magit-ediff.el (25995B)


      1 ;;; magit-ediff.el --- Ediff extension for Magit  -*- lexical-binding:t -*-
      2 
      3 ;; Copyright (C) 2008-2024 The Magit Project Contributors
      4 
      5 ;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
      6 ;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
      7 
      8 ;; SPDX-License-Identifier: GPL-3.0-or-later
      9 
     10 ;; Magit is free software: you can redistribute it and/or modify it
     11 ;; under the terms of the GNU General Public License as published by
     12 ;; the Free Software Foundation, either version 3 of the License, or
     13 ;; (at your option) any later version.
     14 ;;
     15 ;; Magit is distributed in the hope that it will be useful, but WITHOUT
     16 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     17 ;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
     18 ;; License for more details.
     19 ;;
     20 ;; You should have received a copy of the GNU General Public License
     21 ;; along with Magit.  If not, see <https://www.gnu.org/licenses/>.
     22 
     23 ;;; Commentary:
     24 
     25 ;; This library provides basic support for Ediff.
     26 
     27 ;;; Code:
     28 
     29 (require 'magit)
     30 
     31 (require 'ediff)
     32 (require 'smerge-mode)
     33 
     34 (defvar smerge-ediff-buf)
     35 (defvar smerge-ediff-windows)
     36 
     37 ;;; Options
     38 
     39 (defgroup magit-ediff nil
     40   "Ediff support for Magit."
     41   :link '(info-link "(magit)Ediffing")
     42   :group 'magit-extensions)
     43 
     44 (defcustom magit-ediff-quit-hook
     45   '(magit-ediff-cleanup-auxiliary-buffers
     46     magit-ediff-restore-previous-winconf)
     47   "Hooks to run after finishing Ediff, when that was invoked using Magit.
     48 The hooks are run in the Ediff control buffer.  This is similar
     49 to `ediff-quit-hook' but takes the needs of Magit into account.
     50 The `ediff-quit-hook' is ignored by Ediff sessions which were
     51 invoked using Magit."
     52   :package-version '(magit . "2.2.0")
     53   :group 'magit-ediff
     54   :type 'hook
     55   :get #'magit-hook-custom-get
     56   :options '(magit-ediff-cleanup-auxiliary-buffers
     57              magit-ediff-restore-previous-winconf))
     58 
     59 (defcustom magit-ediff-dwim-resolve-function #'magit-ediff-resolve-rest
     60   "The function `magit-ediff-dwim' uses to resolve conflicts."
     61   :package-version '(magit . "4.0.0")
     62   :group 'magit-ediff
     63   :type '(choice (const magit-ediff-resolve-rest)
     64                  (const magit-ediff-resolve-all)
     65                  (const magit-git-mergetool)))
     66 
     67 (defcustom magit-ediff-dwim-show-on-hunks nil
     68   "Whether `magit-ediff-dwim' runs show variants on hunks.
     69 If non-nil, `magit-ediff-show-staged' or
     70 `magit-ediff-show-unstaged' are called based on what section the
     71 hunk is in.  Otherwise, `magit-ediff-dwim' runs
     72 `magit-ediff-stage' when point is on an uncommitted hunk."
     73   :package-version '(magit . "2.2.0")
     74   :group 'magit-ediff
     75   :type 'boolean)
     76 
     77 (defcustom magit-ediff-show-stash-with-index t
     78   "Whether `magit-ediff-show-stash' shows the state of the index.
     79 
     80 If non-nil, use a third Ediff buffer to distinguish which changes
     81 in the stash were staged.  In cases where the stash contains no
     82 staged changes, fall back to a two-buffer Ediff.
     83 
     84 More specifically, a stash is a merge commit, stash@{N}, with
     85 potentially three parents.
     86 
     87 * stash@{N}^1 represents the `HEAD' commit at the time the stash
     88   was created.
     89 
     90 * stash@{N}^2 records any changes that were staged when the stash
     91   was made.
     92 
     93 * stash@{N}^3, if it exists, contains files that were untracked
     94   when stashing.
     95 
     96 If this option is non-nil, `magit-ediff-show-stash' will run
     97 Ediff on a file using three buffers: one for stash@{N}, another
     98 for stash@{N}^1, and a third for stash@{N}^2.
     99 
    100 Otherwise, Ediff uses two buffers, comparing
    101 stash@{N}^1..stash@{N}.  Along with any unstaged changes, changes
    102 in the index commit, stash@{N}^2, will be shown in this
    103 comparison unless they conflicted with changes in the working
    104 tree at the time of stashing."
    105   :package-version '(magit . "2.6.0")
    106   :group 'magit-ediff
    107   :type 'boolean)
    108 
    109 (defvar magit-ediff-use-indirect-buffers nil
    110   "Whether to use indirect buffers.
    111 Ediff already does a lot of buffer and file shuffling and I
    112 recommend you do not further complicate that by enabling this.")
    113 
    114 ;;; Commands
    115 
    116 (defvar magit-ediff-previous-winconf nil)
    117 
    118 ;;;###autoload (autoload 'magit-ediff "magit-ediff" nil)
    119 (transient-define-prefix magit-ediff ()
    120   "Show differences using the Ediff package."
    121   :info-manual "(ediff)"
    122   ["Ediff"
    123    [("E" "Dwim"          magit-ediff-dwim)
    124     ("s" "Stage"         magit-ediff-stage)]
    125    [("m" "Resolve rest"            magit-ediff-resolve-rest)
    126     ("M" "Resolve all conflicts"   magit-ediff-resolve-all)
    127     ("t" "Resolve using mergetool" magit-git-mergetool)]
    128    [("u" "Show unstaged" magit-ediff-show-unstaged)
    129     ("i" "Show staged"   magit-ediff-show-staged)
    130     ("w" "Show worktree" magit-ediff-show-working-tree)]
    131    [("c" "Show commit"   magit-ediff-show-commit)
    132     ("r" "Show range"    magit-ediff-compare)
    133     ("z" "Show stash"    magit-ediff-show-stash)]])
    134 
    135 (defmacro magit-ediff-buffers (a b &optional c setup quit file)
    136   "Run Ediff on two or three buffers.
    137 This is a wrapper around `ediff-buffers-internal'.
    138 
    139 A, B and C have the form (GET-BUFFER CREATE-BUFFER).  If
    140 GET-BUFFER returns a non-nil value, then that buffer is used and
    141 it is not killed when exiting Ediff.  Otherwise CREATE-BUFFER
    142 must return a buffer and that is killed when exiting Ediff.
    143 
    144 If non-nil, SETUP must be a function.  It is called without
    145 arguments after Ediff is done setting up buffers.
    146 
    147 If non-nil, QUIT must be a function.  It is added to
    148 `ediff-quit-hook' and is called without arguments.
    149 
    150 If FILE is non-nil, then perform a merge.  The merge result
    151 is put in FILE."
    152   (let (get make kill (char ?A))
    153     (dolist (spec (list a b c))
    154       (if (not spec)
    155           (push nil make)
    156         (pcase-let ((`(,g ,m) spec))
    157           (let ((b (intern (format "buf%c" char))))
    158             (push `(,b ,g) get)
    159             ;; This is an unfortunate complication that I have added for
    160             ;; the benefit of one user.  Pretend we used this instead:
    161             ;; (push `(or ,b ,m) make)
    162             (push `(if ,b
    163                        (if magit-ediff-use-indirect-buffers
    164                            (prog1 (make-indirect-buffer
    165                                    ,b
    166                                    (generate-new-buffer-name (buffer-name ,b))
    167                                    t)
    168                              (setq ,b nil))
    169                          ,b)
    170                      ,m)
    171                   make)
    172             (push `(unless ,b
    173                      ;; For merge jobs Ediff switches buffer names around.
    174                      ;; See (if ediff-merge-job ...) in `ediff-setup'.
    175                      (let ((var ,(if (and file (= char ?C))
    176                                      'ediff-ancestor-buffer
    177                                    (intern (format "ediff-buffer-%c" char)))))
    178                        (ediff-kill-buffer-carefully var)))
    179                   kill))
    180           (cl-incf char))))
    181     (setq get  (nreverse get))
    182     (setq make (nreverse make))
    183     (setq kill (nreverse kill))
    184     (let ((mconf (cl-gensym "conf"))
    185           (mfile (cl-gensym "file")))
    186       `(magit-with-toplevel
    187          (let ((,mconf (current-window-configuration))
    188                (,mfile ,file)
    189                ,@get)
    190            (ediff-buffers-internal
    191             ,@make
    192             (list ,@(and setup (list setup))
    193                   (lambda ()
    194                     ;; We do not want to kill buffers that existed before
    195                     ;; Ediff was invoked, so we cannot use Ediff's default
    196                     ;; quit functions.  Ediff splits quitting across two
    197                     ;; hooks for merge jobs but we only ever use one.
    198                     (setq-local ediff-quit-merge-hook nil)
    199                     (setq-local ediff-quit-hook
    200                                 (list
    201                                  ,@(and quit (list quit))
    202                                  (lambda ()
    203                                    ,@kill
    204                                    (let ((magit-ediff-previous-winconf ,mconf))
    205                                      (run-hooks 'magit-ediff-quit-hook)))))))
    206             (pcase (list ,(and c t) (and ,mfile t))
    207               ('(nil nil) 'ediff-buffers)
    208               ('(nil t)   'ediff-merge-buffers)
    209               ('(t   nil) 'ediff-buffers3)
    210               ('(t   t)   'ediff-merge-buffers-with-ancestor))
    211             ,mfile))))))
    212 
    213 ;;;###autoload
    214 (defun magit-ediff-resolve-all (file)
    215   "Resolve all conflicts in the FILE at point using Ediff.
    216 
    217 If there is no file at point or if it doesn't have any unmerged
    218 changes, then prompt for a file.
    219 
    220 See info node `(magit) Ediffing' for more information about this
    221 and alternative commands."
    222   (interactive (list (magit-read-unmerged-file)))
    223   (magit-with-toplevel
    224     (let* ((dir   (magit-gitdir))
    225            (revA  (or (magit-name-branch "HEAD")
    226                       (magit-commit-p "HEAD")))
    227            (revB  (cl-find-if (lambda (head)
    228                                 (file-exists-p (expand-file-name head dir)))
    229                               '("MERGE_HEAD" "CHERRY_PICK_HEAD" "REVERT_HEAD")))
    230            (revB  (or (magit-name-branch revB)
    231                       (magit-commit-p revB)))
    232            (revC  (magit-commit-p (magit-git-string "merge-base" revA revB)))
    233            (fileA (magit--rev-file-name file revA revB))
    234            (fileB (magit--rev-file-name file revB revA))
    235            (fileC (or (magit--rev-file-name file revC revA)
    236                       (magit--rev-file-name file revC revB))))
    237       ;; Ediff assumes that the FILE where it is going to store the merge
    238       ;; result does not exist yet, so move the existing file out of the
    239       ;; way.  If a buffer visits FILE, then we have to kill that upfront.
    240       (when-let ((buffer (find-buffer-visiting file)))
    241         (when (and (buffer-modified-p buffer)
    242                    (not (y-or-n-p (format "Save buffer %s %s? "
    243                                           (buffer-name buffer)
    244                                           "(cannot continue otherwise)"))))
    245           (user-error "Abort"))
    246         (kill-buffer buffer))
    247       (let ((orig (concat file ".ORIG")))
    248         (when (file-exists-p orig)
    249           (rename-file orig (make-temp-name (concat orig "_"))))
    250         (rename-file file orig))
    251       (let ((setup (lambda ()
    252                      ;; Use the same conflict marker style as Git uses.
    253                      (setq-local ediff-combination-pattern
    254                                  '("<<<<<<< HEAD" A
    255                                    ,(format "||||||| %s" revC) Ancestor
    256                                    "=======" B
    257                                    ,(format ">>>>>>> %s" revB)))))
    258             (quit  (lambda ()
    259                      ;; For merge jobs Ediff switches buffer names around.
    260                      ;; At this point `ediff-buffer-C' no longer refer to
    261                      ;; the ancestor buffer but to the merge result buffer.
    262                      ;; See (if ediff-merge-job ...) in `ediff-setup'.
    263                      (when (buffer-live-p ediff-buffer-C)
    264                        (with-current-buffer ediff-buffer-C
    265                          (save-buffer)
    266                          (save-excursion
    267                            (goto-char (point-min))
    268                            (unless (re-search-forward "^<<<<<<< " nil t)
    269                              (magit-stage-file file))))))))
    270         (if fileC
    271             (magit-ediff-buffers
    272              ((magit-get-revision-buffer revA fileA)
    273               (magit-find-file-noselect  revA fileA))
    274              ((magit-get-revision-buffer revB fileB)
    275               (magit-find-file-noselect  revB fileB))
    276              ((magit-get-revision-buffer revC fileC)
    277               (magit-find-file-noselect  revC fileC))
    278              setup quit file)
    279           (magit-ediff-buffers
    280            ((magit-get-revision-buffer revA fileA)
    281             (magit-find-file-noselect  revA fileA))
    282            ((magit-get-revision-buffer revB fileB)
    283             (magit-find-file-noselect  revB fileB))
    284            nil setup quit file))))))
    285 
    286 ;;;###autoload
    287 (defun magit-ediff-resolve-rest (file)
    288   "Resolve outstanding conflicts in the FILE at point using Ediff.
    289 
    290 If there is no file at point or if it doesn't have any unmerged
    291 changes, then prompt for a file.
    292 
    293 See info node `(magit) Ediffing' for more information about this
    294 and alternative commands."
    295   (interactive (list (magit-read-unmerged-file)))
    296   (magit-with-toplevel
    297     (with-current-buffer (find-file-noselect file)
    298       (smerge-ediff)
    299       (setq-local
    300        ediff-quit-hook
    301        (lambda ()
    302          (let ((bufC ediff-buffer-C)
    303                (bufS smerge-ediff-buf))
    304            (with-current-buffer bufS
    305              (when (yes-or-no-p (format "Conflict resolution finished; save %s? "
    306                                         buffer-file-name))
    307                (erase-buffer)
    308                (insert-buffer-substring bufC)
    309                (save-buffer))))
    310          (when (buffer-live-p ediff-buffer-A) (kill-buffer ediff-buffer-A))
    311          (when (buffer-live-p ediff-buffer-B) (kill-buffer ediff-buffer-B))
    312          (when (buffer-live-p ediff-buffer-C) (kill-buffer ediff-buffer-C))
    313          (when (buffer-live-p ediff-ancestor-buffer)
    314            (kill-buffer ediff-ancestor-buffer))
    315          (let ((magit-ediff-previous-winconf smerge-ediff-windows))
    316            (run-hooks 'magit-ediff-quit-hook)))))))
    317 
    318 ;;;###autoload
    319 (defun magit-ediff-stage (file)
    320   "Stage and unstage changes to FILE using Ediff.
    321 FILE has to be relative to the top directory of the repository."
    322   (interactive
    323    (let ((files (magit-tracked-files)))
    324      (list (magit-completing-read "Selectively stage file" files nil t nil nil
    325                                   (car (member (magit-current-file) files))))))
    326   (magit-with-toplevel
    327     (let* ((bufA  (magit-get-revision-buffer "HEAD" file))
    328            (bufB  (magit-get-revision-buffer "{index}" file))
    329            (lockB (and bufB (buffer-local-value 'buffer-read-only bufB)))
    330            (bufC  (get-file-buffer file))
    331            ;; Use the same encoding for all three buffers or we
    332            ;; may end up changing the file in an unintended way.
    333            (bufC* (or bufC (find-file-noselect file)))
    334            (coding-system-for-read
    335             (buffer-local-value 'buffer-file-coding-system bufC*))
    336            (bufA* (magit-find-file-noselect-1 "HEAD" file t))
    337            (bufB* (magit-find-file-index-noselect file t)))
    338       (with-current-buffer bufB* (setq buffer-read-only nil))
    339       (magit-ediff-buffers
    340        (bufA bufA*)
    341        (bufB bufB*)
    342        (bufC bufC*)
    343        nil
    344        (lambda ()
    345          (when (buffer-live-p ediff-buffer-B)
    346            (when lockB
    347              (with-current-buffer bufB (setq buffer-read-only t)))
    348            (when (buffer-modified-p ediff-buffer-B)
    349              (with-current-buffer ediff-buffer-B
    350                (magit-update-index))))
    351          (when (and (buffer-live-p ediff-buffer-C)
    352                     (buffer-modified-p ediff-buffer-C))
    353            (with-current-buffer ediff-buffer-C
    354              (when (y-or-n-p (format "Save file %s? " buffer-file-name))
    355                (save-buffer)))))))))
    356 
    357 ;;;###autoload
    358 (defun magit-ediff-compare (revA revB fileA fileB)
    359   "Compare REVA:FILEA with REVB:FILEB using Ediff.
    360 
    361 FILEA and FILEB have to be relative to the top directory of the
    362 repository.  If REVA or REVB is nil, then this stands for the
    363 working tree state.
    364 
    365 If the region is active, use the revisions on the first and last
    366 line of the region.  With a prefix argument, instead of diffing
    367 the revisions, choose a revision to view changes along, starting
    368 at the common ancestor of both revisions (i.e., use a \"...\"
    369 range)."
    370   (interactive
    371    (pcase-let ((`(,revA ,revB) (magit-ediff-compare--read-revisions
    372                                 nil current-prefix-arg)))
    373      (nconc (list revA revB)
    374             (magit-ediff-read-files revA revB))))
    375   (magit-ediff-buffers
    376    ((if revA (magit-get-revision-buffer revA fileA) (get-file-buffer    fileA))
    377     (if revA (magit-find-file-noselect  revA fileA) (find-file-noselect fileA)))
    378    ((if revB (magit-get-revision-buffer revB fileB) (get-file-buffer    fileB))
    379     (if revB (magit-find-file-noselect  revB fileB) (find-file-noselect fileB)))))
    380 
    381 (defun magit-ediff-compare--read-revisions (&optional arg mbase)
    382   (let ((input (or arg (magit-diff-read-range-or-commit
    383                         "Compare range or commit"
    384                         nil mbase))))
    385     (if-let ((range (magit-split-range input)))
    386         (list (car range) (cdr range))
    387       (list input nil))))
    388 
    389 (defun magit-ediff-read-files (revA revB &optional fileB)
    390   "Read file in REVB, return it and the corresponding file in REVA.
    391 When FILEB is non-nil, use this as REVB's file instead of
    392 prompting for it."
    393   (unless (and fileB (member fileB (magit-revision-files revB)))
    394     (setq fileB
    395           (or (and fileB
    396                    magit-buffer-log-files
    397                    (derived-mode-p 'magit-log-mode)
    398                    (member "--follow" magit-buffer-log-args)
    399                    (cdr (assoc fileB
    400                                (magit-renamed-files
    401                                 revB
    402                                 (oref (car (oref magit-root-section children))
    403                                       value)))))
    404               (magit-read-file-choice
    405                (format "File to compare between %s and %s"
    406                        revA (or revB "the working tree"))
    407                (magit-changed-files revA revB)
    408                (format "No changed files between %s and %s"
    409                        revA (or revB "the working tree"))))))
    410   (list (or (car (member fileB (magit-revision-files revA)))
    411             (cdr (assoc fileB (magit-renamed-files revB revA)))
    412             (magit-read-file-choice
    413              (format "File in %s to compare with %s in %s"
    414                      revA fileB (or revB "the working tree"))
    415              (magit-changed-files revB revA)
    416              (format "No files have changed between %s and %s"
    417                      revA revB)))
    418         fileB))
    419 
    420 ;;;###autoload
    421 (defun magit-ediff-dwim ()
    422   "Compare, stage, or resolve using Ediff.
    423 This command tries to guess what file, and what commit or range
    424 the user wants to compare, stage, or resolve using Ediff.  It
    425 might only be able to guess either the file, or range or commit,
    426 in which case the user is asked about the other.  It might not
    427 always guess right, in which case the appropriate `magit-ediff-*'
    428 command has to be used explicitly.  If it cannot read the user's
    429 mind at all, then it asks the user for a command to run."
    430   (interactive)
    431   (magit-section-case
    432     (hunk (save-excursion
    433             (goto-char (oref (oref it parent) start))
    434             (magit-ediff-dwim)))
    435     (t
    436      (let ((range (magit-diff--dwim))
    437            (file (magit-current-file))
    438            command revA revB)
    439        (pcase range
    440          ((and (guard (not magit-ediff-dwim-show-on-hunks))
    441                (or 'unstaged 'staged))
    442           (setq command (if (magit-anything-unmerged-p)
    443                             magit-ediff-dwim-resolve-function
    444                           #'magit-ediff-stage)))
    445          ('unstaged (setq command #'magit-ediff-show-unstaged))
    446          ('staged (setq command #'magit-ediff-show-staged))
    447          (`(commit . ,value)
    448           (setq command #'magit-ediff-show-commit)
    449           (setq revB value))
    450          (`(stash . ,value)
    451           (setq command #'magit-ediff-show-stash)
    452           (setq revB value))
    453          ((pred stringp)
    454           (pcase-let ((`(,a ,b) (magit-ediff-compare--read-revisions range)))
    455             (setq command #'magit-ediff-compare)
    456             (setq revA a)
    457             (setq revB b)))
    458          (_
    459           (when (derived-mode-p 'magit-diff-mode)
    460             (pcase (magit-diff-type)
    461               ('committed (pcase-let ((`(,a ,b)
    462                                        (magit-ediff-compare--read-revisions
    463                                         magit-buffer-range)))
    464                             (setq revA a)
    465                             (setq revB b)))
    466               ((guard (not magit-ediff-dwim-show-on-hunks))
    467                (setq command #'magit-ediff-stage))
    468               ('unstaged  (setq command #'magit-ediff-show-unstaged))
    469               ('staged    (setq command #'magit-ediff-show-staged))
    470               ('undefined (setq command nil))
    471               (_          (setq command nil))))))
    472        (cond ((not command)
    473               (call-interactively
    474                (magit-read-char-case
    475                    "Failed to read your mind; do you want to " t
    476                  (?c "[c]ommit"  #'magit-ediff-show-commit)
    477                  (?r "[r]ange"   #'magit-ediff-compare)
    478                  (?s "[s]tage"   #'magit-ediff-stage)
    479                  (?m "[m] resolve remaining conflicts"
    480                      #'magit-ediff-resolve-rest)
    481                  (?M "[M] resolve all conflicts"
    482                      #'magit-ediff-resolve-all))))
    483              ((eq command #'magit-ediff-compare)
    484               (apply #'magit-ediff-compare revA revB
    485                      (magit-ediff-read-files revA revB file)))
    486              ((eq command #'magit-ediff-show-commit)
    487               (magit-ediff-show-commit revB))
    488              ((eq command #'magit-ediff-show-stash)
    489               (magit-ediff-show-stash revB))
    490              (file
    491               (funcall command file))
    492              (t
    493               (call-interactively command)))))))
    494 
    495 ;;;###autoload
    496 (defun magit-ediff-show-staged (file)
    497   "Show staged changes using Ediff.
    498 
    499 This only allows looking at the changes; to stage, unstage,
    500 and discard changes using Ediff, use `magit-ediff-stage'.
    501 
    502 FILE must be relative to the top directory of the repository."
    503   (interactive
    504    (list (magit-read-file-choice "Show staged changes for file"
    505                                  (magit-staged-files)
    506                                  "No staged files")))
    507   (magit-ediff-buffers ((magit-get-revision-buffer "HEAD" file)
    508                         (magit-find-file-noselect "HEAD" file))
    509                        ((get-buffer (concat file ".~{index}~"))
    510                         (magit-find-file-index-noselect file t))))
    511 
    512 ;;;###autoload
    513 (defun magit-ediff-show-unstaged (file)
    514   "Show unstaged changes using Ediff.
    515 
    516 This only allows looking at the changes; to stage, unstage,
    517 and discard changes using Ediff, use `magit-ediff-stage'.
    518 
    519 FILE must be relative to the top directory of the repository."
    520   (interactive
    521    (list (magit-read-file-choice "Show unstaged changes for file"
    522                                  (magit-unstaged-files)
    523                                  "No unstaged files")))
    524   (magit-ediff-buffers ((get-buffer (concat file ".~{index}~"))
    525                         (magit-find-file-index-noselect file t))
    526                        ((get-file-buffer file)
    527                         (find-file-noselect file))))
    528 
    529 ;;;###autoload
    530 (defun magit-ediff-show-working-tree (file)
    531   "Show changes between `HEAD' and working tree using Ediff.
    532 FILE must be relative to the top directory of the repository."
    533   (interactive
    534    (list (magit-read-file-choice "Show changes in file"
    535                                  (magit-changed-files "HEAD")
    536                                  "No changed files")))
    537   (magit-ediff-buffers ((magit-get-revision-buffer "HEAD" file)
    538                         (magit-find-file-noselect  "HEAD" file))
    539                        ((get-file-buffer file)
    540                         (find-file-noselect file))))
    541 
    542 ;;;###autoload
    543 (defun magit-ediff-show-commit (commit)
    544   "Show changes introduced by COMMIT using Ediff."
    545   (interactive (list (magit-read-branch-or-commit "Revision")))
    546   (let ((revA (concat commit "^"))
    547         (revB commit))
    548     (apply #'magit-ediff-compare
    549            revA revB
    550            (magit-ediff-read-files revA revB (magit-current-file)))))
    551 
    552 ;;;###autoload
    553 (defun magit-ediff-show-stash (stash)
    554   "Show changes introduced by STASH using Ediff.
    555 `magit-ediff-show-stash-with-index' controls whether a
    556 three-buffer Ediff is used in order to distinguish changes in the
    557 stash that were staged."
    558   (interactive (list (magit-read-stash "Stash")))
    559   (pcase-let* ((revA (concat stash "^1"))
    560                (revB (concat stash "^2"))
    561                (revC stash)
    562                (`(,fileA ,fileC) (magit-ediff-read-files revA revC))
    563                (fileB fileC))
    564     (if (and magit-ediff-show-stash-with-index
    565              (member fileA (magit-changed-files revB revA)))
    566         (magit-ediff-buffers
    567          ((magit-get-revision-buffer revA fileA)
    568           (magit-find-file-noselect  revA fileA))
    569          ((magit-get-revision-buffer revB fileB)
    570           (magit-find-file-noselect  revB fileB))
    571          ((magit-get-revision-buffer revC fileC)
    572           (magit-find-file-noselect  revC fileC)))
    573       (magit-ediff-compare revA revC fileA fileC))))
    574 
    575 (defun magit-ediff-cleanup-auxiliary-buffers ()
    576   (let* ((ctl-buf ediff-control-buffer)
    577          (ctl-win (ediff-get-visible-buffer-window ctl-buf))
    578          (ctl-frm ediff-control-frame)
    579          (main-frame (cond ((window-live-p ediff-window-A)
    580                             (window-frame ediff-window-A))
    581                            ((window-live-p ediff-window-B)
    582                             (window-frame ediff-window-B)))))
    583     (ediff-kill-buffer-carefully ediff-diff-buffer)
    584     (ediff-kill-buffer-carefully ediff-custom-diff-buffer)
    585     (ediff-kill-buffer-carefully ediff-fine-diff-buffer)
    586     (ediff-kill-buffer-carefully ediff-tmp-buffer)
    587     (ediff-kill-buffer-carefully ediff-error-buffer)
    588     (ediff-kill-buffer-carefully ediff-msg-buffer)
    589     (ediff-kill-buffer-carefully ediff-debug-buffer)
    590     (when (boundp 'ediff-patch-diagnostics)
    591       (ediff-kill-buffer-carefully ediff-patch-diagnostics))
    592     (cond ((and (display-graphic-p)
    593                 (frame-live-p ctl-frm))
    594            (delete-frame ctl-frm))
    595           ((window-live-p ctl-win)
    596            (delete-window ctl-win)))
    597     (ediff-kill-buffer-carefully ctl-buf)
    598     (when (frame-live-p main-frame)
    599       (select-frame main-frame))))
    600 
    601 (defun magit-ediff-restore-previous-winconf ()
    602   (set-window-configuration magit-ediff-previous-winconf))
    603 
    604 ;;; _
    605 (provide 'magit-ediff)
    606 ;;; magit-ediff.el ends here