magit-commit.el 21.8 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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
(defcustom magit-post-commit-hook nil
  "Hook run after creating a commit without the user editing a message.

This hook is run by `magit-refresh' if `this-command' is a member
of `magit-post-stage-hook-commands'.  This only includes commands
named `magit-commit-*' that do *not* require that the user edits
the commit message in a buffer and then finishes by pressing
\\<with-editor-mode-map>\\[with-editor-finish].

Also see `git-commit-post-finish-hook'."
  :package-version '(magit . "2.90.0")
  :group 'magit-commands
  :type 'hook)

(defvar magit-post-commit-hook-commands
  '(magit-commit-extend
    magit-commit-fixup
    magit-commit-augment
    magit-commit-instant-fixup
    magit-commit-instant-squash))

103
;;; Popup
104

105
(defun magit-commit-popup (&optional arg)
106
  "Popup console for commit commands."
107 108 109 110 111 112
  (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
113
  '(:variable magit-commit-arguments
114 115 116 117
    :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")
118
               (?h "Disable hooks"                          "--no-verify")
119 120
               (?s "Add Signed-off-by line"                 "--signoff")
               (?R "Claim authorship and reset author date" "--reset-author"))
121 122
    :options  ((?A "Override the author"  "--author=")
               (?S "Sign using gpg"       "--gpg-sign=" magit-read-gpg-secret-key)
123 124
               (?C "Reuse commit message" "--reuse-message="
                   magit-read-reuse-message))
125 126 127 128 129 130 131 132 133 134
    :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
135
    :default-action magit-commit))
136

137 138
(magit-define-popup-keys-deferred 'magit-commit-popup)

139 140 141 142
(defun magit-commit-arguments nil
  (if (eq magit-current-popup 'magit-commit-popup)
      magit-current-popup-args
    magit-commit-arguments))
143

144 145 146 147 148 149
(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)))
                             " "
150 151
                             (when-let ((id-obj (car (epg-key-user-id-list it))))
                               (let ((id-str (epg-user-id-string id-obj)))
152 153 154 155 156 157 158 159 160
                                 (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)))
                       " "))))

161 162 163 164 165 166 167
(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"))))

168 169
;;; Commands

170 171
;;;###autoload
(defun magit-commit (&optional args)
172 173
  "Create a new commit on `HEAD'.
With a prefix argument, amend to the commit at `HEAD' instead.
174 175
\n(git commit [--amend] ARGS)"
  (interactive (if current-prefix-arg
176 177
                   (list (cons "--amend" (magit-commit-arguments)))
                 (list (magit-commit-arguments))))
178 179
  (when (member "--all" args)
    (setq this-command 'magit-commit-all))
180
  (when (setq args (magit-commit-assert args))
181 182
    (let ((default-directory (magit-toplevel)))
      (magit-run-git-with-editor "commit" args))))
183 184 185 186 187

;;;###autoload
(defun magit-commit-amend (&optional args)
  "Amend the last commit.
\n(git commit --amend ARGS)"
188
  (interactive (list (magit-commit-arguments)))
189
  (magit-commit-amend-assert)
190 191 192 193 194
  (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.
195 196 197 198 199

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)"
200
  (interactive (list (magit-commit-arguments)
201
                     (if current-prefix-arg
202 203
                         (not magit-commit-extend-override-date)
                       magit-commit-extend-override-date)))
204
  (when (setq args (magit-commit-assert args (not 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 212 213 214
      (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.

215 216 217
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.
218 219 220 221

Non-interactively respect the optional OVERRIDE-DATE argument
and ignore the option.
\n(git commit --amend --only)"
222
  (interactive (list (magit-commit-arguments)
223 224 225
                     (if current-prefix-arg
                         (not magit-commit-reword-override-date)
                       magit-commit-reword-override-date)))
226
  (magit-commit-amend-assert)
227 228
  (let ((process-environment process-environment))
    (unless override-date
229
      (push (magit-rev-format "GIT_COMMITTER_DATE=%cD") process-environment))
230 231 232
    (magit-run-git-with-editor "commit" "--amend" "--only" args)))

;;;###autoload
233
(defun magit-commit-fixup (&optional commit args)
234
  "Create a fixup commit.
235 236

With a prefix argument the target COMMIT has to be confirmed.
237
Otherwise the commit at point may be used without confirmation
238
depending on the value of option `magit-commit-squash-confirm'."
239 240 241
  (interactive (list (magit-commit-at-point)
                     (magit-commit-arguments)))
  (magit-commit-squash-internal "--fixup" commit args))
