fordon_greeman / voidfiles

Branch: 2079ec5
Switch branches/tags
main
theidioticdev add emacs 2079ec5 23h ago
702 lines · 28.3 KB Raw Blame History
1 ;;; magit-stash.el --- Stash support for Magit -*- lexical-binding:t -*-
2
3 ;; Copyright (C) 2008-2026 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 (list #'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 (funcall magit-stash-read-message-function)))
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 first future history element. The second future history
177 element is just \"On BRANCH: \". Future history elements can
178 be accessed using \\<minibuffer-local-map>\\[next-history-element])."
179 (let ((branch (or (magit-get-current-branch) "(no branch)"))
180 (ellipsis (magit--ellipsis)))
181 (read-string (format "Stash message (default: On%s:%s): " ellipsis ellipsis)
182 nil nil
183 (list (format "On %s: %s" branch (magit-rev-format "%h %s"))
184 (format "On %s: " branch)))))
185
186 (defun magit-stash-read-message-traditional ()
187 "Read a message from the minibuffer, to be used for a stash.
188
189 If the user confirms the initial-input unmodified, then the
190 abbreviated commit hash and commit summary are appended.
191 The resulting message is what Git would have used."
192 (let* ((default (format "On %s: "
193 (or (magit-get-current-branch) "(no branch)")))
194 (input (magit-read-string "Stash message" default)))
195 (if (equal input default)
196 (concat default (magit-rev-format "%h %s"))
197 input)))
198
199 (defun magit-stash-read-untracked ()
200 (let ((prefix (prefix-numeric-value current-prefix-arg))
201 (args (magit-stash-arguments)))
202 (cond ((or (= prefix 16) (member "--all" args)) 'all)
203 ((or (= prefix 4) (member "--include-untracked" args)) t))))
204
205 ;;;###autoload
206 (defun magit-snapshot-both (&optional include-untracked)
207 "Create a snapshot of the index and working tree.
208 Untracked files are included according to infix arguments.
209 One prefix argument is equivalent to \"--include-untracked\"
210 while two prefix arguments are equivalent to \"--all\"."
211 (interactive (magit-snapshot-read-args))
212 (magit-snapshot-save t t include-untracked t))
213
214 ;;;###autoload
215 (defun magit-snapshot-index ()
216 "Create a snapshot of the index only.
217 Unstaged and untracked changes are not stashed."
218 (interactive)
219 (magit-snapshot-save t nil nil t))
220
221 ;;;###autoload
222 (defun magit-snapshot-worktree (&optional include-untracked)
223 "Create a snapshot of unstaged changes in the working tree.
224 Untracked files are included according to infix arguments.
225 One prefix argument is equivalent to \"--include-untracked\"
226 while two prefix arguments are equivalent to \"--all\"."
227 (interactive (magit-snapshot-read-args))
228 (magit-snapshot-save nil t include-untracked t))
229
230 (defun magit-snapshot-read-args ()
231 (list (magit-stash-read-untracked)))
232
233 (defun magit-snapshot-save (index worktree untracked &optional refresh)
234 (magit-stash-save (concat "WIP on " (magit-stash-summary))
235 index worktree untracked refresh t))
236
237 ;;;###autoload(autoload 'magit-stash-push "magit-stash" nil t)
238 (transient-define-prefix magit-stash-push (&optional transient args)
239 "Create stash using \"git stash push\".
240
241 This differs from Magit's other stashing commands, which don't
242 use \"git stash\" and are generally more flexible but don't allow
243 specifying a list of files to be stashed."
244 :man-page "git-stash"
245 ["Arguments"
246 (magit:-- :reader (lambda (prompt initial-input history)
247 (magit-read-files prompt initial-input history
248 #'magit-modified-files)))
249 ("-u" "Also save untracked files" ("-u" "--include-untracked"))
250 ("-a" "Also save untracked and ignored files" ("-a" "--all"))
251 ("-k" "Keep index" ("-k" "--keep-index"))
252 ("-K" "Don't keep index" "--no-keep-index")]
253 ["Actions"
254 ("P" "push" magit-stash-push)]
255 (interactive (if (eq transient-current-command 'magit-stash-push)
256 (list nil (transient-args 'magit-stash-push))
257 (list t)))
258 (if transient
259 (transient-setup 'magit-stash-push)
260 (magit-run-git "stash" "push"
261 (seq-filter #'atom args)
262 (assoc "--" args))))
263
264 ;;;###autoload
265 (defun magit-stash-apply (stash)
266 "Apply a stash to the working tree.
267
268 When using a Git release before v2.38.0, simply run \"git stash
269 apply\" or with a prefix argument \"git stash apply --index\".
270
271 When using Git v2.38.0 or later, behave more intelligently:
272
273 First try \"git stash apply --index\", which tries to preserve the
274 index stored in the stash, if any. This may fail because applying
275 the stash could result in conflicts and those have to be stored in
276 the index, making it impossible to also store the stash's index
277 there.
278
279 If \"git stash\" fails, then potentially fall back to using \"git
280 apply\". If the stash does not touch any unstaged files, then pass
281 \"--3way\" to that command. Otherwise ask the user whether to use
282 that argument or \"--reject\". Customize `magit-no-confirm' if you
283 want to fall back to using \"--3way\", without being prompted."
284 (interactive (list (magit-read-stash "Apply stash")))
285 (magit-stash--apply "apply" stash))
286
287 ;;;###autoload
288 (defun magit-stash-pop (stash)
289 "Apply a stash to the working tree, on success remove it from stash list.
290
291 When using a Git release before v2.38.0, simply run \"git stash
292 pop\" or with a prefix argument \"git stash pop --index\".
293
294 When using Git v2.38.0 or later, behave more intelligently:
295
296 First try \"git stash apply --index\", which tries to preserve the
297 index stored in the stash, if any. This may fail because applying
298 the stash could result in conflicts and those have to be stored in
299 the index, making it impossible to also store the stash's index
300 there.
301
302 If \"git stash\" fails, then potentially fall back to using \"git
303 apply\". If the stash does not touch any unstaged files, then pass
304 \"--3way\" to that command. Otherwise ask the user whether to use
305 that argument or \"--reject\". Customize `magit-no-confirm' if you
306 want to fall back to using \"--3way\", without being prompted."
307 (interactive (list (magit-read-stash "Pop stash")))
308 (magit-stash--apply "pop" stash))
309
310 (defun magit-stash--apply (action stash)
311 (if (magit-git-version< "2.38.0")
312 (magit-run-git "stash" action stash (and current-prefix-arg "--index"))
313 (magit-stash--apply-1 action stash)
314 (magit-refresh)))
315
316 (defun magit-stash--apply-1 (action stash)
317 (or
318 (magit--run-git-stash action "--index" stash)
319 ;; The stash's index could not be applied, so always keep the stash.
320 (magit--run-git-stash "apply" stash)
321 (let* ((range (format "%s^..%s" stash stash))
322 (stashed (magit-git-items "diff" "-z" "--name-only" range "--"))
323 (conflicts (compat-call
324 sort (cl-union (magit-unstaged-files t stashed)
325 (magit-untracked-files t stashed)
326 :test #'equal)
327 :lessp #'string<))
328 (arg (if (or (not conflicts)
329 (memq 'stash-apply-3way magit-no-confirm))
330 "--3way"
331 (magit-read-char-case
332 (concat
333 "Could not apply stash because of unstaged changes.\n\n"
334 "To do a three-way merge, these files have to be staged\n"
335 (mapconcat (##format " %s" %) conflicts "\n")
336 "\n")
337 nil
338 (?s (format
339 "\n[s] stage file%s and apply with \"git apply --3way\""
340 (if (length> conflicts 1) "s" ""))
341 "--3way")
342 (?r "\n[r] apply with \"git apply --reject\"" "--reject")
343 (?c "\n[c] cancel" nil)))))
344 (when arg
345 (when (and (equal arg "--3way") conflicts)
346 (magit-stage-1 nil conflicts))
347 (with-temp-buffer
348 (magit-git-insert "diff" range)
349 (magit-run-git-with-input "apply" arg "-"))))))
350
351 (defun magit--run-git-stash (&rest args)
352 (magit--with-temp-process-buffer
353 (let ((exit (save-excursion
354 (with-environment-variables (("LC_ALL" "en_US.utf8"))
355 (magit-process-git t "stash" args))))
356 (buffer (current-buffer))
357 (failed (looking-at "\\`error: ")))
358 (with-current-buffer (magit-process-buffer t)
359 (magit-process-finish-section
360 (magit-process-insert-section default-directory magit-git-executable
361 (magit-process-git-arguments args)
362 exit buffer)
363 exit))
364 (pcase (list exit failed)
365 (`(0 ,_) t) ; no conflict
366 (`(1 nil) t) ; successfully installed conflict
367 (_ nil))))) ; could not install conflict, or genuine error
368
369 ;;;###autoload
370 (defun magit-stash-drop (stash)
371 "Remove a stash from the stash list.
372 When the region is active offer to drop all contained stashes."
373 (interactive
374 (list (if-let ((values (magit-region-values 'stash)))
375 (magit-confirm 'drop-stashes nil "Drop %d stashes" nil values)
376 (magit-read-stash "Drop stash"))))
377 (dolist (stash (if (listp stash)
378 (nreverse (prog1 stash (setq stash (car stash))))
379 (list stash)))
380 (message "Deleted refs/%s (was %s)" stash
381 (magit-rev-parse "--short" stash))
382 (magit-call-git "rev-parse" stash)
383 (magit-call-git "stash" "drop" stash))
384 (magit-refresh))
385
386 ;;;###autoload
387 (defun magit-stash-clear (ref)
388 "Remove all stashes saved in REF's reflog by deleting REF."
389 (interactive (let ((ref (or (magit-section-value-if 'stashes) "refs/stash")))
390 (magit-confirm 'drop-stashes
391 (list "Drop all stashes in %s" ref))
392 (list ref)))
393 (magit-run-git "update-ref" "-d" ref))
394
395 ;;;###autoload
396 (defun magit-stash-branch (stash branch)
397 "Create and checkout a new BRANCH from an existing STASH.
398 The new branch starts at the commit that was current when the
399 stash was created. If the stash applies cleanly, then drop it."
400 (interactive (list (magit-read-stash "Branch stash")
401 (magit-read-string-ns "Branch name")))
402 (magit-run-git "stash" "branch" branch stash))
403
404 ;;;###autoload
405 (defun magit-stash-branch-here (stash branch)
406 "Create and checkout a new BRANCH from an existing STASH.
407 Use the current branch or `HEAD' as the starting-point of BRANCH.
408 Then apply STASH, dropping it if it applies cleanly."
409 (interactive (list (magit-read-stash "Branch stash")
410 (magit-read-string-ns "Branch name")))
411 (let ((start-point (or (magit-get-current-branch) "HEAD")))
412 (magit-call-git "checkout" "-b" branch start-point)
413 (magit-branch-maybe-adjust-upstream branch start-point))
414 (magit-stash-apply stash))
415
416 ;;;###autoload
417 (defun magit-stash-format-patch (stash)
418 "Create a patch from STASH."
419 (interactive (list (magit-read-stash "Create patch from stash")))
420 (with-temp-file (magit-rev-format "0001-%f.patch" stash)
421 (magit-git-insert "stash" "show" "-p" stash))
422 (magit-refresh))
423
424 ;;; Plumbing
425
426 (defun magit-stash-save (message index worktree untracked
427 &optional refresh keep noerror ref)
428 (if (or (and index (magit-staged-files t))
429 (and worktree (magit-unstaged-files t))
430 (and untracked (magit-untracked-files (eq untracked 'all))))
431 (magit-with-toplevel
432 (magit-stash-store message (or ref "refs/stash")
433 (magit-stash-create message index worktree untracked))
434 (if (eq keep 'worktree)
435 (with-temp-buffer
436 (magit-git-insert "diff" "--cached" "--no-ext-diff")
437 (magit-run-git-with-input
438 "apply" "--reverse" "--cached" "--ignore-space-change" "-")
439 (magit-run-git-with-input
440 "apply" "--reverse" "--ignore-space-change" "-"))
441 (unless (eq keep t)
442 (if (eq keep 'index)
443 (magit-call-git "checkout" "--" ".")
444 (magit-call-git "reset" "--hard" "HEAD" "--"))
445 (when untracked
446 (magit-call-git "clean" "--force" "-d"
447 (and (eq untracked 'all) "-x")))))
448 (when refresh
449 (magit-refresh)))
450 (unless noerror
451 (user-error "No %s changes to save" (cond ((not index) "unstaged")
452 ((not worktree) "staged")
453 ("local"))))))
454
455 (defun magit-stash-store (message ref rev)
456 (magit-update-ref ref message rev))
457
458 (defun magit-stash-create (message index worktree untracked)
459 (unless (magit-rev-parse "--verify" "HEAD")
460 (error "You do not have the initial commit yet"))
461 (let ((magit-git-global-arguments (nconc (list "-c" "commit.gpgsign=false")
462 magit-git-global-arguments))
463 (default-directory (magit-toplevel))
464 (summary (magit-stash-summary))
465 (head "HEAD"))
466 (when (and worktree (not index))
467 (setq head (or (magit-commit-tree "pre-stash index" nil "HEAD")
468 (error "Cannot save the current index state"))))
469 (or (setq index (magit-commit-tree (concat "index on " summary) nil head))
470 (error "Cannot save the current index state"))
471 (and untracked
472 (setq untracked (magit-untracked-files (eq untracked 'all)))
473 (setq untracked (magit-with-temp-index nil nil
474 (or (and (magit-update-files untracked)
475 (magit-commit-tree
476 (concat "untracked files on " summary)))
477 (error "Cannot save the untracked files")))))
478 (magit-with-temp-index index "-m"
479 (when worktree
480 (or (magit-update-files (magit-git-items "diff" "-z" "--name-only" head))
481 (error "Cannot save the current worktree state")))
482 (or (magit-commit-tree message nil head index untracked)
483 (error "Cannot save the current worktree state")))))
484
485 (defun magit-stash-summary ()
486 (concat (or (magit-get-current-branch) "(no branch)")
487 ": " (magit-rev-format "%h %s")))
488
489 ;;; Sections
490
491 (defvar-keymap magit-stashes-section-map
492 :doc "Keymap for `stashes' section."
493 "<remap> <magit-delete-thing>" #'magit-stash-clear
494 "<remap> <magit-visit-thing>" #'magit-stash-list
495 "<2>" (magit-menu-item "Clear %t" #'magit-stash-clear)
496 "<1>" (magit-menu-item "List %t" #'magit-stash-list))
497
498 (defvar-keymap magit-stash-section-map
499 :doc "Keymap for `stash' sections."
500 "<remap> <magit-cherry-pick>" #'magit-stash-pop
501 "<remap> <magit-cherry-apply>" #'magit-stash-apply
502 "<remap> <magit-delete-thing>" #'magit-stash-drop
503 "<remap> <magit-visit-thing>" #'magit-stash-show
504 "<4>" (magit-menu-item "Pop %M" #'magit-stash-pop)
505 "<3>" (magit-menu-item "Apply %M" #'magit-stash-apply)
506 "<2>" (magit-menu-item "Delete %M" #'magit-stash-drop)
507 "<1>" (magit-menu-item "Visit %v" #'magit-stash-show))
508
509 (magit-define-section-jumper magit-jump-to-stashes
510 "Stashes" stashes "refs/stash" magit-insert-stashes)
511
512 (cl-defun magit-insert-stashes (&optional (ref "refs/stash")
513 (heading "Stashes:"))
514 "Insert `stashes' section showing reflog for \"refs/stash\".
515 If optional REF is non-nil, show reflog for that instead.
516 If optional HEADING is non-nil, use that as section heading
517 instead of \"Stashes:\"."
518 (let ((verified (magit-rev-verify ref))
519 (autostash (magit-rebase--get-state-lines "autostash")))
520 (when (or autostash verified)
521 (magit-insert-section (stashes ref)
522 (magit-insert-heading heading)
523 (when autostash
524 (pcase-let ((`(,author ,date ,msg)
525 (split-string
526 (car (magit-git-lines
527 "show" "-q" "--format=%aN%x00%at%x00%s"
528 autostash))
529 "\0")))
530 (magit-insert-section (stash autostash)
531 (insert (propertize "AUTOSTASH" 'font-lock-face 'magit-hash))
532 (insert " " msg "\n")
533 (magit-log-format-margin autostash author date))))
534 (if verified
535 (magit-git-wash (apply-partially #'magit-log-wash-log 'stash)
536 "reflog" "--format=%gd%x00%aN%x00%at%x00%gs" ref)
537 (insert ?\n)
538 (magit-make-margin-overlay))))))
539
540 ;;; List Stashes
541
542 ;;;###autoload
543 (defun magit-stash-list ()
544 "List all stashes in a buffer."
545 (interactive)
546 (magit-stashes-setup-buffer))
547
548 (define-derived-mode magit-stashes-mode magit-reflog-mode "Magit Stashes"
549 "Mode for looking at lists of stashes."
550 :interactive nil
551 :group 'magit-log
552 (magit-hack-dir-local-variables))
553
554 (defun magit-stashes-setup-buffer ()
555 (magit-setup-buffer #'magit-stashes-mode nil
556 (magit-buffer-refname "refs/stash")))
557
558 (defun magit-stashes-refresh-buffer ()
559 (magit-insert-section (stashesbuf)
560 (magit-insert-heading t
561 (if (equal magit-buffer-refname "refs/stash")
562 "Stashes"
563 (format "Stashes [%s]" magit-buffer-refname)))
564 (magit-git-wash (apply-partially #'magit-log-wash-log 'stash)
565 "reflog" "--format=%gd%x00%aN%x00%at%x00%gs" magit-buffer-refname)))
566
567 (cl-defmethod magit-buffer-value (&context (major-mode magit-stashes-mode))
568 magit-buffer-refname)
569
570 (defvar magit--update-stash-buffer nil)
571
572 (defun magit-stashes-maybe-update-stash-buffer (&optional _)
573 "When moving in the stashes buffer, update the stash buffer.
574 If there is no stash buffer in the same frame, then do nothing.
575 See also info node `(magit)Section Movement'."
576 (when (derived-mode-p 'magit-stashes-mode)
577 (magit--maybe-update-stash-buffer)))
578
579 (defun magit--maybe-update-stash-buffer ()
580 (when-let* ((stash (magit-section-value-if 'stash))
581 (buffer (magit-get-mode-buffer 'magit-stash-mode nil t)))
582 (if magit--update-stash-buffer
583 (setq magit--update-stash-buffer (list stash buffer))
584 (setq magit--update-stash-buffer (list stash buffer))
585 (run-with-idle-timer
586 magit-update-other-window-delay nil
587 (let ((args (with-current-buffer buffer
588 (let ((magit-direct-use-buffer-arguments 'selected))
589 (magit-show-commit--arguments)))))
590 (lambda ()
591 (pcase-let ((`(,stash ,buf) magit--update-stash-buffer))
592 (setq magit--update-stash-buffer nil)
593 (when (buffer-live-p buf)
594 (let ((magit-display-buffer-noselect t))
595 (apply #'magit-stash-show stash args))))
596 (setq magit--update-stash-buffer nil)))))))
597
598 ;;; Show Stash
599
600 ;;;###autoload
601 (defun magit-stash-show (stash &optional args files)
602 "Show all diffs of a stash in a buffer."
603 (interactive (cons (or (and (not current-prefix-arg)
604 (magit-stash-at-point))
605 (magit-read-stash "Show stash"))
606 (pcase-let ((`(,args ,files)
607 (magit-diff-arguments 'magit-stash-mode)))
608 (list (delete "--stat" args) files))))
609 (magit-stash-setup-buffer stash args files))
610
611 (define-derived-mode magit-stash-mode magit-diff-mode "Magit Stash"
612 "Mode for looking at individual stashes."
613 :interactive nil
614 :group 'magit-diff
615 (magit-hack-dir-local-variables)
616 (setq magit--imenu-group-types '(commit)))
617
618 (put 'magit-stash-mode 'magit-diff-default-arguments
619 '("--no-ext-diff"))
620
621 (defun magit-stash-setup-buffer (stash args files)
622 (magit-setup-buffer #'magit-stash-mode nil
623 (magit-buffer-revision stash)
624 (magit-buffer-diff-range (format "%s^..%s" stash stash))
625 (magit-buffer-diff-args args)
626 (magit-buffer-diff-files files)))
627
628 (defun magit-stash-refresh-buffer ()
629 (magit-set-header-line-format
630 (concat (capitalize magit-buffer-revision) " "
631 (propertize (magit-rev-format "%s" magit-buffer-revision)
632 'font-lock-face
633 (list :weight 'normal :foreground
634 (face-attribute 'default :foreground)))))
635 (setq magit-buffer-revision-oid (magit-commit-oid magit-buffer-revision))
636 (magit-insert-section (stash)
637 (magit-run-section-hook 'magit-stash-sections-hook)))
638
639 (cl-defmethod magit-buffer-value (&context (major-mode magit-stash-mode))
640 magit-buffer-revision)
641
642 (defun magit-stash-insert-section (rev range message &optional files)
643 (magit-insert-section (commit rev)
644 (magit-insert-heading message)
645 (magit--insert-diff nil
646 "diff" range "-p" "--no-prefix" magit-buffer-diff-args
647 "--" (or files magit-buffer-diff-files))))
648
649 (defun magit-insert-stash-notes ()
650 "Insert section showing notes for a stash.
651 This shows the notes for stash@{N} but not for the other commits
652 that make up the stash."
653 (magit-insert-section (note)
654 (magit-insert-heading t "Notes")
655 (magit-git-insert "notes" "show" magit-buffer-revision)
656 (magit-cancel-section 'if-empty)
657 (insert "\n")))
658
659 (defun magit-insert-stash-index ()
660 "Insert section showing staged changes of the stash."
661 (magit-stash-insert-section
662 (format "%s^2" magit-buffer-revision)
663 (format "%s^..%s^2" magit-buffer-revision magit-buffer-revision)
664 "Staged"))
665
666 (defun magit-insert-stash-worktree ()
667 "Insert section showing unstaged changes of the stash."
668 (magit-stash-insert-section
669 magit-buffer-revision
670 (format "%s^2..%s" magit-buffer-revision magit-buffer-revision)
671 "Unstaged"))
672
673 (defun magit-insert-stash-untracked ()
674 "Insert section showing the untracked files commit of the stash."
675 (let ((stash magit-buffer-revision)
676 (rev (concat magit-buffer-revision "^3")))
677 (when (magit-rev-verify rev)
678 (magit-stash-insert-section (format "%s^3" stash)
679 (format "%s^..%s^3" stash stash)
680 "Untracked files"
681 (magit-git-items "ls-tree" "-z" "--name-only"
682 "-r" "--full-tree" rev)))))
683
684 ;;; _
685 (provide 'magit-stash)
686 ;; Local Variables:
687 ;; read-symbol-shorthands: (
688 ;; ("and$" . "cond-let--and$")
689 ;; ("thread$" . "cond-let--thread$")
690 ;; ("when$" . "cond-let--when$")
691 ;; ("and-let*" . "cond-let--and-let*")
692 ;; ("and-let" . "cond-let--and-let")
693 ;; ("if-let*" . "cond-let--if-let*")
694 ;; ("if-let" . "cond-let--if-let")
695 ;; ("when-let*" . "cond-let--when-let*")
696 ;; ("when-let" . "cond-let--when-let")
697 ;; ("while-let*" . "cond-let--while-let*")
698 ;; ("while-let" . "cond-let--while-let")
699 ;; ("match-string" . "match-string")
700 ;; ("match-str" . "match-string-no-properties"))
701 ;; End:
702 ;;; magit-stash.el ends here