config

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

magit-stash.el (27962B)


      1 ;;; magit-stash.el --- Stash support 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 ;; Support for Git stashes.
     26 
     27 ;;; Code:
     28 
     29 (require 'magit)
     30 (require 'magit-reflog)
     31 (require 'magit-sequence)
     32 
     33 ;;; Options
     34 
     35 (defgroup magit-stash nil
     36   "List stashes and show stash diffs."
     37   :group 'magit-modes)
     38 
     39 ;;;; Diff options
     40 
     41 (defcustom magit-stash-sections-hook
     42   '(magit-insert-stash-notes
     43     magit-insert-stash-worktree
     44     magit-insert-stash-index
     45     magit-insert-stash-untracked)
     46   "Hook run to insert sections into stash diff buffers."
     47   :package-version '(magit . "2.1.0")
     48   :group 'magit-stash
     49   :type 'hook)
     50 
     51 ;;;; Log options
     52 
     53 (defcustom magit-stashes-margin
     54   (list (nth 0 magit-log-margin)
     55         (nth 1 magit-log-margin)
     56         'magit-log-margin-width nil
     57         (nth 4 magit-log-margin))
     58   "Format of the margin in `magit-stashes-mode' buffers.
     59 
     60 The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH).
     61 
     62 If INIT is non-nil, then the margin is shown initially.
     63 STYLE controls how to format the author or committer date.
     64   It can be one of `age' (to show the age of the commit),
     65   `age-abbreviated' (to abbreviate the time unit to a character),
     66   or a string (suitable for `format-time-string') to show the
     67   actual date.  Option `magit-log-margin-show-committer-date'
     68   controls which date is being displayed.
     69 WIDTH controls the width of the margin.  This exists for forward
     70   compatibility and currently the value should not be changed.
     71 AUTHOR controls whether the name of the author is also shown by
     72   default.
     73 AUTHOR-WIDTH has to be an integer.  When the name of the author
     74   is shown, then this specifies how much space is used to do so."
     75   :package-version '(magit . "2.9.0")
     76   :group 'magit-stash
     77   :group 'magit-margin
     78   :type magit-log-margin--custom-type
     79   :initialize #'magit-custom-initialize-reset
     80   :set-after '(magit-log-margin)
     81   :set (apply-partially #'magit-margin-set-variable 'magit-stashes-mode))
     82 
     83 ;;;; Variables
     84 
     85 (defvar magit-stash-read-message-function #'magit-stash-read-message
     86   "Function used to read the message when creating a stash.")
     87 
     88 ;;; Commands
     89 
     90 ;;;###autoload (autoload 'magit-stash "magit-stash" nil t)
     91 (transient-define-prefix magit-stash ()
     92   "Stash uncommitted changes."
     93   :man-page "git-stash"
     94   ["Arguments"
     95    ("-u" "Also save untracked files" ("-u" "--include-untracked"))
     96    ("-a" "Also save untracked and ignored files" ("-a" "--all"))]
     97   [["Stash"
     98     ("z" "both"          magit-stash-both)
     99     ("i" "index"         magit-stash-index)
    100     ("w" "worktree"      magit-stash-worktree)
    101     ("x" "keeping index" magit-stash-keep-index)
    102     ("P" "push"          magit-stash-push :level 5)]
    103    ["Snapshot"
    104     ("Z" "both"          magit-snapshot-both)
    105     ("I" "index"         magit-snapshot-index)
    106     ("W" "worktree"      magit-snapshot-worktree)
    107     ("r" "to wip ref"    magit-wip-commit)]
    108    ["Use"
    109     ("a" "Apply"         magit-stash-apply)
    110     ("p" "Pop"           magit-stash-pop)
    111     ("k" "Drop"          magit-stash-drop)]
    112    ["Inspect"
    113     ("l" "List"          magit-stash-list)
    114     ("v" "Show"          magit-stash-show)]
    115    ["Transform"
    116     ("b" "Branch"        magit-stash-branch)
    117     ("B" "Branch here"   magit-stash-branch-here)
    118     ("f" "Format patch"  magit-stash-format-patch)]])
    119 
    120 (defun magit-stash-arguments ()
    121   (transient-args 'magit-stash))
    122 
    123 ;;;###autoload
    124 (defun magit-stash-both (message &optional include-untracked)
    125   "Create a stash of the index and working tree.
    126 Untracked files are included according to infix arguments.
    127 One prefix argument is equivalent to `--include-untracked'
    128 while two prefix arguments are equivalent to `--all'."
    129   (interactive
    130    (progn (when (and (magit-merge-in-progress-p)
    131                      (not (magit-y-or-n-p "\
    132 Stashing and resetting during a merge conflict. \
    133 Applying the resulting stash won't restore the merge state. \
    134 Proceed anyway? ")))
    135             (user-error "Abort"))
    136           (magit-stash-read-args)))
    137   (magit-stash-save message t t include-untracked t))
    138 
    139 ;;;###autoload
    140 (defun magit-stash-index (message)
    141   "Create a stash of the index only.
    142 Unstaged and untracked changes are not stashed.  The stashed
    143 changes are applied in reverse to both the index and the
    144 worktree.  This command can fail when the worktree is not clean.
    145 Applying the resulting stash has the inverse effect."
    146   (interactive (list (magit-stash-read-message)))
    147   (magit-stash-save message t nil nil t 'worktree))
    148 
    149 ;;;###autoload
    150 (defun magit-stash-worktree (message &optional include-untracked)
    151   "Create a stash of unstaged changes in the working tree.
    152 Untracked files are included according to infix arguments.
    153 One prefix argument is equivalent to `--include-untracked'
    154 while two prefix arguments are equivalent to `--all'."
    155   (interactive (magit-stash-read-args))
    156   (magit-stash-save message nil t include-untracked t 'index))
    157 
    158 ;;;###autoload
    159 (defun magit-stash-keep-index (message &optional include-untracked)
    160   "Create a stash of the index and working tree, keeping index intact.
    161 Untracked files are included according to infix arguments.
    162 One prefix argument is equivalent to `--include-untracked'
    163 while two prefix arguments are equivalent to `--all'."
    164   (interactive (magit-stash-read-args))
    165   (magit-stash-save message t t include-untracked t 'index))
    166 
    167 (defun magit-stash-read-args ()
    168   (list (funcall magit-stash-read-message-function)
    169         (magit-stash-read-untracked)))
    170 
    171 (defun magit-stash-read-message ()
    172   "Read a message from the minibuffer, to be used for a stash.
    173 
    174 The message that Git would have picked, is available as the
    175 default (used when the user enters the empty string) and as
    176 the next history element (which can be accessed with \
    177 \\<minibuffer-local-map>\\[next-history-element])."
    178   (read-string (format "Stash message (default: On%s:%s): "
    179                        (magit--ellipsis) (magit--ellipsis))
    180                nil nil
    181                (format "On %s: %s"
    182                        (or (magit-get-current-branch) "(no branch)")
    183                        (magit-rev-format "%h %s"))))
    184 
    185 (defun magit-stash-read-message-traditional ()
    186   "Read a message from the minibuffer, to be used for a stash.
    187 
    188 If the user confirms the initial-input unmodified, then the
    189 abbreviated commit hash and commit summary are appended.
    190 The resulting message is what Git would have used."
    191   (let* ((default (format "On %s: "
    192                           (or (magit-get-current-branch) "(no branch)")))
    193          (input (magit-read-string "Stash message" default)))
    194     (if (equal input default)
    195         (concat default (magit-rev-format "%h %s"))
    196       input)))
    197 
    198 (defun magit-stash-read-untracked ()
    199   (let ((prefix (prefix-numeric-value current-prefix-arg))
    200         (args   (magit-stash-arguments)))
    201     (cond ((or (= prefix 16) (member "--all" args)) 'all)
    202           ((or (= prefix  4) (member "--include-untracked" args)) t))))
    203 
    204 ;;;###autoload
    205 (defun magit-snapshot-both (&optional include-untracked)
    206   "Create a snapshot of the index and working tree.
    207 Untracked files are included according to infix arguments.
    208 One prefix argument is equivalent to `--include-untracked'
    209 while two prefix arguments are equivalent to `--all'."
    210   (interactive (magit-snapshot-read-args))
    211   (magit-snapshot-save t t include-untracked t))
    212 
    213 ;;;###autoload
    214 (defun magit-snapshot-index ()
    215   "Create a snapshot of the index only.
    216 Unstaged and untracked changes are not stashed."
    217   (interactive)
    218   (magit-snapshot-save t nil nil t))
    219 
    220 ;;;###autoload
    221 (defun magit-snapshot-worktree (&optional include-untracked)
    222   "Create a snapshot of unstaged changes in the working tree.
    223 Untracked files are included according to infix arguments.
    224 One prefix argument is equivalent to `--include-untracked'
    225 while two prefix arguments are equivalent to `--all'."
    226   (interactive (magit-snapshot-read-args))
    227   (magit-snapshot-save nil t include-untracked t))
    228 
    229 (defun magit-snapshot-read-args ()
    230   (list (magit-stash-read-untracked)))
    231 
    232 (defun magit-snapshot-save (index worktree untracked &optional refresh)
    233   (magit-stash-save (concat "WIP on " (magit-stash-summary))
    234                     index worktree untracked refresh t))
    235 
    236 ;;;###autoload (autoload 'magit-stash-push "magit-stash" nil t)
    237 (transient-define-prefix magit-stash-push (&optional transient args)
    238   "Create stash using \"git stash push\".
    239 
    240 This differs from Magit's other stashing commands, which don't
    241 use \"git stash\" and are generally more flexible but don't allow
    242 specifying a list of files to be stashed."
    243   :man-page "git-stash"
    244   ["Arguments"
    245    (magit:-- :reader (lambda (prompt initial-input history)
    246                        (magit-read-files prompt initial-input history
    247                                          #'magit-modified-files)))
    248    ("-u" "Also save untracked files" ("-u" "--include-untracked"))
    249    ("-a" "Also save untracked and ignored files" ("-a" "--all"))
    250    ("-k" "Keep index" ("-k" "--keep-index"))
    251    ("-K" "Don't keep index" "--no-keep-index")]
    252   ["Actions"
    253    ("P" "push" magit-stash-push)]
    254   (interactive (if (eq transient-current-command 'magit-stash-push)
    255                    (list nil (transient-args 'magit-stash-push))
    256                  (list t)))
    257   (if transient
    258       (transient-setup 'magit-stash-push)
    259     (magit-run-git "stash" "push"
    260                    (seq-filter #'atom args)
    261                    (assoc "--" args))))
    262 
    263 ;;;###autoload
    264 (defun magit-stash-apply (stash)
    265   "Apply a stash to the working tree.
    266 
    267 When using a Git release before v2.38.0, simply run \"git stash
    268 apply\" or with a prefix argument \"git stash apply --index\".
    269 
    270 When using Git v2.38.0 or later, behave more intelligently:
    271 
    272 First try \"git stash apply --index\", which tries to preserve
    273 the index stored in the stash, if any.  This may fail because
    274 applying the stash could result in conflicts and those have to
    275 be stored in the index, making it impossible to also store the
    276 stash's index there.
    277 
    278 If the above failed, then try \"git stash apply\".  This fails
    279 \(with or without \"--index\") if there are any uncommitted
    280 changes to files that are also modified in the stash.
    281 
    282 If both of the above failed, then apply using \"git apply\".
    283 If there are no conflicting files, use \"--3way\".  If there are
    284 conflicting files, then using \"--3way\" requires that those
    285 files are staged first, which may be undesirable, so prompt
    286 the user whether to use \"--3way\" or \"--reject\"."
    287   (interactive (list (magit-read-stash "Apply stash")))
    288   (magit-stash--apply "apply" stash))
    289 
    290 ;;;###autoload
    291 (defun magit-stash-pop (stash)
    292   "Apply a stash to the working tree, on success remove it from stash list.
    293 
    294 When using a Git release before v2.38.0, simply run \"git stash
    295 pop\" or with a prefix argument \"git stash pop --index\".
    296 
    297 When using Git v2.38.0 or later, behave more intelligently:
    298 
    299 First try \"git stash pop --index\", which tries to preserve
    300 the index stored in the stash, if any.  This may fail because
    301 applying the stash could result in conflicts and those have to
    302 be stored in the index, making it impossible to also store the
    303 stash's index there.
    304 
    305 If the above failed, then try \"git stash apply\".  This fails
    306 \(with or without \"--index\") if there are any uncommitted
    307 changes to files that are also modified in the stash.
    308 
    309 If both of the above failed, then apply using \"git apply\".
    310 If there are no conflicting files, use \"--3way\".  If there are
    311 conflicting files, then using \"--3way\" requires that those
    312 files are staged first, which may be undesirable, so prompt
    313 the user whether to use \"--3way\" or \"--reject\"."
    314   (interactive (list (magit-read-stash "Pop stash")))
    315   (magit-stash--apply "pop" stash))
    316 
    317 (defun magit-stash--apply (action stash)
    318   (if (magit-git-version< "2.38.0")
    319       (magit-run-git "stash" action stash (and current-prefix-arg "--index"))
    320     (or (magit--run-git-stash action "--index" stash)
    321         ;; The stash's index could not be applied, so always keep the stash.
    322         (magit--run-git-stash "apply" stash)
    323         (let* ((range (format "%s^..%s" stash stash))
    324                (stashed (magit-git-items "diff" "-z" "--name-only" range "--"))
    325                (conflicts (cl-sort (cl-union (magit-unstaged-files t stashed)
    326                                              (magit-untracked-files t stashed)
    327                                              :test #'equal)
    328                                    #'string<))
    329                (arg (cond
    330                      ((not conflicts) "--3way")
    331                      ((magit-confirm-files
    332                        'stash-apply-3way conflicts
    333                        "Apply stash using `--3way', which requires first staging"
    334                        "(else use `--reject')"
    335                        t)
    336                       (magit-stage-1 nil conflicts)
    337                       "--3way")
    338                      ("--reject"))))
    339           (with-temp-buffer
    340             (magit-git-insert "diff" range)
    341             (magit-run-git-with-input "apply" arg "-"))))
    342     (magit-refresh)))
    343 
    344 (defun magit--run-git-stash (&rest args)
    345   (magit--with-temp-process-buffer
    346     (let ((exit (save-excursion
    347                   (with-environment-variables (("LC_ALL" "en_US.utf8"))
    348                     (magit-process-git t "stash" args))))
    349           (buffer (current-buffer))
    350           (failed (looking-at "\\`error: \
    351 Your local changes to the following files would be overwritten by merge")))
    352       (with-current-buffer (magit-process-buffer t)
    353         (magit-process-finish-section
    354          (magit-process-insert-section default-directory magit-git-executable
    355                                        (magit-process-git-arguments args)
    356                                        exit buffer)
    357          exit))
    358       (pcase (list exit failed)
    359         (`(0  ,_) t) ; no conflict
    360         (`(1 nil) t) ; successfully installed conflict
    361         (_ nil)))))  ; could not install conflict, or genuine error
    362 
    363 ;;;###autoload
    364 (defun magit-stash-drop (stash)
    365   "Remove a stash from the stash list.
    366 When the region is active offer to drop all contained stashes."
    367   (interactive
    368    (list (if-let ((values (magit-region-values 'stash)))
    369              (magit-confirm 'drop-stashes nil "Drop %d stashes" nil values)
    370            (magit-read-stash "Drop stash"))))
    371   (dolist (stash (if (listp stash)
    372                      (nreverse (prog1 stash (setq stash (car stash))))
    373                    (list stash)))
    374     (message "Deleted refs/%s (was %s)" stash
    375              (magit-rev-parse "--short" stash))
    376     (magit-call-git "rev-parse" stash)
    377     (magit-call-git "stash" "drop" stash))
    378   (magit-refresh))
    379 
    380 ;;;###autoload
    381 (defun magit-stash-clear (ref)
    382   "Remove all stashes saved in REF's reflog by deleting REF."
    383   (interactive (let ((ref (or (magit-section-value-if 'stashes) "refs/stash")))
    384                  (magit-confirm t (list "Drop all stashes in %s" ref))
    385                  (list ref)))
    386   (magit-run-git "update-ref" "-d" ref))
    387 
    388 ;;;###autoload
    389 (defun magit-stash-branch (stash branch)
    390   "Create and checkout a new BRANCH from an existing STASH.
    391 The new branch starts at the commit that was current when the
    392 stash was created.  If the stash applies cleanly, then drop it."
    393   (interactive (list (magit-read-stash "Branch stash")
    394                      (magit-read-string-ns "Branch name")))
    395   (magit-run-git "stash" "branch" branch stash))
    396 
    397 ;;;###autoload
    398 (defun magit-stash-branch-here (stash branch)
    399   "Create and checkout a new BRANCH from an existing STASH.
    400 Use the current branch or `HEAD' as the starting-point of BRANCH.
    401 Then apply STASH, dropping it if it applies cleanly."
    402   (interactive (list (magit-read-stash "Branch stash")
    403                      (magit-read-string-ns "Branch name")))
    404   (let ((start-point (or (magit-get-current-branch) "HEAD")))
    405     (magit-call-git "checkout" "-b" branch start-point)
    406     (magit-branch-maybe-adjust-upstream branch start-point))
    407   (magit-stash-apply stash))
    408 
    409 ;;;###autoload
    410 (defun magit-stash-format-patch (stash)
    411   "Create a patch from STASH"
    412   (interactive (list (magit-read-stash "Create patch from stash")))
    413   (with-temp-file (magit-rev-format "0001-%f.patch" stash)
    414     (magit-git-insert "stash" "show" "-p" stash))
    415   (magit-refresh))
    416 
    417 ;;; Plumbing
    418 
    419 (defun magit-stash-save (message index worktree untracked
    420                                  &optional refresh keep noerror ref)
    421   (if (or (and index     (magit-staged-files t))
    422           (and worktree  (magit-unstaged-files t))
    423           (and untracked (magit-untracked-files (eq untracked 'all))))
    424       (magit-with-toplevel
    425         (magit-stash-store message (or ref "refs/stash")
    426                            (magit-stash-create message index worktree untracked))
    427         (if (eq keep 'worktree)
    428             (with-temp-buffer
    429               (magit-git-insert "diff" "--cached" "--no-ext-diff")
    430               (magit-run-git-with-input
    431                "apply" "--reverse" "--cached" "--ignore-space-change" "-")
    432               (magit-run-git-with-input
    433                "apply" "--reverse" "--ignore-space-change" "-"))
    434           (unless (eq keep t)
    435             (if (eq keep 'index)
    436                 (magit-call-git "checkout" "--" ".")
    437               (magit-call-git "reset" "--hard" "HEAD" "--"))
    438             (when untracked
    439               (magit-call-git "clean" "--force" "-d"
    440                               (and (eq untracked 'all) "-x")))))
    441         (when refresh
    442           (magit-refresh)))
    443     (unless noerror
    444       (user-error "No %s changes to save" (cond ((not index)  "unstaged")
    445                                                 ((not worktree) "staged")
    446                                                 (t "local"))))))
    447 
    448 (defun magit-stash-store (message ref commit)
    449   (magit-update-ref ref message commit t))
    450 
    451 (defun magit-stash-create (message index worktree untracked)
    452   (unless (magit-rev-parse "--verify" "HEAD")
    453     (error "You do not have the initial commit yet"))
    454   (let ((magit-git-global-arguments (nconc (list "-c" "commit.gpgsign=false")
    455                                            magit-git-global-arguments))
    456         (default-directory (magit-toplevel))
    457         (summary (magit-stash-summary))
    458         (head "HEAD"))
    459     (when (and worktree (not index))
    460       (setq head (or (magit-commit-tree "pre-stash index" nil "HEAD")
    461                      (error "Cannot save the current index state"))))
    462     (or (setq index (magit-commit-tree (concat "index on " summary) nil head))
    463         (error "Cannot save the current index state"))
    464     (and untracked
    465          (setq untracked (magit-untracked-files (eq untracked 'all)))
    466          (setq untracked (magit-with-temp-index nil nil
    467                            (or (and (magit-update-files untracked)
    468                                     (magit-commit-tree
    469                                      (concat "untracked files on " summary)))
    470                                (error "Cannot save the untracked files")))))
    471     (magit-with-temp-index index "-m"
    472       (when worktree
    473         (or (magit-update-files (magit-git-items "diff" "-z" "--name-only" head))
    474             (error "Cannot save the current worktree state")))
    475       (or (magit-commit-tree message nil head index untracked)
    476           (error "Cannot save the current worktree state")))))
    477 
    478 (defun magit-stash-summary ()
    479   (concat (or (magit-get-current-branch) "(no branch)")
    480           ": " (magit-rev-format "%h %s")))
    481 
    482 ;;; Sections
    483 
    484 (defvar-keymap magit-stashes-section-map
    485   :doc "Keymap for `stashes' section."
    486   "<remap> <magit-delete-thing>" #'magit-stash-clear
    487   "<remap> <magit-visit-thing>"  #'magit-stash-list
    488   "<2>" (magit-menu-item "Clear %t" #'magit-stash-clear)
    489   "<1>" (magit-menu-item "List %t"  #'magit-stash-list))
    490 
    491 (defvar-keymap magit-stash-section-map
    492   :doc "Keymap for `stash' sections."
    493   "<remap> <magit-cherry-pick>"  #'magit-stash-pop
    494   "<remap> <magit-cherry-apply>" #'magit-stash-apply
    495   "<remap> <magit-delete-thing>" #'magit-stash-drop
    496   "<remap> <magit-visit-thing>"  #'magit-stash-show
    497   "<4>" (magit-menu-item "Pop %M"    #'magit-stash-pop)
    498   "<3>" (magit-menu-item "Apply %M"  #'magit-stash-apply)
    499   "<2>" (magit-menu-item "Delete %M" #'magit-stash-drop)
    500   "<1>" (magit-menu-item "Visit %v"  #'magit-stash-show))
    501 
    502 (magit-define-section-jumper magit-jump-to-stashes
    503   "Stashes" stashes "refs/stash" magit-insert-stashes)
    504 
    505 (cl-defun magit-insert-stashes (&optional (ref   "refs/stash")
    506                                           (heading "Stashes:"))
    507   "Insert `stashes' section showing reflog for \"refs/stash\".
    508 If optional REF is non-nil, show reflog for that instead.
    509 If optional HEADING is non-nil, use that as section heading
    510 instead of \"Stashes:\"."
    511   (let ((verified (magit-rev-verify ref))
    512         (autostash (magit-rebase--get-state-lines "autostash")))
    513     (when (or autostash verified)
    514       (magit-insert-section (stashes ref)
    515         (magit-insert-heading heading)
    516         (when autostash
    517           (pcase-let ((`(,author ,date ,msg)
    518                        (split-string
    519                         (car (magit-git-lines
    520                               "show" "-q" "--format=%aN%x00%at%x00%s"
    521                               autostash))
    522                         "\0")))
    523             (magit-insert-section (stash autostash)
    524               (insert (propertize "AUTOSTASH" 'font-lock-face 'magit-hash))
    525               (insert " " msg "\n")
    526               (save-excursion
    527                 (backward-char)
    528                 (magit-log-format-margin autostash author date)))))
    529         (if verified
    530             (magit-git-wash (apply-partially #'magit-log-wash-log 'stash)
    531               "reflog" "--format=%gd%x00%aN%x00%at%x00%gs" ref)
    532           (insert ?\n)
    533           (save-excursion
    534             (backward-char)
    535             (magit-make-margin-overlay)))))))
    536 
    537 ;;; List Stashes
    538 
    539 ;;;###autoload
    540 (defun magit-stash-list ()
    541   "List all stashes in a buffer."
    542   (interactive)
    543   (magit-stashes-setup-buffer))
    544 
    545 (define-derived-mode magit-stashes-mode magit-reflog-mode "Magit Stashes"
    546   "Mode for looking at lists of stashes."
    547   :interactive nil
    548   :group 'magit-log
    549   (magit-hack-dir-local-variables))
    550 
    551 (defun magit-stashes-setup-buffer ()
    552   (magit-setup-buffer #'magit-stashes-mode nil
    553     (magit-buffer-refname "refs/stash")))
    554 
    555 (defun magit-stashes-refresh-buffer ()
    556   (magit-insert-section (stashesbuf)
    557     (magit-insert-heading t
    558       (if (equal magit-buffer-refname "refs/stash")
    559           "Stashes"
    560         (format "Stashes [%s]" magit-buffer-refname)))
    561     (magit-git-wash (apply-partially #'magit-log-wash-log 'stash)
    562       "reflog" "--format=%gd%x00%aN%x00%at%x00%gs" magit-buffer-refname)))
    563 
    564 (cl-defmethod magit-buffer-value (&context (major-mode magit-stashes-mode))
    565   magit-buffer-refname)
    566 
    567 (defvar magit--update-stash-buffer nil)
    568 
    569 (defun magit-stashes-maybe-update-stash-buffer (&optional _)
    570   "When moving in the stashes buffer, update the stash buffer.
    571 If there is no stash buffer in the same frame, then do nothing."
    572   (when (derived-mode-p 'magit-stashes-mode)
    573     (magit--maybe-update-stash-buffer)))
    574 
    575 (defun magit--maybe-update-stash-buffer ()
    576   (when-let* ((stash  (magit-section-value-if 'stash))
    577               (buffer (magit-get-mode-buffer 'magit-stash-mode nil t)))
    578     (if magit--update-stash-buffer
    579         (setq magit--update-stash-buffer (list stash buffer))
    580       (setq magit--update-stash-buffer (list stash buffer))
    581       (run-with-idle-timer
    582        magit-update-other-window-delay nil
    583        (let ((args (with-current-buffer buffer
    584                      (let ((magit-direct-use-buffer-arguments 'selected))
    585                        (magit-show-commit--arguments)))))
    586          (lambda ()
    587            (pcase-let ((`(,stash ,buf) magit--update-stash-buffer))
    588              (setq magit--update-stash-buffer nil)
    589              (when (buffer-live-p buf)
    590                (let ((magit-display-buffer-noselect t))
    591                  (apply #'magit-stash-show stash args))))
    592            (setq magit--update-stash-buffer nil)))))))
    593 
    594 ;;; Show Stash
    595 
    596 ;;;###autoload
    597 (defun magit-stash-show (stash &optional args files)
    598   "Show all diffs of a stash in a buffer."
    599   (interactive (cons (or (and (not current-prefix-arg)
    600                               (magit-stash-at-point))
    601                          (magit-read-stash "Show stash"))
    602                      (pcase-let ((`(,args ,files)
    603                                   (magit-diff-arguments 'magit-stash-mode)))
    604                        (list (delete "--stat" args) files))))
    605   (magit-stash-setup-buffer stash args files))
    606 
    607 (define-derived-mode magit-stash-mode magit-diff-mode "Magit Stash"
    608   "Mode for looking at individual stashes."
    609   :interactive nil
    610   :group 'magit-diff
    611   (magit-hack-dir-local-variables)
    612   (setq magit--imenu-group-types '(commit)))
    613 
    614 (defun magit-stash-setup-buffer (stash args files)
    615   (magit-setup-buffer #'magit-stash-mode nil
    616     (magit-buffer-revision stash)
    617     (magit-buffer-range (format "%s^..%s" stash stash))
    618     (magit-buffer-diff-args args)
    619     (magit-buffer-diff-files files)))
    620 
    621 (defun magit-stash-refresh-buffer ()
    622   (magit-set-header-line-format
    623    (concat (capitalize magit-buffer-revision) " "
    624            (propertize (magit-rev-format "%s" magit-buffer-revision)
    625                        'font-lock-face
    626                        (list :weight 'normal :foreground
    627                              (face-attribute 'default :foreground)))))
    628   (setq magit-buffer-revision-hash (magit-rev-parse magit-buffer-revision))
    629   (magit-insert-section (stash)
    630     (magit-run-section-hook 'magit-stash-sections-hook)))
    631 
    632 (cl-defmethod magit-buffer-value (&context (major-mode magit-stash-mode))
    633   magit-buffer-revision)
    634 
    635 (defun magit-stash-insert-section (commit range message &optional files)
    636   (magit-insert-section (commit commit)
    637     (magit-insert-heading message)
    638     (magit--insert-diff nil
    639       "diff" range "-p" "--no-prefix" magit-buffer-diff-args
    640       "--" (or files magit-buffer-diff-files))))
    641 
    642 (defun magit-insert-stash-notes ()
    643   "Insert section showing notes for a stash.
    644 This shows the notes for stash@{N} but not for the other commits
    645 that make up the stash."
    646   (magit-insert-section (note)
    647     (magit-insert-heading t "Notes")
    648     (magit-git-insert "notes" "show" magit-buffer-revision)
    649     (magit-cancel-section 'if-empty)
    650     (insert "\n")))
    651 
    652 (defun magit-insert-stash-index ()
    653   "Insert section showing staged changes of the stash."
    654   (magit-stash-insert-section
    655    (format "%s^2" magit-buffer-revision)
    656    (format "%s^..%s^2" magit-buffer-revision magit-buffer-revision)
    657    "Staged"))
    658 
    659 (defun magit-insert-stash-worktree ()
    660   "Insert section showing unstaged changes of the stash."
    661   (magit-stash-insert-section
    662    magit-buffer-revision
    663    (format "%s^2..%s" magit-buffer-revision magit-buffer-revision)
    664    "Unstaged"))
    665 
    666 (defun magit-insert-stash-untracked ()
    667   "Insert section showing the untracked files commit of the stash."
    668   (let ((stash magit-buffer-revision)
    669         (rev (concat magit-buffer-revision "^3")))
    670     (when (magit-rev-verify rev)
    671       (magit-stash-insert-section (format "%s^3" stash)
    672                                   (format "%s^..%s^3" stash stash)
    673                                   "Untracked files"
    674                                   (magit-git-items "ls-tree" "-z" "--name-only"
    675                                                    "-r" "--full-tree" rev)))))
    676 
    677 ;;; _
    678 (provide 'magit-stash)
    679 ;;; magit-stash.el ends here