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

Chizi123
2018-11-18 c655eea759be1db69c5e6b45c228139d8390122a
commit | author | age
5cb5f7 1 ;;; magit-wip.el --- commit snapshots to work-in-progress refs  -*- lexical-binding: t -*-
C 2
3 ;; Copyright (C) 2010-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 ;; This library defines tree global modes which automatically commit
27 ;; snapshots to branch-specific work-in-progress refs before and after
28 ;; making changes, and two commands which can be used to do so on
29 ;; demand.
30
31 ;;; Code:
32
33 (eval-when-compile
34   (require 'subr-x))
35
36 (require 'magit-core)
37 (require 'magit-log)
38
39 ;;; Options
40
41 (defgroup magit-wip nil
42   "Automatically commit to work-in-progress refs."
43   :link '(info-link "(magit)Wip Modes")
44   :group 'magit-modes
45   :group 'magit-essentials)
46
47 (defgroup magit-wip-legacy nil
48   "It is better to not use these modes individually."
49   :link '(info-link "(magit)Legacy Wip Modes")
50   :group 'magit-wip)
51
52 (defcustom magit-wip-mode-lighter " Wip"
53   "Lighter for Magit-Wip mode."
54   :package-version '(magit . "2.90.0")
55   :group 'magit-wip
56   :type 'string)
57
58 (defcustom magit-wip-after-save-local-mode-lighter ""
59   "Lighter for Magit-Wip-After-Save-Local mode."
60   :package-version '(magit . "2.1.0")
61   :group 'magit-wip-legacy
62   :type 'string)
63
64 (defcustom magit-wip-after-apply-mode-lighter ""
65   "Lighter for Magit-Wip-After-Apply mode."
66   :package-version '(magit . "2.1.0")
67   :group 'magit-wip-legacy
68   :type 'string)
69
70 (defcustom magit-wip-before-change-mode-lighter ""
71   "Lighter for Magit-Wip-Before-Change mode."
72   :package-version '(magit . "2.1.0")
73   :group 'magit-wip-legacy
74   :type 'string)
75
76 (defcustom magit-wip-initial-backup-mode-lighter ""
77   "Lighter for Magit-Wip-Initial Backup mode."
78   :package-version '(magit . "2.1.0")
79   :group 'magit-wip-legacy
80   :type 'string)
81
82 (defcustom magit-wip-merge-branch nil
83   "Whether to merge the current branch into its wip ref.
84
85 If non-nil and the current branch has new commits, then it is
86 merged into the wip ref before creating a new wip commit.  This
87 makes it easier to inspect wip history and the wip commits are
88 never garbage collected.
89
90 If nil and the current branch has new commits, then the wip ref
91 is reset to the tip of the branch before creating a new wip
92 commit.  With this setting wip commits are eventually garbage
93 collected.  This is currently the default."
94   :package-version '(magit . "2.90.0")
95   :group 'magit-wip
96   :type 'boolean)
97
98 (defcustom magit-wip-namespace "refs/wip/"
99   "Namespace used for work-in-progress refs.
100 The wip refs are named \"<namespace/>index/<branchref>\"
101 and \"<namespace/>wtree/<branchref>\".  When snapshots
102 are created while the `HEAD' is detached then \"HEAD\"
103 is used as `branch-ref'."
104   :package-version '(magit . "2.1.0")
105   :group 'magit-wip
106   :type 'string)
107
108 ;;; Modes
109
110 (define-minor-mode magit-wip-mode
111   "Save uncommitted changes to work-in-progress refs.
112
113 Whenever appropriate (i.e. when dataloss would be a possibility
114 otherwise) this mode causes uncommitted changes to be committed
115 to dedicated work-in-progress refs.
116
117 For historic reasons this mode is implemented on top of four
118 other `magit-wip-*' modes, which can also be used individually,
119 if you want finer control over when the wip refs are updated;
120 but that is discouraged."
121   :package-version '(magit . "2.90.0")
122   :lighter magit-wip-mode-lighter
123   :global t
124   (let ((arg (if magit-wip-mode 1 -1)))
125     (magit-wip-after-save-mode arg)
126     (magit-wip-after-apply-mode arg)
127     (magit-wip-before-change-mode arg)
128     (magit-wip-initial-backup-mode arg)))
129
130 (define-minor-mode magit-wip-after-save-local-mode
131   "After saving, also commit to a worktree work-in-progress ref.
132
133 After saving the current file-visiting buffer this mode also
134 commits the changes to the worktree work-in-progress ref for
135 the current branch.
136
137 This mode should be enabled globally by turning on the globalized
138 variant `magit-wip-after-save-mode'."
139   :package-version '(magit . "2.1.0")
140   :lighter magit-wip-after-save-local-mode-lighter
141   (if magit-wip-after-save-local-mode
142       (if (and buffer-file-name (magit-inside-worktree-p t))
143           (add-hook 'after-save-hook 'magit-wip-commit-buffer-file t t)
144         (setq magit-wip-after-save-local-mode nil)
145         (user-error "Need a worktree and a file"))
146     (remove-hook 'after-save-hook 'magit-wip-commit-buffer-file t)))
147
148 (defun magit-wip-after-save-local-mode-turn-on ()
149   (and buffer-file-name
150        (magit-inside-worktree-p t)
151        (magit-file-tracked-p buffer-file-name)
152        (magit-wip-after-save-local-mode)))
153
154 ;;;###autoload
155 (define-globalized-minor-mode magit-wip-after-save-mode
156   magit-wip-after-save-local-mode magit-wip-after-save-local-mode-turn-on
157   :package-version '(magit . "2.1.0")
158   :group 'magit-wip)
159
160 (defun magit-wip-commit-buffer-file (&optional msg)
161   "Commit visited file to a worktree work-in-progress ref.
162
163 Also see `magit-wip-after-save-mode' which calls this function
164 automatically whenever a buffer visiting a tracked file is saved."
165   (interactive)
166   (--when-let (magit-wip-get-ref)
167     (magit-with-toplevel
168       (let ((file (file-relative-name buffer-file-name)))
169         (magit-wip-commit-worktree
170          it (list file)
171          (format (cond (msg)
172                        ((called-interactively-p 'any)
173                         "wip-save %s after save")
174                        (t
175                         "autosave %s after save"))
176                  file))))))
177
178 ;;;###autoload
179 (define-minor-mode magit-wip-after-apply-mode
180   "Commit to work-in-progress refs.
181
182 After applying a change using any \"apply variant\"
183 command (apply, stage, unstage, discard, and reverse) commit the
184 affected files to the current wip refs.  For each branch there
185 may be two wip refs; one contains snapshots of the files as found
186 in the worktree and the other contains snapshots of the entries
187 in the index."
188   :package-version '(magit . "2.1.0")
189   :group 'magit-wip
190   :lighter magit-wip-after-apply-mode-lighter
191   :global t)
192
193 (defun magit-wip-commit-after-apply (&optional files msg)
194   (when magit-wip-after-apply-mode
195     (magit-wip-commit files msg)))
196
197 ;;;###autoload
198 (define-minor-mode magit-wip-before-change-mode
199   "Commit to work-in-progress refs before certain destructive changes.
200
201 Before invoking a revert command or an \"apply variant\"
202 command (apply, stage, unstage, discard, and reverse) commit the
203 affected tracked files to the current wip refs.  For each branch
204 there may be two wip refs; one contains snapshots of the files
205 as found in the worktree and the other contains snapshots of the
206 entries in the index.
207
208 Only changes to files which could potentially be affected by the
209 command which is about to be called are committed."
210   :package-version '(magit . "2.1.0")
211   :group 'magit-wip
212   :lighter magit-wip-before-change-mode-lighter
213   :global t)
214
215 (defun magit-wip-commit-before-change (&optional files msg)
216   (when magit-wip-before-change-mode
217     (magit-with-toplevel
218       (magit-wip-commit files msg))))
219
220 (define-minor-mode magit-wip-initial-backup-mode
221   "Before saving a buffer for the first time, commit to a wip ref."
222   :package-version '(magit . "2.90.0")
223   :group 'magit-wip
224   :lighter magit-wip-initial-backup-mode-lighter
225   :global t
226   (if magit-wip-initial-backup-mode
227       (add-hook  'before-save-hook 'magit-wip-commit-initial-backup)
228     (remove-hook 'before-save-hook 'magit-wip-commit-initial-backup)))
229
230 (defvar-local magit-wip-buffer-backed-up nil)
231 (put 'magit-wip-buffer-backed-up 'permanent-local t)
232
233 ;;;###autoload
234 (defun magit-wip-commit-initial-backup ()
235   "Before saving, commit current file to a worktree wip ref.
236
237 The user has to add this function to `before-save-hook'.
238
239 Commit the current state of the visited file before saving the
240 current buffer to that file.  This backs up the same version of
241 the file as `backup-buffer' would, but stores the backup in the
242 worktree wip ref, which is also used by the various Magit Wip
243 modes, instead of in a backup file as `backup-buffer' would.
244
245 This function ignores the variables that affect `backup-buffer'
246 and can be used along-side that function, which is recommended
247 because this function only backs up files that are tracked in
248 a Git repository."
249   (when (and (not magit-wip-buffer-backed-up)
250              buffer-file-name
251              (magit-inside-worktree-p t)
252              (magit-file-tracked-p buffer-file-name))
253     (let ((magit-save-repository-buffers nil))
254       (magit-wip-commit-buffer-file "autosave %s before save"))
255     (setq magit-wip-buffer-backed-up t)))
256
257 ;;; Core
258
259 (defun magit-wip-commit (&optional files msg)
260   "Commit all tracked files to the work-in-progress refs.
261
262 Interactively, commit all changes to all tracked files using
263 a generic commit message.  With a prefix-argument the commit
264 message is read in the minibuffer.
265
266 Non-interactively, only commit changes to FILES using MSG as
267 commit message."
268   (interactive (list nil (if current-prefix-arg
269                              (magit-read-string "Wip commit message")
270                            "wip-save tracked files")))
271   (--when-let (magit-wip-get-ref)
272     (magit-wip-commit-index it files msg)
273     (magit-wip-commit-worktree it files msg)))
274
275 (defun magit-wip-commit-index (ref files msg)
276   (let* ((wipref (magit--wip-index-ref ref))
277          (parent (magit-wip-get-parent ref wipref))
278          (tree   (magit-git-string "write-tree")))
279     (magit-wip-update-wipref ref wipref tree parent files msg "index")))
280
281 (defun magit-wip-commit-worktree (ref files msg)
282   (let* ((wipref (magit--wip-wtree-ref ref))
283          (parent (magit-wip-get-parent ref wipref))
284          (tree (magit-with-temp-index parent "--reset"
285                  (if files
286                      (magit-call-git "add" "--" files)
287                    (magit-with-toplevel
288                      (magit-call-git "add" "-u" ".")))
289                  (magit-git-string "write-tree"))))
290     (magit-wip-update-wipref ref wipref tree parent files msg "worktree")))
291
292 (defun magit-wip-update-wipref (ref wipref tree parent files msg start-msg)
293   (cond
294    ((and (not (equal parent wipref))
295          (or (not magit-wip-merge-branch)
296              (not (magit-rev-verify wipref))))
297     (setq start-msg (concat "start autosaving " start-msg))
298     (magit-update-ref wipref start-msg
299                       (magit-git-string "commit-tree" "--no-gpg-sign"
300                                         "-p" parent "-m" start-msg
301                                         (concat parent "^{tree}")))
302     (setq parent wipref))
303    ((and magit-wip-merge-branch
304          (or (not (magit-rev-ancestor-p ref wipref))
305              (not (magit-rev-ancestor-p
306                    (concat (magit-git-string "log" "--format=%H"
307                                              "-1" "--merges" wipref)
308                            "^2")
309                    ref))))
310     (setq start-msg (format "merge %s into %s" ref start-msg))
311     (magit-update-ref wipref start-msg
312                       (magit-git-string "commit-tree" "--no-gpg-sign"
313                                         "-p" wipref "-p" ref
314                                         "-m" start-msg
315                                         (concat ref "^{tree}")))
316     (setq parent wipref)))
317   (when (magit-git-failure "diff-tree" "--quiet" parent tree "--" files)
318     (unless (and msg (not (= (aref msg 0) ?\s)))
319       (let ((len (length files)))
320         (setq msg (concat
321                    (cond ((= len 0) "autosave tracked files")
322                          ((> len 1) (format "autosave %s files" len))
323                          (t (concat "autosave "
324                                     (file-relative-name (car files)
325                                                         (magit-toplevel)))))
326                    msg))))
327     (magit-update-ref wipref msg
328                       (magit-git-string "commit-tree" "--no-gpg-sign"
329                                         "-p" parent "-m" msg tree))))
330
331 (defun magit-wip-get-ref ()
332   (let ((ref (or (magit-git-string "symbolic-ref" "HEAD") "HEAD")))
333     (and (magit-rev-verify ref)
334          ref)))
335
336 (defun magit-wip-get-parent (ref wipref)
337   (if (and (magit-rev-verify wipref)
338            (equal (magit-git-string "merge-base" wipref ref)
339                   (magit-rev-verify ref)))
340       wipref
341     ref))
342
343 (defun magit--wip-index-ref (&optional ref)
344   (magit--wip-ref "index/" ref))
345
346 (defun magit--wip-wtree-ref (&optional ref)
347   (magit--wip-ref "wtree/" ref))
348
349 (defun magit--wip-ref (namespace &optional ref)
350   (concat magit-wip-namespace namespace
351           (or (and ref (string-prefix-p "refs/" ref) ref)
352               (when-let ((branch (or ref (magit-get-current-branch))))
353                 (concat "refs/heads/" branch))
354               "HEAD")))
355
356 (defun magit-wip-maybe-add-commit-hook ()
357   (when (and magit-wip-merge-branch
358              (magit-wip-any-enabled-p))
359     (add-hook 'git-commit-post-finish-hook 'magit-wip-commit nil t)))
360
361 (defun magit-wip-any-enabled-p ()
362   (or magit-wip-mode
363       magit-wip-after-save-local-mode
364       magit-wip-after-save-mode
365       magit-wip-after-apply-mode
366       magit-wip-before-change-mode
367       magit-wip-initial-backup-mode))
368
369 ;;; Log
370
371 (defun magit-wip-log-index (args files)
372   "Show log for the index wip ref of the current branch."
373   (interactive (magit-log-arguments))
374   (magit-git-log (list (magit--wip-index-ref)) args files))
375
376 (defun magit-wip-log-worktree (args files)
377   "Show log for the worktree wip ref of the current branch."
378   (interactive (magit-log-arguments))
379   (magit-git-log (list (magit--wip-wtree-ref)) args files))
380
381 (defun magit-wip-log-current (branch args files count)
382   "Show log for the current branch and its wip refs.
383 With a negative prefix argument only show the worktree wip ref.
384 The absolute numeric value of the prefix argument controls how
385 many \"branches\" of each wip ref are shown."
386   (interactive
387    (nconc (list (or (magit-get-current-branch) "HEAD"))
388           (magit-log-arguments)
389           (list (prefix-numeric-value current-prefix-arg))))
390   (magit-wip-log branch args files count))
391
392 (defun magit-wip-log (branch args files count)
393   "Show log for a branch and its wip refs.
394 With a negative prefix argument only show the worktree wip ref.
395 The absolute numeric value of the prefix argument controls how
396 many \"branches\" of each wip ref are shown."
397   (interactive
398    (nconc (list (magit-completing-read
399                  "Log branch and its wip refs"
400                  (-snoc (magit-list-local-branch-names) "HEAD")
401                  nil t nil 'magit-revision-history
402                  (or (magit-branch-at-point)
403                      (magit-get-current-branch)
404                      "HEAD")))
405           (magit-log-arguments)
406           (list (prefix-numeric-value current-prefix-arg))))
407   (magit-git-log (nconc (list branch)
408                         (magit-wip-log-get-tips
409                          (magit--wip-wtree-ref branch)
410                          (abs count))
411                         (and (>= count 0)
412                              (magit-wip-log-get-tips
413                               (magit--wip-index-ref branch)
414                               (abs count))))
415                  args files))
416
417 (defun magit-wip-log-get-tips (wipref count)
418   (when-let ((reflog (magit-git-lines "reflog" wipref)))
419     (let (tips)
420       (while (and reflog (> count 1))
421         (setq reflog (cl-member "^[^ ]+ [^:]+: restart autosaving"
422                                 reflog :test #'string-match-p))
423         (when (and (cadr reflog)
424                    (string-match "^[^ ]+ \\([^:]+\\)" (cadr reflog)))
425           (push (match-string 1 (cadr reflog)) tips))
426         (setq reflog (cddr reflog))
427         (cl-decf count))
428       (cons wipref (nreverse tips)))))
429
430 ;;; _
431 (provide 'magit-wip)
432 ;;; magit-wip.el ends here