magit-commit.el 19.5 KB
Newer Older
1
;;; magit-commit.el --- create Git commits  -*- lexical-binding: t -*-
2

Jonas Bernoulli's avatar
Jonas Bernoulli committed
3
;; Copyright (C) 2008-2018  The Magit Project Contributors
4
;;
5 6
;; You should have received a copy of the AUTHORS.md file which
;; lists all contributors.  If not, see http://magit.vc/authors.
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>

;; Magit is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; Magit is distributed in the hope that it will be useful, but WITHOUT
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
;; License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with Magit.  If not, see http://www.gnu.org/licenses.

24 25 26 27 28 29
;;; Commentary:

;; This library implements commands for creating Git commits.  These
;; commands just initiate the commit, support for writing the commit
;; messages is implemented in `git-commit.el'.

30 31 32 33 34 35 36 37 38 39
;;; Code:

(require 'magit)
(require 'magit-sequence)

(eval-when-compile (require 'epa)) ; for `epa-protocol'
(eval-when-compile (require 'epg))

;;; Options

40 41
(defcustom magit-commit-arguments nil
  "The arguments used when committing."
42
  :group 'magit-git-arguments
43 44
  :type '(repeat (string :tag "Argument")))

45
(defcustom magit-commit-ask-to-stage 'verbose
46
  "Whether to ask to stage all unstaged changes when committing and nothing is staged."
47 48 49 50 51 52 53 54 55
  :package-version '(magit . "2.3.0")
  :group 'magit-commands
  :type '(choice (const :tag "Ask showing diff" verbose)
                 (const :tag "Ask" t)
                 (const :tag "Don't ask" nil)))

(defcustom magit-commit-show-diff t
  "Whether the relevant diff is automatically shown when committing."
  :package-version '(magit . "2.3.0")
56 57 58
  :group 'magit-commands
  :type 'boolean)

59
(defcustom magit-commit-extend-override-date t
60
  "Whether using `magit-commit-extend' changes the committer date."
61
  :package-version '(magit . "2.3.0")
62 63 64
  :group 'magit-commands
  :type 'boolean)

65
(defcustom magit-commit-reword-override-date t
66
  "Whether using `magit-commit-reword' changes the committer date."
67
  :package-version '(magit . "2.3.0")
68 69 70 71 72 73 74 75 76 77 78 79 80 81
  :group 'magit-commands
  :type 'boolean)

(defcustom magit-commit-squash-confirm t
  "Whether the commit targeted by squash and fixup has to be confirmed.
When non-nil then the commit at point (if any) is used as default
choice, otherwise it has to be confirmed.  This option only
affects `magit-commit-squash' and `magit-commit-fixup'.  The
\"instant\" variants always require confirmation because making
an error while using those is harder to recover from."
  :package-version '(magit . "2.1.0")
  :group 'magit-commands
  :type 'boolean)

82
;;; Popup
83

84
(defun magit-commit-popup (&optional arg)
85
  "Popup console for commit commands."
86 87 88 89 90 91
  (interactive "P")
  (--if-let (magit-commit-message-buffer)
      (switch-to-buffer it)
    (magit-invoke-popup 'magit-commit-popup nil arg)))

(defvar magit-commit-popup
Kyle Meyer's avatar
Kyle Meyer committed
92
  '(:variable magit-commit-arguments
93 94 95 96
    :man-page "git-commit"
    :switches ((?a "Stage all modified and deleted files"   "--all")
               (?e "Allow empty commit"                     "--allow-empty")
               (?v "Show diff of changes to be committed"   "--verbose")
97
               (?h "Disable hooks"                          "--no-verify")
98 99
               (?s "Add Signed-off-by line"                 "--signoff")
               (?R "Claim authorship and reset author date" "--reset-author"))
100 101
    :options  ((?A "Override the author"  "--author=")
               (?S "Sign using gpg"       "--gpg-sign=" magit-read-gpg-secret-key)
102 103
               (?C "Reuse commit message" "--reuse-message="
                   magit-read-reuse-message))
104 105 106 107 108 109 110 111 112 113
    :actions  ((?c "Commit"         magit-commit)
               (?e "Extend"         magit-commit-extend)
               (?f "Fixup"          magit-commit-fixup)
               (?F "Instant Fixup"  magit-commit-instant-fixup) nil
               (?w "Reword"         magit-commit-reword)
               (?s "Squash"         magit-commit-squash)
               (?S "Instant Squash" magit-commit-instant-squash) nil
               (?a "Amend"          magit-commit-amend)
               (?A "Augment"        magit-commit-augment))
    :max-action-columns 4
114
    :default-action magit-commit))
115

116 117
(magit-define-popup-keys-deferred 'magit-commit-popup)

118 119 120 121
(defun magit-commit-arguments nil
  (if (eq magit-current-popup 'magit-commit-popup)
      magit-current-popup-args
    magit-commit-arguments))
122

123 124 125 126 127 128
(defvar magit-gpg-secret-key-hist nil)

(defun magit-read-gpg-secret-key (prompt &optional _initial-input)
  (require 'epa)
  (let ((keys (--map (concat (epg-sub-key-id (car (epg-key-sub-key-list it)))
                             " "
129 130
                             (when-let ((id-obj (car (epg-key-user-id-list it))))
                               (let ((id-str (epg-user-id-string id-obj)))
131 132 133 134 135 136 137 138 139
                                 (if (stringp id-str)
                                     id-str
                                   (epg-decode-dn id-obj)))))
                     (epg-list-keys (epg-make-context epa-protocol) nil t))))
    (car (split-string (magit-completing-read
                        prompt keys nil nil nil 'magit-gpg-secret-key-hist
                        (car (or magit-gpg-secret-key-hist keys)))
                       " "))))

140 141 142 143 144 145 146
(defun magit-read-reuse-message (prompt &optional default)
  (magit-completing-read prompt (magit-list-refnames)
                         nil nil nil 'magit-revision-history
                         (or default
                             (and (magit-rev-verify "ORIG_HEAD")
                                  "ORIG_HEAD"))))

147 148
;;; Commands

149 150
;;;###autoload
(defun magit-commit (&optional args)
151 152
  "Create a new commit on `HEAD'.
With a prefix argument, amend to the commit at `HEAD' instead.
153 154
\n(git commit [--amend] ARGS)"
  (interactive (if current-prefix-arg
155 156
                   (list (cons "--amend" (magit-commit-arguments)))
                 (list (magit-commit-arguments))))
157 158
  (when (member "--all" args)
    (setq this-command 'magit-commit-all))
159
  (when (setq args (magit-commit-assert args))
160 161
    (let ((default-directory (magit-toplevel)))
      (magit-run-git-with-editor "commit" args))))
162 163 164 165 166

;;;###autoload
(defun magit-commit-amend (&optional args)
  "Amend the last commit.
\n(git commit --amend ARGS)"
167
  (interactive (list (magit-commit-arguments)))
168
  (magit-commit-amend-assert)
169 170 171 172 173
  (magit-run-git-with-editor "commit" "--amend" args))

;;;###autoload
(defun magit-commit-extend (&optional args override-date)
  "Amend the last commit, without editing the message.
174 175 176 177 178

With a prefix argument keep the committer date, otherwise change
it.  The option `magit-commit-extend-override-date' can be used
to inverse the meaning of the prefix argument.  \n(git commit
--amend --no-edit)"
179
  (interactive (list (magit-commit-arguments)
180
                     (if current-prefix-arg
181 182
                         (not magit-commit-extend-override-date)
                       magit-commit-extend-override-date)))
183
  (when (setq args (magit-commit-assert args (not override-date)))
184
    (magit-commit-amend-assert)
185 186
    (let ((process-environment process-environment))
      (unless override-date
187
        (push (magit-rev-format "GIT_COMMITTER_DATE=%cD") process-environment))
188 189 190 191 192 193
      (magit-run-git-with-editor "commit" "--amend" "--no-edit" args))))

;;;###autoload
(defun magit-commit-reword (&optional args override-date)
  "Reword the last commit, ignoring staged changes.

194 195 196
With a prefix argument keep the committer date, otherwise change
it.  The option `magit-commit-reword-override-date' can be used
to inverse the meaning of the prefix argument.
197 198 199 200

Non-interactively respect the optional OVERRIDE-DATE argument
and ignore the option.
\n(git commit --amend --only)"
201
  (interactive (list (magit-commit-arguments)
202 203 204
                     (if current-prefix-arg
                         (not magit-commit-reword-override-date)
                       magit-commit-reword-override-date)))
205
  (magit-commit-amend-assert)
206 207
  (let ((process-environment process-environment))
    (unless override-date
208
      (push (magit-rev-format "GIT_COMMITTER_DATE=%cD") process-environment))
209 210 211
    (magit-run-git-with-editor "commit" "--amend" "--only" args)))

;;;###autoload
212
(defun magit-commit-fixup (&optional commit args)
213
  "Create a fixup commit.
214 215

With a prefix argument the target COMMIT has to be confirmed.
216
Otherwise the commit at point may be used without confirmation
217
depending on the value of option `magit-commit-squash-confirm'."
218 219 220
  (interactive (list (magit-commit-at-point)
                     (magit-commit-arguments)))
  (magit-commit-squash-internal "--fixup" commit args))
221 222

;;;###autoload
223
(defun magit-commit-squash (&optional commit args)
224
  "Create a squash commit, without editing the squash message.
225 226

With a prefix argument the target COMMIT has to be confirmed.
227
Otherwise the commit at point may be used without confirmation
228
depending on the value of option `magit-commit-squash-confirm'."
229 230 231
  (interactive (list (magit-commit-at-point)
                     (magit-commit-arguments)))
  (magit-commit-squash-internal "--squash" commit args))
232

233
;;;###autoload
234
(defun magit-commit-augment (&optional commit args)
235
  "Create a squash commit, editing the squash message.
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 241 242
  (interactive (list (magit-commit-at-point)
                     (magit-commit-arguments)))
  (magit-commit-squash-internal "--squash" commit args nil t))
243

244
;;;###autoload
245
(defun magit-commit-instant-fixup (&optional commit args)
246
  "Create a fixup commit targeting COMMIT and instantly rebase."
247 248 249
  (interactive (list (magit-commit-at-point)
                     (magit-commit-arguments)))
  (magit-commit-squash-internal "--fixup" commit args t))
250 251

;;;###autoload
252
(defun magit-commit-instant-squash (&optional commit args)
253
  "Create a squash commit targeting COMMIT and instantly rebase."
254 255 256
  (interactive (list (magit-commit-at-point)
                     (magit-commit-arguments)))
  (magit-commit-squash-internal "--squash" commit args t))
257

258 259
(defun magit-commit-squash-internal
    (option commit &optional args rebase edit confirmed)
260
  (when-let ((args (magit-commit-assert args t)))
261 262 263 264 265 266 267
    (when commit
      (when (and rebase (not (magit-rev-ancestor-p commit "HEAD")))
        (magit-read-char-case
            (format "%s isn't an ancestor of HEAD.  " commit) nil
          (?c "[c]reate without rebasing" (setq rebase nil))
          (?s "[s]elect other"            (setq commit nil))
          (?a "[a]bort"                   (user-error "Quit")))))
268
    (when commit
269
      (setq commit (magit-rebase-interactive-assert commit t)))
270 271 272 273 274
    (if (and commit
             (or confirmed
                 (not (or rebase
                          current-prefix-arg
                          magit-commit-squash-confirm))))
275
        (let ((magit-commit-show-diff nil))
276 277 278 279
          (push (concat option "=" commit) args)
          (unless edit
            (push "--no-edit" args))
          (if rebase
280 281
              (magit-with-editor
                (magit-call-git
282 283 284 285
                 "commit" "--no-gpg-sign"
                 (-remove-first
                  (apply-partially #'string-match-p "\\`--gpg-sign=")
                  args)))
286 287
            (magit-run-git-with-editor "commit" args))
          t) ; The commit was created; used by below lambda.
288
      (magit-log-select
289 290 291 292
        (lambda (commit)
          (when (and (magit-commit-squash-internal option commit args
                                                   rebase edit t)
                     rebase)
293
            (magit-commit-amend-assert commit)
294 295
            (magit-rebase-interactive-1 commit
                (list "--autosquash" "--autostash")
296
              "" "true" nil t)))
297
        (format "Type %%p on a commit to %s into it,"
298 299
                (substring option 2))
        nil nil (list "--graph"))
300
      (when magit-commit-show-diff
301
        (let ((magit-display-buffer-noselect t))
302
          (apply #'magit-diff-staged nil (magit-diff-arguments)))))))
303

304 305 306 307
(defun magit-commit-amend-assert (&optional commit)
  (--when-let (magit-list-publishing-branches commit)
    (let ((m1 "This commit has already been published to ")
          (m2 ".\nDo you really want to modify it"))
308
      (magit-confirm 'amend-published
309 310 311 312
        (concat m1 "%s" m2)
        (concat m1 "%i public branches" m2)
        nil it))))

313 314 315 316 317 318 319 320 321 322 323 324
(defun magit-commit-assert (args &optional strict)
  (cond
   ((or (magit-anything-staged-p)
        (and (magit-anything-unstaged-p)
             ;; ^ Everything of nothing is still nothing.
             (member "--all" args))
        (and (not strict)
             ;; ^ For amend variants that don't make sense otherwise.
             (or (member "--amend" args)
                 (member "--allow-empty" args))))
    (or args (list "--")))
   ((and (magit-rebase-in-progress-p)
325
         (not (magit-anything-unstaged-p))
326
         (y-or-n-p "Nothing staged.  Continue in-progress rebase? "))
327
    (magit-run-git-sequencer "rebase" "--continue")
328 329 330 331
    nil)
   ((and (file-exists-p (magit-git-dir "MERGE_MSG"))
         (not (magit-anything-unstaged-p)))
    (or args (list "--")))
332 333
   ((not (magit-anything-unstaged-p))
    (user-error "Nothing staged (or unstaged)"))
334
   (magit-commit-ask-to-stage
335
    (when (eq magit-commit-ask-to-stage 'verbose)
336
      (magit-diff-unstaged))
337
    (prog1 (when (y-or-n-p "Nothing staged.  Stage and commit all unstaged changes? ")
338 339
             (magit-run-git "add" "-u" ".")
             (or args (list "--")))
340
      (when (and (eq magit-commit-ask-to-stage 'verbose)
341
                 (derived-mode-p 'magit-diff-mode))
342
        (magit-mode-bury-buffer))))
343 344 345
   (t
    (user-error "Nothing staged"))))

Jon's avatar
Jon committed
346
(defvar magit--reshelve-history nil)
347 348 349 350 351 352 353 354 355 356 357 358 359 360

;;;###autoload
(defun magit-commit-reshelve (date)
  "Change the committer date and possibly the author date of `HEAD'.

If you are the author of `HEAD', then both dates are changed,
otherwise only the committer date.  The current time is used
as the initial minibuffer input and the original author (if
that is you) or committer date is available as the previous
history element."
  (interactive
   (let ((author-p (magit-rev-author-p "HEAD")))
     (push (magit-rev-format (if author-p "%ad" "%cd") "HEAD"
                             (concat "--date=format:%F %T %z"))
Jon's avatar
Jon committed
361
           magit--reshelve-history)
362 363 364 365
     (list (read-string (if author-p
                            "Change author and committer dates to: "
                          "Change committer date to: ")
                        (cons (format-time-string "%F %T %z") 17)
Jon's avatar
Jon committed
366
                        'magit--reshelve-history))))
367 368 369 370 371 372
  (let ((process-environment process-environment))
    (push (concat "GIT_COMMITTER_DATE=" date) process-environment)
    (magit-run-git "commit" "--amend" "--no-edit"
                   (and (magit-rev-author-p "HEAD")
                        (concat "--date=" date)))))

373 374
;;; Pending Diff

375
(defun magit-commit-diff ()
376
  (when (and git-commit-mode magit-commit-show-diff)
377
    (when-let ((diff-buffer (magit-mode-get-buffer 'magit-diff-mode)))
378 379 380 381
      ;; This window just started displaying the commit message
      ;; buffer.  Without this that buffer would immediately be
      ;; replaced with the diff buffer.  See #2632.
      (unrecord-window-buffer nil diff-buffer))
382
    (condition-case nil
383 384
        (let ((args (car (magit-diff-arguments)))
              (magit-inhibit-save-previous-winconf 'unset)
385 386 387
              (magit-display-buffer-noselect t)
              (inhibit-quit nil))
          (message "Diffing changes to be committed (C-g to abort diffing)")
388 389 390 391 392 393 394 395 396
          (if-let ((fn (cl-case last-command
                         (magit-commit
                          (apply-partially 'magit-diff-staged nil))
                         (magit-commit-all
                          (apply-partially 'magit-diff-working-tree nil))
                         ((magit-commit-amend
                           magit-commit-reword
                           magit-rebase-reword-commit)
                          'magit-diff-while-amending))))
397 398 399 400
              (funcall fn args)
            (if (magit-anything-staged-p)
                (magit-diff-staged nil args)
              (magit-diff-while-amending args))))
401
      (quit))))
402

403 404
;; Mention `magit-diff-while-committing' because that's
;; always what I search for when I try to find this line.
405 406 407 408 409
(add-hook 'server-switch-hook 'magit-commit-diff)

(add-to-list 'with-editor-server-window-alist
             (cons git-commit-filename-regexp 'switch-to-buffer))

410 411
;;; Message Utilities

412 413 414 415 416 417 418 419
(defun magit-commit-message-buffer ()
  (let* ((find-file-visit-truename t) ; git uses truename of COMMIT_EDITMSG
         (topdir (magit-toplevel)))
    (--first (equal topdir (with-current-buffer it
                             (and git-commit-mode (magit-toplevel))))
             (append (buffer-list (selected-frame))
                     (buffer-list)))))

420 421
(defvar magit-commit-add-log-insert-function 'magit-commit-add-log-insert
  "Used by `magit-commit-add-log' to insert a single entry.")
422 423

(defun magit-commit-add-log ()
424
  "Add a stub for the current change into the commit message buffer.
425 426 427 428
If no commit is in progress, then initiate it.  Use the function
specified by variable `magit-commit-add-log-insert-function' to
actually insert the entry."
  (interactive)
429 430
  (let ((hunk (magit-section-when 'hunk it))
        (log (magit-commit-message-buffer)) buf pos)
431
    (save-window-excursion
432
      (call-interactively #'magit-diff-visit-file)
433 434
      (setq buf (current-buffer))
      (setq pos (point)))
435 436 437 438 439 440 441 442 443 444
    (unless log
      (unless (magit-commit-assert nil)
        (user-error "Abort"))
      (magit-commit)
      (while (not (setq log (magit-commit-message-buffer)))
        (sit-for 0.01)))
    (save-excursion
      (with-current-buffer buf
        (goto-char pos)
        (funcall magit-commit-add-log-insert-function log
445
                 (magit-file-relative-name)
446
                 (and hunk (add-log-current-defun)))))))
447 448 449

(defun magit-commit-add-log-insert (buffer file defun)
  (with-current-buffer buffer
450 451 452
    (undo-boundary)
    (goto-char (point-max))
    (while (re-search-backward (concat "^" comment-start) nil t))
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
    (save-restriction
      (narrow-to-region (point-min) (point))
      (cond ((re-search-backward (format "* %s\\(?: (\\([^)]+\\))\\)?: " file)
                                 nil t)
             (when (equal (match-string 1) defun)
               (setq defun nil))
             (re-search-forward ": "))
            (t
             (when (re-search-backward "^[\\*(].+\n" nil t)
               (goto-char (match-end 0)))
             (while (re-search-forward "^[^\\*\n].*\n" nil t))
             (if defun
                 (progn (insert (format "* %s (%s): \n" file defun))
                        (setq defun nil))
               (insert (format "* %s: \n" file)))
             (backward-char)
             (unless (looking-at "\n[\n\\']")
               (insert ?\n)
               (backward-char))))
      (when defun
        (forward-line)
        (let ((limit (save-excursion
                       (and (re-search-forward "^\\*" nil t)
                            (point)))))
          (unless (or (looking-back (format "(%s): " defun)
                                    (line-beginning-position))
                      (re-search-forward (format "^(%s): " defun) limit t))
            (while (re-search-forward "^[^\\*\n].*\n" limit t))
            (insert (format "(%s): \n" defun))
            (backward-char)))))))
483 484 485

(provide 'magit-commit)
;;; magit-commit.el ends here