magit-commit.el 18 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-2017  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 40 41 42 43 44 45
;;; Code:

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

(eval-when-compile (require 'epa)) ; for `epa-protocol'
(eval-when-compile (require 'epg))
(declare-function epg-sub-key-id 'epg)
(declare-function epg-key-sub-key-list 'epg)
(declare-function epg-key-user-id-list 'epg)
(declare-function epg-user-id-string 'epg)
(declare-function epg-decode-dn 'epg)
(declare-function epg-list-keys 'epg)

;;; Options

46 47
(defcustom magit-commit-arguments nil
  "The arguments used when committing."
48
  :group 'magit-git-arguments
49 50
  :type '(repeat (string :tag "Argument")))

51
(defcustom magit-commit-ask-to-stage 'verbose
52
  "Whether to ask to stage everything when committing and nothing is staged."
53 54 55 56 57 58 59 60 61
  :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")
62 63 64
  :group 'magit-commands
  :type 'boolean)

65
(defcustom magit-commit-extend-override-date t
66
  "Whether using `magit-commit-extend' changes the committer date."
67
  :package-version '(magit . "2.3.0")
68 69 70
  :group 'magit-commands
  :type 'boolean)

71
(defcustom magit-commit-reword-override-date t
72
  "Whether using `magit-commit-reword' changes the committer date."
73
  :package-version '(magit . "2.3.0")
74 75 76 77 78 79 80 81 82 83 84 85 86 87
  :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)

88
;;; Popup
89

90
(defun magit-commit-popup (&optional arg)
91
  "Popup console for commit commands."
92 93 94 95 96 97
  (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
98
  '(:variable magit-commit-arguments
99 100 101 102
    :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")
103
               (?h "Disable hooks"                          "--no-verify")
104 105
               (?s "Add Signed-off-by line"                 "--signoff")
               (?R "Claim authorship and reset author date" "--reset-author"))
106 107
    :options  ((?A "Override the author"  "--author=")
               (?S "Sign using gpg"       "--gpg-sign=" magit-read-gpg-secret-key)
108 109
               (?C "Reuse commit message" "--reuse-message="
                   magit-read-reuse-message))
110 111 112 113 114 115 116 117 118 119
    :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
120
    :default-action magit-commit))
121

122 123
(magit-define-popup-keys-deferred 'magit-commit-popup)

124 125 126 127
(defun magit-commit-arguments nil
  (if (eq magit-current-popup 'magit-commit-popup)
      magit-current-popup-args
    magit-commit-arguments))
128

129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
(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)))
                             " "
                             (-when-let (id-obj (car (epg-key-user-id-list it)))
                               (let    ((id-str (epg-user-id-string id-obj)))
                                 (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)))
                       " "))))

146 147 148 149 150 151 152
(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"))))

153 154
;;; Commands

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

;;;###autoload
(defun magit-commit-amend (&optional args)
  "Amend the last commit.
\n(git commit --amend ARGS)"
173
  (interactive (list (magit-commit-arguments)))
174 175 176 177 178
  (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.
179 180 181 182 183

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)"
184
  (interactive (list (magit-commit-arguments)
185
                     (if current-prefix-arg
186 187
                         (not magit-commit-extend-override-date)
                       magit-commit-extend-override-date)))
188 189 190
  (when (setq args (magit-commit-assert args (not override-date)))
    (let ((process-environment process-environment))
      (unless override-date
191
        (push (magit-rev-format "GIT_COMMITTER_DATE=%cD") process-environment))
192 193 194 195 196 197
      (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.

198 199 200
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.
201 202 203 204

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

;;;###autoload
215
(defun magit-commit-fixup (&optional commit args)
216
  "Create a fixup commit.
217 218

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

;;;###autoload
226
(defun magit-commit-squash (&optional commit args)
227
  "Create a squash commit, without editing the squash message.
228 229

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

236
;;;###autoload
237
(defun magit-commit-augment (&optional commit args)
238
  "Create a squash commit, editing the squash message.
239 240

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

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

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

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

(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)
318
         (not (magit-anything-unstaged-p))
319
         (y-or-n-p "Nothing staged.  Continue in-progress rebase? "))
320
    (magit-run-git-sequencer "rebase" "--continue")
321 322 323 324
    nil)
   ((and (file-exists-p (magit-git-dir "MERGE_MSG"))
         (not (magit-anything-unstaged-p)))
    (or args (list "--")))
325 326
   ((not (magit-anything-unstaged-p))
    (user-error "Nothing staged (or unstaged)"))
327
   (magit-commit-ask-to-stage
328
    (when (eq magit-commit-ask-to-stage 'verbose)
329 330 331 332
      (magit-diff-unstaged))
    (prog1 (when (y-or-n-p "Nothing staged.  Stage and commit everything? ")
             (magit-run-git "add" "-u" ".")
             (or args (list "--")))
333
      (when (and (eq magit-commit-ask-to-stage 'verbose)
334
                 (derived-mode-p 'magit-diff-mode))
335
        (magit-mode-bury-buffer))))
336 337 338
   (t
    (user-error "Nothing staged"))))

339 340
;;; Pending Diff

341
(defun magit-commit-diff ()
342
  (when (and git-commit-mode magit-commit-show-diff)
343 344 345 346 347
    (-when-let (diff-buffer (magit-mode-get-buffer 'magit-diff-mode))
      ;; 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))
348
    (condition-case nil
349 350
        (let ((args (car (magit-diff-arguments)))
              (magit-inhibit-save-previous-winconf 'unset)
351 352 353
              (magit-display-buffer-noselect t)
              (inhibit-quit nil))
          (message "Diffing changes to be committed (C-g to abort diffing)")
354 355 356 357 358 359 360 361 362 363 364 365 366
          (-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)))
              (funcall fn args)
            (if (magit-anything-staged-p)
                (magit-diff-staged nil args)
              (magit-diff-while-amending args))))
367
      (quit))))
368

369 370
;; Mention `magit-diff-while-committing' because that's
;; always what I search for when I try to find this line.
371 372 373 374 375
(add-hook 'server-switch-hook 'magit-commit-diff)

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

376 377
;;; Message Utilities

378 379 380 381 382 383 384 385
(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)))))

386 387
(defvar magit-commit-add-log-insert-function 'magit-commit-add-log-insert
  "Used by `magit-commit-add-log' to insert a single entry.")
388 389

(defun magit-commit-add-log ()
390
  "Add a stub for the current change into the commit message buffer.
391 392 393 394
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)
395 396
  (let ((hunk (magit-section-when 'hunk it))
        (log (magit-commit-message-buffer)) buf pos)
397
    (save-window-excursion
398
      (call-interactively #'magit-diff-visit-file)
399 400
      (setq buf (current-buffer))
      (setq pos (point)))
401 402 403 404 405 406 407 408 409 410
    (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
411
                 (magit-file-relative-name)
412
                 (and hunk (add-log-current-defun)))))))
413 414 415

(defun magit-commit-add-log-insert (buffer file defun)
  (with-current-buffer buffer
416 417 418 419 420 421 422 423
    (undo-boundary)
    (goto-char (point-max))
    (while (re-search-backward (concat "^" comment-start) nil t))
    (cond ((re-search-backward (format "* %s\\(?: (\\([^)]+\\))\\)?: " file)
                               nil t)
           (when (equal (match-string 1) defun)
             (setq defun nil))
           (re-search-forward ": "))
424
          (t
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
           (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))))))
447 448 449

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