242 243

;;;###autoload
244
(defun magit-commit-squash (&optional commit args)
245
  "Create a squash commit, without editing the squash message.
246 247

With a prefix argument the target COMMIT has to be confirmed.
248
Otherwise the commit at point may be used without confirmation
249
depending on the value of option `magit-commit-squash-confirm'."
250 251 252
  (interactive (list (magit-commit-at-point)
                     (magit-commit-arguments)))
  (magit-commit-squash-internal "--squash" commit args))
253

254
;;;###autoload
255
(defun magit-commit-augment (&optional commit args)
256
  "Create a squash commit, editing the squash message.
257 258

With a prefix argument the target COMMIT has to be confirmed.
259
Otherwise the commit at point may be used without confirmation
260
depending on the value of option `magit-commit-squash-confirm'."
261 262 263
  (interactive (list (magit-commit-at-point)
                     (magit-commit-arguments)))
  (magit-commit-squash-internal "--squash" commit args nil t))
264

265
;;;###autoload
266
(defun magit-commit-instant-fixup (&optional commit args)
267
  "Create a fixup commit targeting COMMIT and instantly rebase."
268 269 270
  (interactive (list (magit-commit-at-point)
                     (magit-commit-arguments)))
  (magit-commit-squash-internal "--fixup" commit args t))
271 272

;;;###autoload
273
(defun magit-commit-instant-squash (&optional commit args)
274
  "Create a squash commit targeting COMMIT and instantly rebase."
275 276 277
  (interactive (list (magit-commit-at-point)
                     (magit-commit-arguments)))
  (magit-commit-squash-internal "--squash" commit args t))
278

