mirror of https://github.com/Chizi123/.emacs.d.git

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