magit-stash.el (21938B)
1 ;;; magit-stash.el --- stash support for Magit -*- lexical-binding: t -*- 2 3 ;; Copyright (C) 2008-2021 The Magit Project Contributors 4 ;; 5 ;; You should have received a copy of the AUTHORS.md file which 6 ;; lists all contributors. If not, see http://magit.vc/authors. 7 8 ;; Author: Jonas Bernoulli <jonas@bernoul.li> 9 ;; Maintainer: Jonas Bernoulli <jonas@bernoul.li> 10 11 ;; SPDX-License-Identifier: GPL-3.0-or-later 12 13 ;; Magit is free software; you can redistribute it and/or modify it 14 ;; under the terms of the GNU General Public License as published by 15 ;; the Free Software Foundation; either version 3, or (at your option) 16 ;; any later version. 17 ;; 18 ;; Magit is distributed in the hope that it will be useful, but WITHOUT 19 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 20 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 21 ;; License for more details. 22 ;; 23 ;; You should have received a copy of the GNU General Public License 24 ;; along with Magit. If not, see http://www.gnu.org/licenses. 25 26 ;;; Commentary: 27 28 ;; Support for Git stashes. 29 30 ;;; Code: 31 32 (require 'magit) 33 (require 'magit-reflog) 34 35 ;; For `magit-stash-drop'. 36 (defvar helm-comp-read-use-marked) 37 38 ;;; Options 39 40 (defgroup magit-stash nil 41 "List stashes and show stash diffs." 42 :group 'magit-modes) 43 44 ;;;; Diff options 45 46 (defcustom magit-stash-sections-hook 47 '(magit-insert-stash-notes 48 magit-insert-stash-worktree 49 magit-insert-stash-index 50 magit-insert-stash-untracked) 51 "Hook run to insert sections into stash diff buffers." 52 :package-version '(magit . "2.1.0") 53 :group 'magit-stash 54 :type 'hook) 55 56 ;;;; Log options 57 58 (defcustom magit-stashes-margin 59 (list (nth 0 magit-log-margin) 60 (nth 1 magit-log-margin) 61 'magit-log-margin-width nil 62 (nth 4 magit-log-margin)) 63 "Format of the margin in `magit-stashes-mode' buffers. 64 65 The value has the form (INIT STYLE WIDTH AUTHOR AUTHOR-WIDTH). 66 67 If INIT is non-nil, then the margin is shown initially. 68 STYLE controls how to format the author or committer date. 69 It can be one of `age' (to show the age of the commit), 70 `age-abbreviated' (to abbreviate the time unit to a character), 71 or a string (suitable for `format-time-string') to show the 72 actual date. Option `magit-log-margin-show-committer-date' 73 controls which date is being displayed. 74 WIDTH controls the width of the margin. This exists for forward 75 compatibility and currently the value should not be changed. 76 AUTHOR controls whether the name of the author is also shown by 77 default. 78 AUTHOR-WIDTH has to be an integer. When the name of the author 79 is shown, then this specifies how much space is used to do so." 80 :package-version '(magit . "2.9.0") 81 :group 'magit-stash 82 :group 'magit-margin 83 :type magit-log-margin--custom-type 84 :initialize 'magit-custom-initialize-reset 85 :set-after '(magit-log-margin) 86 :set (apply-partially #'magit-margin-set-variable 'magit-stashes-mode)) 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 ["Snapshot" 103 ("Z" "both" magit-snapshot-both) 104 ("I" "index" magit-snapshot-index) 105 ("W" "worktree" magit-snapshot-worktree) 106 ("r" "to wip ref" magit-wip-commit)] 107 ["Use" 108 ("a" "Apply" magit-stash-apply) 109 ("p" "Pop" magit-stash-pop) 110 ("k" "Drop" magit-stash-drop)] 111 ["Inspect" 112 ("l" "List" magit-stash-list) 113 ("v" "Show" magit-stash-show)] 114 ["Transform" 115 ("b" "Branch" magit-stash-branch) 116 ("B" "Branch here" magit-stash-branch-here) 117 ("f" "Format patch" magit-stash-format-patch)]]) 118 119 (defun magit-stash-arguments () 120 (transient-args 'magit-stash)) 121 122 ;;;###autoload 123 (defun magit-stash-both (message &optional include-untracked) 124 "Create a stash of the index and working tree. 125 Untracked files are included according to infix arguments. 126 One prefix argument is equivalent to `--include-untracked' 127 while two prefix arguments are equivalent to `--all'." 128 (interactive 129 (progn (when (and (magit-merge-in-progress-p) 130 (not (magit-y-or-n-p "\ 131 Stashing and resetting during a merge conflict. \ 132 Applying the resulting stash won't restore the merge state. \ 133 Proceed anyway? "))) 134 (user-error "Abort")) 135 (magit-stash-read-args))) 136 (magit-stash-save message t t include-untracked t)) 137 138 ;;;###autoload 139 (defun magit-stash-index (message) 140 "Create a stash of the index only. 141 Unstaged and untracked changes are not stashed. The stashed 142 changes are applied in reverse to both the index and the 143 worktree. This command can fail when the worktree is not clean. 144 Applying the resulting stash has the inverse effect." 145 (interactive (list (magit-stash-read-message))) 146 (magit-stash-save message t nil nil t 'worktree)) 147 148 ;;;###autoload 149 (defun magit-stash-worktree (message &optional include-untracked) 150 "Create a stash of unstaged changes in the working tree. 151 Untracked files are included according to infix arguments. 152 One prefix argument is equivalent to `--include-untracked' 153 while two prefix arguments are equivalent to `--all'." 154 (interactive (magit-stash-read-args)) 155 (magit-stash-save message nil t include-untracked t 'index)) 156 157 ;;;###autoload 158 (defun magit-stash-keep-index (message &optional include-untracked) 159 "Create a stash of the index and working tree, keeping index intact. 160 Untracked files are included according to infix arguments. 161 One prefix argument is equivalent to `--include-untracked' 162 while two prefix arguments are equivalent to `--all'." 163 (interactive (magit-stash-read-args)) 164 (magit-stash-save message t t include-untracked t 'index)) 165 166 (defun magit-stash-read-args () 167 (list (magit-stash-read-message) 168 (magit-stash-read-untracked))) 169 170 (defun magit-stash-read-untracked () 171 (let ((prefix (prefix-numeric-value current-prefix-arg)) 172 (args (magit-stash-arguments))) 173 (cond ((or (= prefix 16) (member "--all" args)) 'all) 174 ((or (= prefix 4) (member "--include-untracked" args)) t)))) 175 176 (defun magit-stash-read-message () 177 (let* ((default (format "On %s: " 178 (or (magit-get-current-branch) "(no branch)"))) 179 (input (magit-read-string "Stash message" default))) 180 (if (equal input default) 181 (concat default (magit-rev-format "%h %s")) 182 input))) 183 184 ;;;###autoload 185 (defun magit-snapshot-both (&optional include-untracked) 186 "Create a snapshot of the index and working tree. 187 Untracked files are included according to infix arguments. 188 One prefix argument is equivalent to `--include-untracked' 189 while two prefix arguments are equivalent to `--all'." 190 (interactive (magit-snapshot-read-args)) 191 (magit-snapshot-save t t include-untracked t)) 192 193 ;;;###autoload 194 (defun magit-snapshot-index () 195 "Create a snapshot of the index only. 196 Unstaged and untracked changes are not stashed." 197 (interactive) 198 (magit-snapshot-save t nil nil t)) 199 200 ;;;###autoload 201 (defun magit-snapshot-worktree (&optional include-untracked) 202 "Create a snapshot of unstaged changes in the working tree. 203 Untracked files are included according to infix arguments. 204 One prefix argument is equivalent to `--include-untracked' 205 while two prefix arguments are equivalent to `--all'." 206 (interactive (magit-snapshot-read-args)) 207 (magit-snapshot-save nil t include-untracked t)) 208 209 (defun magit-snapshot-read-args () 210 (list (magit-stash-read-untracked))) 211 212 (defun magit-snapshot-save (index worktree untracked &optional refresh) 213 (magit-stash-save (concat "WIP on " (magit-stash-summary)) 214 index worktree untracked refresh t)) 215 216 ;;;###autoload 217 (defun magit-stash-apply (stash) 218 "Apply a stash to the working tree. 219 Try to preserve the stash index. If that fails because there 220 are staged changes, apply without preserving the stash index." 221 (interactive (list (magit-read-stash "Apply stash"))) 222 (if (= (magit-call-git "stash" "apply" "--index" stash) 0) 223 (magit-refresh) 224 (magit-run-git "stash" "apply" stash))) 225 226 ;;;###autoload 227 (defun magit-stash-pop (stash) 228 "Apply a stash to the working tree and remove it from stash list. 229 Try to preserve the stash index. If that fails because there 230 are staged changes, apply without preserving the stash index 231 and forgo removing the stash." 232 (interactive (list (magit-read-stash "Pop stash"))) 233 (if (= (magit-call-git "stash" "apply" "--index" stash) 0) 234 (magit-stash-drop stash) 235 (magit-run-git "stash" "apply" stash))) 236 237 ;;;###autoload 238 (defun magit-stash-drop (stash) 239 "Remove a stash from the stash list. 240 When the region is active offer to drop all contained stashes." 241 (interactive 242 (list (--if-let (magit-region-values 'stash) 243 (magit-confirm 'drop-stashes nil "Drop %i stashes" nil it) 244 (let ((helm-comp-read-use-marked t)) 245 (magit-read-stash "Drop stash"))))) 246 (dolist (stash (if (listp stash) 247 (nreverse (prog1 stash (setq stash (car stash)))) 248 (list stash))) 249 (message "Deleted refs/%s (was %s)" stash 250 (magit-rev-parse "--short" stash)) 251 (magit-call-git "rev-parse" stash) 252 (magit-call-git "stash" "drop" stash)) 253 (magit-refresh)) 254 255 ;;;###autoload 256 (defun magit-stash-clear (ref) 257 "Remove all stashes saved in REF's reflog by deleting REF." 258 (interactive (let ((ref (or (magit-section-value-if 'stashes) "refs/stash"))) 259 (magit-confirm t (format "Drop all stashes in %s" ref)) 260 (list ref))) 261 (magit-run-git "update-ref" "-d" ref)) 262 263 ;;;###autoload 264 (defun magit-stash-branch (stash branch) 265 "Create and checkout a new BRANCH from STASH." 266 (interactive (list (magit-read-stash "Branch stash") 267 (magit-read-string-ns "Branch name"))) 268 (magit-run-git "stash" "branch" branch stash)) 269 270 ;;;###autoload 271 (defun magit-stash-branch-here (stash branch) 272 "Create and checkout a new BRANCH and apply STASH. 273 The branch is created using `magit-branch-and-checkout', using the 274 current branch or `HEAD' as the start-point." 275 (interactive (list (magit-read-stash "Branch stash") 276 (magit-read-string-ns "Branch name"))) 277 (let ((magit-inhibit-refresh t)) 278 (magit-branch-and-checkout branch (or (magit-get-current-branch) "HEAD"))) 279 (magit-stash-apply stash)) 280 281 ;;;###autoload 282 (defun magit-stash-format-patch (stash) 283 "Create a patch from STASH" 284 (interactive (list (magit-read-stash "Create patch from stash"))) 285 (with-temp-file (magit-rev-format "0001-%f.patch" stash) 286 (magit-git-insert "stash" "show" "-p" stash)) 287 (magit-refresh)) 288 289 ;;; Plumbing 290 291 (defun magit-stash-save (message index worktree untracked 292 &optional refresh keep noerror ref) 293 (if (or (and index (magit-staged-files t)) 294 (and worktree (magit-unstaged-files t)) 295 (and untracked (magit-untracked-files (eq untracked 'all)))) 296 (magit-with-toplevel 297 (magit-stash-store message (or ref "refs/stash") 298 (magit-stash-create message index worktree untracked)) 299 (if (eq keep 'worktree) 300 (with-temp-buffer 301 (magit-git-insert "diff" "--cached") 302 (magit-run-git-with-input 303 "apply" "--reverse" "--cached" "--ignore-space-change" "-") 304 (magit-run-git-with-input 305 "apply" "--reverse" "--ignore-space-change" "-")) 306 (unless (eq keep t) 307 (if (eq keep 'index) 308 (magit-call-git "checkout" "--" ".") 309 (magit-call-git "reset" "--hard" "HEAD" "--")) 310 (when untracked 311 (magit-call-git "clean" "--force" "-d" 312 (and (eq untracked 'all) "-x"))))) 313 (when refresh 314 (magit-refresh))) 315 (unless noerror 316 (user-error "No %s changes to save" (cond ((not index) "unstaged") 317 ((not worktree) "staged") 318 (t "local")))))) 319 320 (defun magit-stash-store (message ref commit) 321 (magit-update-ref ref message commit t)) 322 323 (defun magit-stash-create (message index worktree untracked) 324 (unless (magit-rev-parse "--verify" "HEAD") 325 (error "You do not have the initial commit yet")) 326 (let ((magit-git-global-arguments (nconc (list "-c" "commit.gpgsign=false") 327 magit-git-global-arguments)) 328 (default-directory (magit-toplevel)) 329 (summary (magit-stash-summary)) 330 (head "HEAD")) 331 (when (and worktree (not index)) 332 (setq head (or (magit-commit-tree "pre-stash index" nil "HEAD") 333 (error "Cannot save the current index state")))) 334 (or (setq index (magit-commit-tree (concat "index on " summary) nil head)) 335 (error "Cannot save the current index state")) 336 (and untracked 337 (setq untracked (magit-untracked-files (eq untracked 'all))) 338 (setq untracked (magit-with-temp-index nil nil 339 (or (and (magit-update-files untracked) 340 (magit-commit-tree 341 (concat "untracked files on " summary))) 342 (error "Cannot save the untracked files"))))) 343 (magit-with-temp-index index "-m" 344 (when worktree 345 (or (magit-update-files (magit-git-items "diff" "-z" "--name-only" head)) 346 (error "Cannot save the current worktree state"))) 347 (or (magit-commit-tree message nil head index untracked) 348 (error "Cannot save the current worktree state"))))) 349 350 (defun magit-stash-summary () 351 (concat (or (magit-get-current-branch) "(no branch)") 352 ": " (magit-rev-format "%h %s"))) 353 354 ;;; Sections 355 356 (defvar magit-stashes-section-map 357 (let ((map (make-sparse-keymap))) 358 (define-key map [remap magit-delete-thing] 'magit-stash-clear) 359 map) 360 "Keymap for `stashes' section.") 361 362 (defvar magit-stash-section-map 363 (let ((map (make-sparse-keymap))) 364 (define-key map [remap magit-visit-thing] 'magit-stash-show) 365 (define-key map [remap magit-delete-thing] 'magit-stash-drop) 366 (define-key map "a" 'magit-stash-apply) 367 (define-key map "A" 'magit-stash-pop) 368 map) 369 "Keymap for `stash' sections.") 370 371 (magit-define-section-jumper magit-jump-to-stashes 372 "Stashes" stashes "refs/stash") 373 374 (cl-defun magit-insert-stashes (&optional (ref "refs/stash") 375 (heading "Stashes:")) 376 "Insert `stashes' section showing reflog for \"refs/stash\". 377 If optional REF is non-nil, show reflog for that instead. 378 If optional HEADING is non-nil, use that as section heading 379 instead of \"Stashes:\"." 380 (let ((verified (magit-rev-verify ref)) 381 (autostash 382 (and (magit-rebase-in-progress-p) 383 (thread-first 384 (if (file-directory-p (magit-git-dir "rebase-merge")) 385 "rebase-merge/autostash" 386 "rebase-apply/autostash") 387 magit-git-dir 388 magit-file-line)))) 389 (when (or autostash verified) 390 (magit-insert-section (stashes ref) 391 (magit-insert-heading heading) 392 (when autostash 393 (pcase-let ((`(,author ,date ,msg) 394 (split-string 395 (car (magit-git-lines 396 "show" "-q" "--format=%aN%x00%at%x00%s" 397 autostash)) 398 "\0"))) 399 (magit-insert-section (stash autostash) 400 (insert (propertize "AUTOSTASH" 'font-lock-face 'magit-hash)) 401 (insert " " msg "\n") 402 (save-excursion 403 (backward-char) 404 (magit-log-format-margin autostash author date))))) 405 (if verified 406 (magit-git-wash (apply-partially 'magit-log-wash-log 'stash) 407 "reflog" "--format=%gd%x00%aN%x00%at%x00%gs" ref) 408 (insert ?\n) 409 (save-excursion 410 (backward-char) 411 (magit-make-margin-overlay))))))) 412 413 ;;; List Stashes 414 415 ;;;###autoload 416 (defun magit-stash-list () 417 "List all stashes in a buffer." 418 (interactive) 419 (magit-stashes-setup-buffer)) 420 421 (define-derived-mode magit-stashes-mode magit-reflog-mode "Magit Stashes" 422 "Mode for looking at lists of stashes." 423 :group 'magit-log 424 (hack-dir-local-variables-non-file-buffer)) 425 426 (defun magit-stashes-setup-buffer () 427 (magit-setup-buffer #'magit-stashes-mode nil 428 (magit-buffer-refname "refs/stash"))) 429 430 (defun magit-stashes-refresh-buffer () 431 (magit-insert-section (stashesbuf) 432 (magit-insert-heading (if (equal magit-buffer-refname "refs/stash") 433 "Stashes:" 434 (format "Stashes [%s]:" magit-buffer-refname))) 435 (magit-git-wash (apply-partially 'magit-log-wash-log 'stash) 436 "reflog" "--format=%gd%x00%aN%x00%at%x00%gs" magit-buffer-refname))) 437 438 (cl-defmethod magit-buffer-value (&context (major-mode magit-stashes-mode)) 439 magit-buffer-refname) 440 441 (defvar magit--update-stash-buffer nil) 442 443 (defun magit-stashes-maybe-update-stash-buffer (&optional _) 444 "When moving in the stashes buffer, update the stash buffer. 445 If there is no stash buffer in the same frame, then do nothing." 446 (when (derived-mode-p 'magit-stashes-mode) 447 (magit--maybe-update-stash-buffer))) 448 449 (defun magit--maybe-update-stash-buffer () 450 (when-let ((stash (magit-section-value-if 'stash)) 451 (buffer (magit-get-mode-buffer 'magit-stash-mode nil t))) 452 (if magit--update-stash-buffer 453 (setq magit--update-stash-buffer (list stash buffer)) 454 (setq magit--update-stash-buffer (list stash buffer)) 455 (run-with-idle-timer 456 magit-update-other-window-delay nil 457 (let ((args (with-current-buffer buffer 458 (let ((magit-direct-use-buffer-arguments 'selected)) 459 (magit-show-commit--arguments))))) 460 (lambda () 461 (pcase-let ((`(,stash ,buf) magit--update-stash-buffer)) 462 (setq magit--update-stash-buffer nil) 463 (when (buffer-live-p buf) 464 (let ((magit-display-buffer-noselect t)) 465 (apply #'magit-stash-show stash args)))) 466 (setq magit--update-stash-buffer nil))))))) 467 468 ;;; Show Stash 469 470 ;;;###autoload 471 (defun magit-stash-show (stash &optional args files) 472 "Show all diffs of a stash in a buffer." 473 (interactive (cons (or (and (not current-prefix-arg) 474 (magit-stash-at-point)) 475 (magit-read-stash "Show stash")) 476 (pcase-let ((`(,args ,files) 477 (magit-diff-arguments 'magit-stash-mode))) 478 (list (delete "--stat" args) files)))) 479 (magit-stash-setup-buffer stash args files)) 480 481 (define-derived-mode magit-stash-mode magit-diff-mode "Magit Stash" 482 "Mode for looking at individual stashes." 483 :group 'magit-diff 484 (hack-dir-local-variables-non-file-buffer)) 485 486 (defun magit-stash-setup-buffer (stash args files) 487 (magit-setup-buffer #'magit-stash-mode nil 488 (magit-buffer-revision stash) 489 (magit-buffer-range (format "%s^..%s" stash stash)) 490 (magit-buffer-diff-args args) 491 (magit-buffer-diff-files files))) 492 493 (defun magit-stash-refresh-buffer () 494 (magit-set-header-line-format 495 (concat (capitalize magit-buffer-revision) " " 496 (propertize (magit-rev-format "%s" magit-buffer-revision) 497 'font-lock-face 498 (list :weight 'normal :foreground 499 (face-attribute 'default :foreground))))) 500 (setq magit-buffer-revision-hash (magit-rev-parse magit-buffer-revision)) 501 (magit-insert-section (stash) 502 (magit-run-section-hook 'magit-stash-sections-hook))) 503 504 (cl-defmethod magit-buffer-value (&context (major-mode magit-stash-mode)) 505 magit-buffer-revision) 506 507 (defun magit-stash-insert-section (commit range message &optional files) 508 (magit-insert-section (commit commit) 509 (magit-insert-heading message) 510 (magit--insert-diff "diff" range "-p" "--no-prefix" magit-buffer-diff-args 511 "--" (or files magit-buffer-diff-files)))) 512 513 (defun magit-insert-stash-notes () 514 "Insert section showing notes for a stash. 515 This shows the notes for stash@{N} but not for the other commits 516 that make up the stash." 517 (magit-insert-section section (note) 518 (magit-insert-heading "Notes") 519 (magit-git-insert "notes" "show" magit-buffer-revision) 520 (if (= (point) 521 (oref section content)) 522 (magit-cancel-section) 523 (insert "\n")))) 524 525 (defun magit-insert-stash-index () 526 "Insert section showing staged changes of the stash." 527 (magit-stash-insert-section 528 (format "%s^2" magit-buffer-revision) 529 (format "%s^..%s^2" magit-buffer-revision magit-buffer-revision) 530 "Staged")) 531 532 (defun magit-insert-stash-worktree () 533 "Insert section showing unstaged changes of the stash." 534 (magit-stash-insert-section 535 magit-buffer-revision 536 (format "%s^2..%s" magit-buffer-revision magit-buffer-revision) 537 "Unstaged")) 538 539 (defun magit-insert-stash-untracked () 540 "Insert section showing the untracked files commit of the stash." 541 (let ((stash magit-buffer-revision) 542 (rev (concat magit-buffer-revision "^3"))) 543 (when (magit-rev-verify rev) 544 (magit-stash-insert-section (format "%s^3" stash) 545 (format "%s^..%s^3" stash stash) 546 "Untracked files" 547 (magit-git-items "ls-tree" "-z" "--name-only" 548 "-r" "--full-tree" rev))))) 549 550 ;;; _ 551 (provide 'magit-stash) 552 ;;; magit-stash.el ends here