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

Chizi123
2018-11-18 21067e7cbe6d7a0f65ff5c317a96b5c337b0b3d8
commit | author | age
5cb5f7 1 ;;; magit-commit.el --- create Git commits  -*- 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 ;; This library implements commands for creating Git commits.  These
27 ;; commands just initiate the commit, support for writing the commit
28 ;; messages is implemented in `git-commit.el'.
29
30 ;;; Code:
31
32 (require 'magit)
33 (require 'magit-sequence)
34
35 (eval-when-compile (require 'epa)) ; for `epa-protocol'
36 (eval-when-compile (require 'epg))
37 (eval-when-compile (require 'subr-x))
38
39 ;;; Options
40
41 (defcustom magit-commit-arguments nil
42   "The arguments used when committing."
43   :group 'magit-git-arguments
44   :type '(repeat (string :tag "Argument")))
45
46 (defcustom magit-commit-ask-to-stage 'verbose
47   "Whether to ask to stage all unstaged changes when committing and nothing is staged."
48   :package-version '(magit . "2.3.0")
49   :group 'magit-commands
50   :type '(choice (const :tag "Ask showing diff" verbose)
51                  (const :tag "Ask" t)
52                  (const :tag "Don't ask" nil)))
53
54 (defcustom magit-commit-show-diff t
55   "Whether the relevant diff is automatically shown when committing."
56   :package-version '(magit . "2.3.0")
57   :group 'magit-commands
58   :type 'boolean)
59
60 (defcustom magit-commit-extend-override-date t
61   "Whether using `magit-commit-extend' changes the committer date."
62   :package-version '(magit . "2.3.0")
63   :group 'magit-commands
64   :type 'boolean)
65
66 (defcustom magit-commit-reword-override-date t
67   "Whether using `magit-commit-reword' changes the committer date."
68   :package-version '(magit . "2.3.0")
69   :group 'magit-commands
70   :type 'boolean)
71
72 (defcustom magit-commit-squash-confirm t
73   "Whether the commit targeted by squash and fixup has to be confirmed.
74 When non-nil then the commit at point (if any) is used as default
75 choice, otherwise it has to be confirmed.  This option only
76 affects `magit-commit-squash' and `magit-commit-fixup'.  The
77 \"instant\" variants always require confirmation because making
78 an error while using those is harder to recover from."
79   :package-version '(magit . "2.1.0")
80   :group 'magit-commands
81   :type 'boolean)
82
83 (defcustom magit-post-commit-hook nil
84   "Hook run after creating a commit without the user editing a message.
85
86 This hook is run by `magit-refresh' if `this-command' is a member
87 of `magit-post-stage-hook-commands'.  This only includes commands
88 named `magit-commit-*' that do *not* require that the user edits
89 the commit message in a buffer and then finishes by pressing
90 \\<with-editor-mode-map>\\[with-editor-finish].
91
92 Also see `git-commit-post-finish-hook'."
93   :package-version '(magit . "2.90.0")
94   :group 'magit-commands
95   :type 'hook)
96
97 (defvar magit-post-commit-hook-commands
98   '(magit-commit-extend
99     magit-commit-fixup
100     magit-commit-augment
101     magit-commit-instant-fixup
102     magit-commit-instant-squash))
103
104 ;;; Popup
105
106 (defun magit-commit-popup (&optional arg)
107   "Popup console for commit commands."
108   (interactive "P")
109   (--if-let (magit-commit-message-buffer)
110       (switch-to-buffer it)
111     (magit-invoke-popup 'magit-commit-popup nil arg)))
112
113 (defvar magit-commit-popup
114   '(:variable magit-commit-arguments
115     :man-page "git-commit"
116     :switches ((?a "Stage all modified and deleted files"   "--all")
117                (?e "Allow empty commit"                     "--allow-empty")
118                (?v "Show diff of changes to be committed"   "--verbose")
119                (?h "Disable hooks"                          "--no-verify")
120                (?s "Add Signed-off-by line"                 "--signoff")
121                (?R "Claim authorship and reset author date" "--reset-author"))
122     :options  ((?A "Override the author"  "--author=")
123                (?S "Sign using gpg"       "--gpg-sign=" magit-read-gpg-secret-key)
124                (?C "Reuse commit message" "--reuse-message="
125                    magit-read-reuse-message))
126     :actions  ((?c "Commit"         magit-commit-create)
127                (?e "Extend"         magit-commit-extend)
128                (?f "Fixup"          magit-commit-fixup)
129                (?F "Instant Fixup"  magit-commit-instant-fixup) nil
130                (?w "Reword"         magit-commit-reword)
131                (?s "Squash"         magit-commit-squash)
132                (?S "Instant Squash" magit-commit-instant-squash) nil
133                (?a "Amend"          magit-commit-amend)
134                (?A "Augment"        magit-commit-augment))
135     :max-action-columns 4
136     :default-action magit-commit-create))
137
138 (magit-define-popup-keys-deferred 'magit-commit-popup)
139
140 (defun magit-commit-arguments nil
141   (if (eq magit-current-popup 'magit-commit-popup)
142       magit-current-popup-args
143     magit-commit-arguments))
144
145 (defvar magit-gpg-secret-key-hist nil)
146
147 (defun magit-read-gpg-secret-key (prompt &optional _initial-input)
148   (require 'epa)
149   (let ((keys (--map (concat (epg-sub-key-id (car (epg-key-sub-key-list it)))
150                              " "
151                              (when-let ((id-obj (car (epg-key-user-id-list it))))
152                                (let ((id-str (epg-user-id-string id-obj)))
153                                  (if (stringp id-str)
154                                      id-str
155                                    (epg-decode-dn id-obj)))))
156                      (epg-list-keys (epg-make-context epa-protocol) nil t))))
157     (car (split-string (magit-completing-read
158                         prompt keys nil nil nil 'magit-gpg-secret-key-hist
159                         (car (or magit-gpg-secret-key-hist keys)))
160                        " "))))
161
162 (defun magit-read-reuse-message (prompt &optional default)
163   (magit-completing-read prompt (magit-list-refnames)
164                          nil nil nil 'magit-revision-history
165                          (or default
166                              (and (magit-rev-verify "ORIG_HEAD")
167                                   "ORIG_HEAD"))))
168
169 ;;; Commands
170
171 ;;;###autoload
172 (defun magit-commit-create (&optional args)
173   "Create a new commit on `HEAD'.
174 With a prefix argument, amend to the commit at `HEAD' instead.
175 \n(git commit [--amend] ARGS)"
176   (interactive (if current-prefix-arg
177                    (list (cons "--amend" (magit-commit-arguments)))
178                  (list (magit-commit-arguments))))
179   (when (member "--all" args)
180     (setq this-command 'magit-commit-all))
181   (when (setq args (magit-commit-assert args))
182     (let ((default-directory (magit-toplevel)))
183       (magit-run-git-with-editor "commit" args))))
184
185 ;;;###autoload
186 (defun magit-commit-amend (&optional args)
187   "Amend the last commit.
188 \n(git commit --amend ARGS)"
189   (interactive (list (magit-commit-arguments)))
190   (magit-commit-amend-assert)
191   (magit-run-git-with-editor "commit" "--amend" args))
192
193 ;;;###autoload
194 (defun magit-commit-extend (&optional args override-date)
195   "Amend the last commit, without editing the message.
196
197 With a prefix argument keep the committer date, otherwise change
198 it.  The option `magit-commit-extend-override-date' can be used
199 to inverse the meaning of the prefix argument.  \n(git commit
200 --amend --no-edit)"
201   (interactive (list (magit-commit-arguments)
202                      (if current-prefix-arg
203                          (not magit-commit-extend-override-date)
204                        magit-commit-extend-override-date)))
205   (when (setq args (magit-commit-assert args (not override-date)))
206     (magit-commit-amend-assert)
207     (let ((process-environment process-environment))
208       (unless override-date
209         (push (magit-rev-format "GIT_COMMITTER_DATE=%cD") process-environment))
210       (magit-run-git-with-editor "commit" "--amend" "--no-edit" args))))
211
212 ;;;###autoload
213 (defun magit-commit-reword (&optional args override-date)
214   "Reword the last commit, ignoring staged changes.
215
216 With a prefix argument keep the committer date, otherwise change
217 it.  The option `magit-commit-reword-override-date' can be used
218 to inverse the meaning of the prefix argument.
219
220 Non-interactively respect the optional OVERRIDE-DATE argument
221 and ignore the option.
222 \n(git commit --amend --only)"
223   (interactive (list (magit-commit-arguments)
224                      (if current-prefix-arg
225                          (not magit-commit-reword-override-date)
226                        magit-commit-reword-override-date)))
227   (magit-commit-amend-assert)
228   (let ((process-environment process-environment))
229     (unless override-date
230       (push (magit-rev-format "GIT_COMMITTER_DATE=%cD") process-environment))
231     (magit-run-git-with-editor "commit" "--amend" "--only" args)))
232
233 ;;;###autoload
234 (defun magit-commit-fixup (&optional commit args)
235   "Create a fixup commit.
236
237 With a prefix argument the target COMMIT has to be confirmed.
238 Otherwise the commit at point may be used without confirmation
239 depending on the value of option `magit-commit-squash-confirm'."
240   (interactive (list (magit-commit-at-point)
241                      (magit-commit-arguments)))
242   (magit-commit-squash-internal "--fixup" commit args))
243
244 ;;;###autoload
245 (defun magit-commit-squash (&optional commit args)
246   "Create a squash commit, without editing the squash message.
247
248 With a prefix argument the target COMMIT has to be confirmed.
249 Otherwise the commit at point may be used without confirmation
250 depending on the value of option `magit-commit-squash-confirm'."
251   (interactive (list (magit-commit-at-point)
252                      (magit-commit-arguments)))
253   (magit-commit-squash-internal "--squash" commit args))
254
255 ;;;###autoload
256 (defun magit-commit-augment (&optional commit args)
257   "Create a squash commit, editing the squash message.
258
259 With a prefix argument the target COMMIT has to be confirmed.
260 Otherwise the commit at point may be used without confirmation
261 depending on the value of option `magit-commit-squash-confirm'."
262   (interactive (list (magit-commit-at-point)
263                      (magit-commit-arguments)))
264   (magit-commit-squash-internal "--squash" commit args nil t))
265
266 ;;;###autoload
267 (defun magit-commit-instant-fixup (&optional commit args)
268   "Create a fixup commit targeting COMMIT and instantly rebase."
269   (interactive (list (magit-commit-at-point)
270                      (magit-commit-arguments)))
271   (magit-commit-squash-internal "--fixup" commit args t))
272
273 ;;;###autoload
274 (defun magit-commit-instant-squash (&optional commit args)
275   "Create a squash commit targeting COMMIT and instantly rebase."
276   (interactive (list (magit-commit-at-point)
277                      (magit-commit-arguments)))
278   (magit-commit-squash-internal "--squash" commit args t))
279
280 (defun magit-commit-squash-internal
281     (option commit &optional args rebase edit confirmed)
282   (when-let ((args (magit-commit-assert args t)))
283     (when commit
284       (when (and rebase (not (magit-rev-ancestor-p commit "HEAD")))
285         (magit-read-char-case
286             (format "%s isn't an ancestor of HEAD.  " commit) nil
287           (?c "[c]reate without rebasing" (setq rebase nil))
288           (?s "[s]elect other"            (setq commit nil))
289           (?a "[a]bort"                   (user-error "Quit")))))
290     (when commit
291       (setq commit (magit-rebase-interactive-assert commit t)))
292     (if (and commit
293              (or confirmed
294                  (not (or rebase
295                           current-prefix-arg
296                           magit-commit-squash-confirm))))
297         (let ((magit-commit-show-diff nil))
298           (push (concat option "=" commit) args)
299           (unless edit
300             (push "--no-edit" args))
301           (if rebase
302               (magit-with-editor
303                 (magit-call-git
304                  "commit" "--no-gpg-sign"
305                  (-remove-first
306                   (apply-partially #'string-match-p "\\`--gpg-sign=")
307                   args)))
308             (magit-run-git-with-editor "commit" args))
309           t) ; The commit was created; used by below lambda.
310       (magit-log-select
311         (lambda (commit)
312           (when (and (magit-commit-squash-internal option commit args
313                                                    rebase edit t)
314                      rebase)
315             (magit-commit-amend-assert commit)
316             (magit-rebase-interactive-1 commit
317                 (list "--autosquash" "--autostash")
318               "" "true" nil t)))
319         (format "Type %%p on a commit to %s into it,"
320                 (substring option 2)))
321       (when magit-commit-show-diff
322         (let ((magit-display-buffer-noselect t))
323           (apply #'magit-diff-staged nil (magit-diff-arguments)))))))
324
325 (defun magit-commit-amend-assert (&optional commit)
326   (--when-let (magit-list-publishing-branches commit)
327     (let ((m1 "This commit has already been published to ")
328           (m2 ".\nDo you really want to modify it"))
329       (magit-confirm 'amend-published
330         (concat m1 "%s" m2)
331         (concat m1 "%i public branches" m2)
332         nil it))))
333
334 (defun magit-commit-assert (args &optional strict)
335   (cond
336    ((or (magit-anything-staged-p)
337         (and (magit-anything-unstaged-p)
338              ;; ^ Everything of nothing is still nothing.
339              (member "--all" args))
340         (and (not strict)
341              ;; ^ For amend variants that don't make sense otherwise.
342              (or (member "--amend" args)
343                  (member "--allow-empty" args))))
344     (or args (list "--")))
345    ((and (magit-rebase-in-progress-p)
346          (not (magit-anything-unstaged-p))
347          (y-or-n-p "Nothing staged.  Continue in-progress rebase? "))
348     (setq this-command 'magit-rebase-continue)
349     (magit-run-git-sequencer "rebase" "--continue")
350     nil)
351    ((and (file-exists-p (magit-git-dir "MERGE_MSG"))
352          (not (magit-anything-unstaged-p)))
353     (or args (list "--")))
354    ((not (magit-anything-unstaged-p))
355     (user-error "Nothing staged (or unstaged)"))
356    (magit-commit-ask-to-stage
357     (when (eq magit-commit-ask-to-stage 'verbose)
358       (magit-diff-unstaged))
359     (prog1 (when (y-or-n-p "Nothing staged.  Stage and commit all unstaged changes? ")
360              (magit-run-git "add" "-u" ".")
361              (or args (list "--")))
362       (when (and (eq magit-commit-ask-to-stage 'verbose)
363                  (derived-mode-p 'magit-diff-mode))
364         (magit-mode-bury-buffer))))
365    (t
366     (user-error "Nothing staged"))))
367
368 (defvar magit--reshelve-history nil)
369
370 ;;;###autoload
371 (defun magit-commit-reshelve (date)
372   "Change the committer date and possibly the author date of `HEAD'.
373
374 If you are the author of `HEAD', then both dates are changed,
375 otherwise only the committer date.  The current time is used
376 as the initial minibuffer input and the original author (if
377 that is you) or committer date is available as the previous
378 history element."
379   (interactive
380    (let ((author-p (magit-rev-author-p "HEAD")))
381      (push (magit-rev-format (if author-p "%ad" "%cd") "HEAD"
382                              (concat "--date=format:%F %T %z"))
383            magit--reshelve-history)
384      (list (read-string (if author-p
385                             "Change author and committer dates to: "
386                           "Change committer date to: ")
387                         (cons (format-time-string "%F %T %z") 17)
388                         'magit--reshelve-history))))
389   (let ((process-environment process-environment))
390     (push (concat "GIT_COMMITTER_DATE=" date) process-environment)
391     (magit-run-git "commit" "--amend" "--no-edit"
392                    (and (magit-rev-author-p "HEAD")
393                         (concat "--date=" date)))))
394
395 ;;;###autoload (autoload 'magit-commit-absorb-popup "magit-commit" nil t)
396 (magit-define-popup magit-commit-absorb-popup
397   "Spread unstaged changes across recent commits.
398 Without a prefix argument just call `magit-commit-absorb'.
399 With a prefix argument use a popup buffer to select arguments."
400   :man-page "git-bisect"
401   :options '((?c "Diff context lines" "--context=")
402              (?s "Strictness"         "--strict="))
403   :actions '((?x "Absorb" magit-commit-absorb))
404   :default-action 'magit-commit-absorb
405   :use-prefix 'popup)
406
407 (defun magit-commit-absorb (&optional commit args confirmed)
408   "Spread unstaged changes across recent commits.
409 This command requires the git-autofixup script, which is
410 available from https://github.com/torbiak/git-autofixup."
411   (interactive (list (magit-get-upstream-branch)
412                      (magit-commit-absorb-arguments)))
413   (unless (executable-find "git-autofixup")
414     (user-error "This command requires the git-autofixup script, which %s"
415                 "is available from https://github.com/torbiak/git-autofixup"))
416   (when (magit-anything-staged-p)
417     (user-error "Cannot absorb when there are staged changes"))
418   (unless (magit-anything-unstaged-p)
419     (user-error "There are no unstaged changes that could be absorbed"))
420   (when commit
421     (setq commit (magit-rebase-interactive-assert commit t)))
422   (if (and commit confirmed)
423       (progn (magit-run-git-async "autofixup" "-vv" args commit) t)
424     (magit-log-select
425       (lambda (commit)
426         (magit-commit-absorb commit args t))
427       nil nil nil nil commit)))
428
429 ;;; Pending Diff
430
431 (defun magit-commit-diff ()
432   (when (and git-commit-mode magit-commit-show-diff)
433     (when-let ((diff-buffer (magit-mode-get-buffer 'magit-diff-mode)))
434       ;; This window just started displaying the commit message
435       ;; buffer.  Without this that buffer would immediately be
436       ;; replaced with the diff buffer.  See #2632.
437       (unrecord-window-buffer nil diff-buffer))
438     (condition-case nil
439         (let ((args (car (magit-diff-arguments)))
440               (magit-inhibit-save-previous-winconf 'unset)
441               (magit-display-buffer-noselect t)
442               (inhibit-quit nil))
443           (message "Diffing changes to be committed (C-g to abort diffing)")
444           (if-let ((fn (cl-case last-command
445                          (magit-commit
446                           (apply-partially 'magit-diff-staged nil))
447                          (magit-commit-all
448                           (apply-partially 'magit-diff-working-tree nil))
449                          ((magit-commit-amend
450                            magit-commit-reword
451                            magit-rebase-reword-commit)
452                           'magit-diff-while-amending))))
453               (funcall fn args)
454             (if (magit-anything-staged-p)
455                 (magit-diff-staged nil args)
456               (magit-diff-while-amending args))))
457       (quit))))
458
459 ;; Mention `magit-diff-while-committing' because that's
460 ;; always what I search for when I try to find this line.
461 (add-hook 'server-switch-hook 'magit-commit-diff)
462
463 (add-to-list 'with-editor-server-window-alist
464              (cons git-commit-filename-regexp 'switch-to-buffer))
465
466 ;;; Message Utilities
467
468 (defun magit-commit-message-buffer ()
469   (let* ((find-file-visit-truename t) ; git uses truename of COMMIT_EDITMSG
470          (topdir (magit-toplevel)))
471     (--first (equal topdir (with-current-buffer it
472                              (and git-commit-mode (magit-toplevel))))
473              (append (buffer-list (selected-frame))
474                      (buffer-list)))))
475
476 (defvar magit-commit-add-log-insert-function 'magit-commit-add-log-insert
477   "Used by `magit-commit-add-log' to insert a single entry.")
478
479 (defun magit-commit-add-log ()
480   "Add a stub for the current change into the commit message buffer.
481 If no commit is in progress, then initiate it.  Use the function
482 specified by variable `magit-commit-add-log-insert-function' to
483 actually insert the entry."
484   (interactive)
485   (let ((hunk (and (magit-section-match 'hunk)
486                    (magit-current-section)))
487         (log  (magit-commit-message-buffer)) buf pos)
488     (save-window-excursion
489       (call-interactively #'magit-diff-visit-file)
490       (setq buf (current-buffer))
491       (setq pos (point)))
492     (unless log
493       (unless (magit-commit-assert nil)
494         (user-error "Abort"))
495       (magit-commit-create)
496       (while (not (setq log (magit-commit-message-buffer)))
497         (sit-for 0.01)))
498     (save-excursion
499       (with-current-buffer buf
500         (goto-char pos)
501         (funcall magit-commit-add-log-insert-function log
502                  (magit-file-relative-name)
503                  (and hunk (add-log-current-defun)))))))
504
505 (defun magit-commit-add-log-insert (buffer file defun)
506   (with-current-buffer buffer
507     (undo-boundary)
508     (goto-char (point-max))
509     (while (re-search-backward (concat "^" comment-start) nil t))
510     (save-restriction
511       (narrow-to-region (point-min) (point))
512       (cond ((re-search-backward (format "* %s\\(?: (\\([^)]+\\))\\)?: " file)
513                                  nil t)
514              (when (equal (match-string 1) defun)
515                (setq defun nil))
516              (re-search-forward ": "))
517             (t
518              (when (re-search-backward "^[\\*(].+\n" nil t)
519                (goto-char (match-end 0)))
520              (while (re-search-forward "^[^\\*\n].*\n" nil t))
521              (if defun
522                  (progn (insert (format "* %s (%s): \n" file defun))
523                         (setq defun nil))
524                (insert (format "* %s: \n" file)))
525              (backward-char)
526              (unless (looking-at "\n[\n\\']")
527                (insert ?\n)
528                (backward-char))))
529       (when defun
530         (forward-line)
531         (let ((limit (save-excursion
532                        (and (re-search-forward "^\\*" nil t)
533                             (point)))))
534           (unless (or (looking-back (format "(%s): " defun)
535                                     (line-beginning-position))
536                       (re-search-forward (format "^(%s): " defun) limit t))
537             (while (re-search-forward "^[^\\*\n].*\n" limit t))
538             (insert (format "(%s): \n" defun))
539             (backward-char)))))))
540
541 ;;; _
542 (provide 'magit-commit)
543 ;;; magit-commit.el ends here