279 280
(defun magit-commit-squash-internal
    (option commit &optional args rebase edit confirmed)
281
  (when-let ((args (magit-commit-assert args t)))
282 283 284 285 286 287 288
    (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")))))
289
    (when commit
290
      (setq commit (magit-rebase-interactive-assert commit t)))
291 292 293 294 295
    (if (and commit
             (or confirmed
                 (not (or rebase
                          current-prefix-arg
                          magit-commit-squash-confirm))))
296
        (let ((magit-commit-show-diff nil))
297 298 299 300
          (push (concat option "=" commit) args)
          (unless edit
            (push "--no-edit" args))
          (if rebase
301 302
              (magit-with-editor
                (magit-call-git
303 304 305 306
                 "commit" "--no-gpg-sign"
                 (-remove-first
                  (apply-partially #'string-match-p "\\`--gpg-sign=")
                  args)))
307 308
            (magit-run-git-with-editor "commit" args))
          t) ; The commit was created; used by below lambda.
309
      (magit-log-select
310 311 312 313
        (lambda (commit)
          (when (and (magit-commit-squash-internal option commit args
                                                   rebase edit t)
                     rebase)
314
            (magit-commit-amend-assert commit)
315 316
            (magit-rebase-interactive-1 commit
                (list "--autosquash" "--autostash")
317
              "" "true" nil t)))
318
        (format "Type %%p on a commit to %s into it,"
319
                (substring option 2)))
320
      (when magit-commit-show-diff
321
        (let ((magit-display-buffer-noselect t))
322
          (apply #'magit-diff-staged nil (magit-diff-arguments)))))))
323

324 325 326 327
(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"))
328
      (magit-confirm 'amend-published
329 330 331 332
        (concat m1 "%s" m2)
        (concat m1 "%i public branches" m2)
        nil it))))

333 334 335 336 337 338 339 340 341 342 343 344
(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)
345
         (not (magit-anything-unstaged-p))
346
         (y-or-n-p "Nothing staged.  Continue in-progress rebase? "))
347
    (magit-run-git-sequencer "rebase" "--continue")
348 349 350 351
    nil)
   ((and (file-exists-p (magit-git-dir "MERGE_MSG"))
         (not (magit-anything-unstaged-p)))
    (or args (list "--")))
352 353
   ((not (magit-anything-unstaged-p))
    (user-error "Nothing staged (or unstaged)"))
354
   (magit-commit-ask-to-stage
355
    (when (eq magit-commit-ask-to-stage 'verbose)
356
      (magit-diff-unstaged))
357
    (prog1 (when (y-or-n-p "Nothing staged.  Stage and commit all unstaged changes? ")
358 359
             (magit-run-git "add" "-u" ".")
             (or args (list "--")))
360
      (when (and (eq magit-commit-ask-to-stage 'verbose)
361
                 (derived-mode-p 'magit-diff-mode))
362
        (magit-mode-bury-buffer))))
363 364 365
   (t
    (user-error "Nothing staged"))))

Jon's avatar
Jon committed
366
(defvar magit--reshelve-history nil)
367 368 369 370 371 372 373 374 375 376 377 378 379 380

;;;###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
381
           magit--reshelve-history)
382 383 384 385
     (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
386
                        'magit--reshelve-history))))
387 388 389 390 391 392
  (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)))))

393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
;;;###autoload (autoload 'magit-commit-absorb-popup "magit-commit" nil t)
(magit-define-popup magit-commit-absorb-popup
  "Spread unstaged changes across recent commits.
Without a prefix argument just call `magit-commit-absorb'.
With a prefix argument use a popup buffer to select arguments."
  :man-page "git-bisect"
  :options '((?c "Diff context lines" "--context=")
             (?s "Strictness"         "--strict="))
  :actions '((?x "Absorb" magit-commit-absorb))
  :default-action 'magit-commit-absorb
  :use-prefix 'popup)

(defun magit-commit-absorb (&optional commit args confirmed)
  "Spread unstaged changes across recent commits.
This command requires the git-autofixup script, which is
available from https://github.com/torbiak/git-autofixup."
  (interactive (list (magit-get-upstream-branch)
                     (magit-commit-absorb-arguments)))
  (unless (executable-find "git-autofixup")
    (user-error "This command requires the git-autofixup script, which %s"
                "is available from https://github.com/torbiak/git-autofixup"))
  (when (magit-anything-staged-p)
    (user-error "Cannot absorb when there are staged changes"))
  (unless (magit-anything-unstaged-p)
    (user-error "There are no unstaged changes that could be absorbed"))
  (when commit
    (setq commit (magit-rebase-interactive-assert commit t)))
  (if (and commit confirmed)
      (progn (magit-run-git-async "autofixup" "-vv" args commit) t)
    (magit-log-select
      (lambda (commit)
        (magit-commit-absorb commit args t))
      nil nil nil nil commit)))

427 428
;;; Pending Diff

429
(defun magit-commit-diff ()
430
  (when (and git-commit-mode magit-commit-show-diff)
431
    (when-let ((diff-buffer (magit-mode-get-buffer 'magit-diff-mode)))
432 433 434 435
      ;; 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))
436
    (condition-case nil
437 438
        (let ((args (car (magit-diff-arguments)))
              (magit-inhibit-save-previous-winconf 'unset)
439 440 441
              (magit-display-buffer-noselect t)
              (inhibit-quit nil))
          (message "Diffing changes to be committed (C-g to abort diffing)")
442 443 444 445 446 447 448 449 450
          (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))))
451 452 453 454
              (funcall fn args)
            (if (magit-anything-staged-p)
                (magit-diff-staged nil args)
              (magit-diff-while-amending args))))
455
      (quit))))
456

457 458
;; Mention `magit-diff-while-committing' because that's
;; always what I search for when I try to find this line.
459 460 461 462 463
(add-hook 'server-switch-hook 'magit-commit-diff)

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

464 465
;;; Message Utilities

466 467 468 469 470 471 472 473
(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)))))

474 475
(defvar magit-commit-add-log-insert-function 'magit-commit-add-log-insert
  "Used by `magit-commit-add-log' to insert a single entry.")
476 477

(defun magit-commit-add-log ()
478
  "Add a stub for the current change into the commit message buffer.
479 480 481 482
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)
483 484 485
  (let ((hunk (and (magit-section-match 'hunk)
                   (magit-current-section)))
        (log  (magit-commit-message-buffer)) buf pos)
486
    (save-window-excursion
487
      (call-interactively #'magit-diff-visit-file)
488 489
      (setq buf (current-buffer))
      (setq pos (point)))
490 491 492 493 494 495 496 497 498 499
    (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
500
                 (magit-file-relative-name)
501
                 (and hunk (add-log-current-defun)))))))
502 503 504

(defun magit-commit-add-log-insert (buffer file defun)
  (with-current-buffer buffer
505 506 507
    (undo-boundary)
    (goto-char (point-max))
    (while (re-search-backward (concat "^" comment-start) nil t))
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537
    (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)))))))
538 539 540

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