fordon_greeman / voidfiles

Branch: 2079ec5
Switch branches/tags
main
theidioticdev add emacs 2079ec5 1d ago
878 lines · 35.4 KB Raw Blame History
1 ;;; magit-commit.el --- Create Git commits -*- lexical-binding:t -*-
2
3 ;; Copyright (C) 2008-2026 The Magit Project Contributors
4
5 ;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
6 ;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev>
7
8 ;; SPDX-License-Identifier: GPL-3.0-or-later
9
10 ;; Magit is free software: you can redistribute it and/or modify it
11 ;; under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation, either version 3 of the License, or
13 ;; (at your option) any later version.
14 ;;
15 ;; Magit is distributed in the hope that it will be useful, but WITHOUT
16 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
18 ;; License for more details.
19 ;;
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with Magit. If not, see <https://www.gnu.org/licenses/>.
22
23 ;;; Commentary:
24
25 ;; This library implements commands for creating Git commits. These
26 ;; commands just initiate the commit, support for writing the commit
27 ;; messages is implemented in `git-commit.el'.
28
29 ;;; Code:
30
31 (require 'magit)
32 (require 'magit-sequence)
33
34 ;;; Options
35
36 (defcustom magit-commit-ask-to-stage 'verbose
37 "Whether to ask to stage everything when committing and nothing is staged."
38 :package-version '(magit . "2.3.0")
39 :group 'magit-commands
40 :type '(choice (const :tag "Ask" t)
41 (const :tag "Ask showing diff" verbose)
42 (const :tag "Stage without confirmation" stage)
43 (const :tag "Don't ask" nil)))
44
45 (defcustom magit-commit-show-diff t
46 "Whether the relevant diff is automatically shown when committing."
47 :package-version '(magit . "2.3.0")
48 :group 'magit-commands
49 :type 'boolean)
50
51 (defcustom magit-commit-extend-override-date t
52 "Whether using `magit-commit-extend' changes the committer date."
53 :package-version '(magit . "2.3.0")
54 :group 'magit-commands
55 :type 'boolean)
56
57 (defcustom magit-commit-reword-override-date t
58 "Whether using `magit-commit-reword' changes the committer date."
59 :package-version '(magit . "2.3.0")
60 :group 'magit-commands
61 :type 'boolean)
62
63 (defcustom magit-commit-squash-confirm t
64 "Whether the commit targeted by squash and fixup has to be confirmed.
65 When non-nil then the commit at point (if any) is used as default
66 choice, otherwise it has to be confirmed. This option only
67 affects `magit-commit-squash' and `magit-commit-fixup'. The
68 \"instant\" variants always require confirmation because making
69 an error while using those is harder to recover from."
70 :package-version '(magit . "2.1.0")
71 :group 'magit-commands
72 :type 'boolean)
73
74 (defcustom magit-commit-diff-inhibit-same-window nil
75 "Whether to inhibit use of same window when showing diff while committing.
76
77 When writing a commit, then a diff of the changes to be committed
78 is automatically shown. The idea is that the diff is shown in a
79 different window of the same frame and for most users that just
80 works. In other words most users can completely ignore this
81 option because its value doesn't make a difference for them.
82
83 However for users who configured Emacs to never create a new
84 window even when the package explicitly tries to do so, then
85 displaying two new buffers necessarily means that the first is
86 immediately replaced by the second. In our case the message
87 buffer is immediately replaced by the diff buffer, which is of
88 course highly undesirable.
89
90 A workaround is to suppress this user configuration in this
91 particular case. Users have to explicitly opt-in by toggling
92 this option. We cannot enable the workaround unconditionally
93 because that again causes issues for other users: if the frame
94 is too tiny or the relevant settings too aggressive, then the
95 diff buffer would end up being displayed in a new frame.
96
97 Also see https://github.com/magit/magit/issues/4132."
98 :package-version '(magit . "3.3.0")
99 :group 'magit-commands
100 :type 'boolean)
101
102 (defvar magit-common-git-post-commit-functions nil
103 "Hook run by Git hooks `post-commit', `post-merge' and `post-rewrite'.
104
105 There is not a single Git hook, which is called after a commit is
106 created; to achieve that, all of these Git hooks have to be used.
107
108 Git hooks are documented in the githooks(5) manpage. This Lisp hook
109 is only run if `magit-run-hooks-from-githooks' is non-nil, and Magit
110 runs Git asynchronously and on the local machine. The hook functions
111 are called with the same arguments as the Git hook; see the mentioned
112 manpage for details.")
113
114 ;;; Menu
115
116 ;;;###autoload(autoload 'magit-commit "magit-commit" nil t)
117 (transient-define-prefix magit-commit ()
118 "Create a new commit or replace an existing commit."
119 :info-manual "(magit)Initiating a Commit"
120 :man-page "git-commit"
121 :value '("--verbose")
122 ["Arguments"
123 ("-a" "Stage all modified and deleted files" ("-a" "--all"))
124 ("-e" "Allow empty commit" "--allow-empty")
125 ("-v" "Show diff of changes to be committed" ("-v" "--verbose"))
126 ("-n" "Disable hooks" ("-n" "--no-verify"))
127 ("-R" "Claim authorship and reset author date" "--reset-author")
128 (magit:--author :description "Override the author")
129 (magit-commit:--date :level 7)
130 (magit:--gpg-sign :level 5)
131 (magit:--signoff)
132 (magit-commit:--reuse-message)
133 (magit-commit:--reedit-message)]
134 [["Create"
135 ("c" "Commit" magit-commit-create)]
136 ["Edit HEAD"
137 ("e" "Extend" magit-commit-extend)
138 ""
139 ("a" "Amend" magit-commit-amend)
140 ""
141 ("w" "Reword" magit-commit-reword)
142 ("d" "Reshelve" magit-commit-reshelve :level 0)]
143 ["Edit"
144 ("f" "Fixup" magit-commit-fixup)
145 ("s" "Squash" magit-commit-squash)
146 ("A" "Alter" magit-commit-alter)
147 ("n" "Augment" magit-commit-augment)
148 ("W" "Revise" magit-commit-revise)]
149 ["Edit and rebase"
150 ("F" "Instant fixup" magit-commit-instant-fixup)
151 ("S" "Instant squash" magit-commit-instant-squash)
152 ""
153 ""
154 ("R" "Reword past" magit-rebase-reword-commit :level 0)]
155 ["Spread across commits"
156 ("x" "Modified files" magit-commit-autofixup :level 6)
157 ("X" "Updated modules" magit-commit-absorb-modules :level 6)]]
158 (interactive)
159 (if-let ((buffer (magit-commit-message-buffer)))
160 (switch-to-buffer buffer)
161 (transient-setup 'magit-commit)))
162
163 (defun magit-commit-arguments nil
164 (transient-args 'magit-commit))
165
166 (transient-define-argument magit-commit:--date ()
167 :description "Override the author date"
168 :class 'transient-option
169 :shortarg "-D"
170 :argument "--date="
171 :reader #'transient-read-date)
172
173 (transient-define-argument magit-commit:--reuse-message ()
174 :description "Reuse commit message"
175 :class 'transient-option
176 :shortarg "-C"
177 :argument "--reuse-message="
178 :reader #'magit-read-reuse-message
179 :history-key 'magit-revision-history)
180
181 (transient-define-argument magit-commit:--reedit-message ()
182 :description "Reedit commit message"
183 :class 'transient-option
184 :shortarg "-c"
185 :argument "--reedit-message="
186 :reader #'magit-read-reuse-message
187 :history-key 'magit-revision-history)
188
189 (defun magit-read-reuse-message (prompt &optional default history)
190 (if current-prefix-arg
191 (let (rev)
192 (magit-log-select
193 (lambda (r) (setq rev r) (exit-recursive-edit))
194 "Type %p on a commit to reuse its message")
195 (recursive-edit)
196 rev)
197 (magit-completing-read prompt (magit-list-refnames)
198 nil nil nil history
199 (or default
200 (magit-commit-at-point)
201 (and (magit-rev-verify "ORIG_HEAD")
202 "ORIG_HEAD")))))
203
204 ;;; Commands
205 ;;;; Create
206
207 ;;;###autoload
208 (defun magit-commit-create (&optional args)
209 "Create a new commit."
210 (interactive (list (magit-commit-arguments)))
211 (cond ((member "--all" args)
212 (setq this-command 'magit-commit--all))
213 ((member "--allow-empty" args)
214 (setq this-command 'magit-commit--allow-empty)))
215 (when (setq args (magit-commit-assert args))
216 (let ((default-directory (magit-toplevel)))
217 (magit-run-git-with-editor "commit" args))))
218
219 ;;;; Edit HEAD
220
221 ;;;###autoload
222 (defun magit-commit-extend (&optional args override-date)
223 "Amend staged changes to the last commit, without editing its message.
224
225 With a prefix argument do not update the committer date; without an
226 argument update it. The option `magit-commit-extend-override-date'
227 can be used to inverse the meaning of the prefix argument. Called
228 non-interactively, the optional OVERRIDE-DATE argument controls this
229 behavior, and the option is of no relevance."
230 (interactive (list (magit-commit-arguments)
231 (if current-prefix-arg
232 (not magit-commit-extend-override-date)
233 magit-commit-extend-override-date)))
234 (when (setq args (magit-commit-assert args))
235 (magit-commit-amend-assert)
236 (if override-date
237 (magit-run-git-with-editor "commit" "--amend" "--no-edit" args)
238 (with-environment-variables
239 (("GIT_COMMITTER_DATE" (magit-rev-format "%cD")))
240 (magit-run-git-with-editor "commit" "--amend" "--no-edit" args)))))
241
242 ;;;###autoload
243 (defun magit-commit-amend (&optional args)
244 "Amend staged changes (if any) to the last commit, and edit its message."
245 (interactive (list (magit-commit-arguments)))
246 (magit-commit-amend-assert)
247 (magit-run-git-with-editor "commit" "--amend" args))
248
249 ;;;###autoload
250 (defun magit-commit-reword (&optional args override-date)
251 "Reword the message of the last commit, without amending its tree.
252
253 With a prefix argument do not update the committer date; without an
254 argument update it. The option `magit-commit-reword-override-date'
255 can be used to inverse the meaning of the prefix argument. Called
256 non-interactively, the optional OVERRIDE-DATE argument controls this
257 behavior, and the option is of no relevance."
258 (interactive (list (magit-commit-arguments)
259 (if current-prefix-arg
260 (not magit-commit-reword-override-date)
261 magit-commit-reword-override-date)))
262 (magit-commit-amend-assert)
263 (cl-pushnew "--allow-empty" args :test #'equal)
264 (if override-date
265 (magit-run-git-with-editor "commit" "--amend" "--only" args)
266 (with-environment-variables
267 (("GIT_COMMITTER_DATE" (magit-rev-format "%cD")))
268 (magit-run-git-with-editor "commit" "--amend" "--only" args))))
269
270 ;;;; Edit
271
272 ;;;###autoload
273 (defun magit-commit-fixup (&optional commit args)
274 "Create a fixup commit, leaving the original commit message untouched.
275
276 If there is a reachable commit at point, target that. Otherwise prompt
277 for a commit. If `magit-commit-squash-confirm' is non-nil, always make
278 the user explicitly select a commit, in a buffer dedicated to that task.
279
280 During a later rebase, when this commit gets squashed into its targeted
281 commit, the original message of the targeted commit is used as-is.
282
283 In other words, call \"git commit --fixup=COMMIT --no-edit\"."
284 (interactive (list (magit-commit-at-point)
285 (magit-commit-arguments)))
286 (magit-commit-squash-internal "--fixup=" commit args))
287
288 ;;;###autoload
289 (defun magit-commit-squash (&optional commit args)
290 "Create a squash commit, without the user authoring a commit message.
291
292 If there is a reachable commit at point, target that. Otherwise prompt
293 for a commit. If `magit-commit-squash-confirm' is non-nil, always make
294 the user explicitly select a commit, in a buffer dedicated to that task.
295
296 During a later rebase, when this commit gets squashed into its targeted
297 commit, the user is given a chance to edit the original message to take
298 the changes from the squash commit into account.
299
300 In other words, call \"git commit --squash=COMMIT --no-edit\"."
301 (interactive (list (magit-commit-at-point)
302 (magit-commit-arguments)))
303 (magit-commit-squash-internal "--squash=" commit args))
304
305 ;;;###autoload
306 (defun magit-commit-alter (&optional commit args)
307 "Create a squash commit, authoring the final commit message now.
308
309 If there is a reachable commit at point, target that. Otherwise prompt
310 for a commit. If `magit-commit-squash-confirm' is non-nil, always make
311 the user explicitly select a commit, in a buffer dedicated to that task.
312
313 During a later rebase, when this commit gets squashed into its targeted
314 commit, the original message of the targeted commit is replaced with the
315 message of this commit, without the user automatically being given a
316 chance to edit again.
317
318 In other words, call \"git commit --fixup=amend:COMMIT --edit\"."
319 (interactive (list (magit-commit-at-point)
320 (magit-commit-arguments)))
321 (magit-commit-squash-internal "--fixup=amend:" commit args nil 'edit))
322
323 ;;;###autoload
324 (defun magit-commit-augment (&optional commit args)
325 "Create a squash commit, authoring a new temporary commit message.
326
327 If there is a reachable commit at point, target that. Otherwise prompt
328 for a commit. If `magit-commit-squash-confirm' is non-nil, always make
329 the user explicitly select a commit, in a buffer dedicated to that task.
330
331 During a later rebase, when this commit gets squashed into its targeted
332 commit, the user is asked to write a final commit message, in a buffer
333 that starts out containing both the original commit message, as well as
334 the temporary commit message of the squash commit.
335
336 In other words, call \"git commit --squash=COMMIT --edit\"."
337 (interactive (list (magit-commit-at-point)
338 (magit-commit-arguments)))
339 (magit-commit-squash-internal "--squash=" commit args nil 'edit))
340
341 ;;;###autoload
342 (defun magit-commit-revise (&optional commit args)
343 "Reword the message of an existing commit, without editing its tree.
344
345 If there is a reachable commit at point, target that. Otherwise prompt
346 for a commit. If `magit-commit-squash-confirm' is non-nil, always make
347 the user explicitly select a commit, in a buffer dedicated to that task.
348
349 During a later rebase, when this commit gets squashed into its targeted
350 commit, a combined commit is created which uses the message of the fixup
351 commit and the tree of the targeted commit.
352
353 In other words, call \"git commit --fixup=reword:COMMIT --edit\"."
354 (interactive (list (magit-commit-at-point)
355 (magit-commit-arguments)))
356 (magit-commit-squash-internal "--fixup=reword:" commit args 'nopatch 'edit))
357
358 ;;;; Edit and Rebase
359
360 ;;;###autoload
361 (defun magit-commit-instant-fixup (&optional commit args)
362 "Create a fixup commit, and immediately combine it with its target.
363
364 If there is a reachable commit at point, target that. Otherwise prompt
365 for a commit. If `magit-commit-squash-confirm' is non-nil, always make
366 the user explicitly select a commit, in a buffer dedicated to that task.
367
368 Leave the original commit message of the targeted commit untouched.
369
370 Like `magit-commit-fixup' but also run a \"--autofixup\" rebase."
371 (interactive (list (magit-commit-at-point)
372 (magit-commit-arguments)))
373 (magit-commit-squash-internal "--fixup=" commit args nil nil 'rebase))
374
375 ;;;###autoload
376 (defun magit-commit-instant-squash (&optional commit args)
377 "Create a squash commit, and immediately combine it with its target.
378
379 If there is a reachable commit at point, target that. Otherwise prompt
380 for a commit. If `magit-commit-squash-confirm' is non-nil, always make
381 the user explicitly select a commit, in a buffer dedicated to that task.
382
383 Turing the rebase phase, when the two commits are being squashed, ask
384 the user to author the final commit message, based on the original
385 message of the targeted commit.
386
387 Like `magit-commit-squash' but also run a \"--autofixup\" rebase."
388 (interactive (list (magit-commit-at-point)
389 (magit-commit-arguments)))
390 (magit-commit-squash-internal "--squash=" commit args nil nil 'rebase))
391
392 ;;;; Internal
393
394 (defun magit-commit-squash-internal
395 (option commit &optional args nopatch edit rebase confirmed)
396 (when-let ((args (magit-commit-assert args nopatch (not edit))))
397 (when (and commit rebase (not (magit-rev-ancestor-p commit "HEAD")))
398 (magit-read-char-case
399 (format "%s isn't an ancestor of HEAD. " commit) nil
400 (?c "[c]reate without rebasing" (setq rebase nil))
401 (?s "[s]elect other" (setq commit nil))
402 (?a "[a]bort" (user-error "Quit"))))
403 (when commit
404 (setq commit (magit-rebase-interactive-assert commit t)))
405 (if (and commit
406 (or confirmed
407 (not (or rebase
408 current-prefix-arg
409 magit-commit-squash-confirm))))
410 (let ((magit-commit-show-diff nil))
411 (push (concat option commit) args)
412 (push (if edit "--edit" "--no-edit") args)
413 (if rebase
414 (magit-with-editor
415 (magit-call-git
416 "commit" "--no-gpg-sign"
417 (seq-remove (apply-partially #'string-prefix-p "--gpg-sign=")
418 args)))
419 (magit-run-git-with-editor "commit" args))
420 t) ; The commit was created; used by below lambda.
421 (let ((winconf (and magit-commit-show-diff
422 (current-window-configuration))))
423 (magit-log-select
424 (lambda (commit)
425 (when (and (magit-commit-squash-internal option commit args
426 nopatch edit rebase t)
427 rebase)
428 (magit-commit-amend-assert commit)
429 (magit-rebase-interactive-1 commit
430 (list "--autosquash" "--autostash" "--keep-empty")
431 "" "true" nil t))
432 (when winconf
433 (set-window-configuration winconf)))
434 (format "Type %%p on a commit to %s into it,"
435 (substring option 2))
436 nil nil nil commit))
437 (when (and magit-commit-show-diff (not nopatch))
438 (let ((magit-display-buffer-noselect t))
439 (apply #'magit-diff-staged nil (magit-diff-arguments)))))))
440
441 (defun magit-commit-amend-assert (&optional commit)
442 (when-let ((branches (magit-list-publishing-branches commit)))
443 (let ((m1 "This commit has already been published to ")
444 (m2 ".\nDo you really want to modify it"))
445 (magit-confirm 'amend-published
446 (concat m1 "%s" m2)
447 (concat m1 "%d public branches" m2)
448 nil branches))))
449
450 (defun magit-commit-assert (args &optional nopatch strict)
451 (cond
452 (nopatch (or args (list "--")))
453 ((or (magit-anything-staged-p)
454 (and (magit-anything-unstaged-p)
455 ;; ^ Everything of nothing is still nothing.
456 (member "--all" args))
457 (and (not strict)
458 ;; ^ For amend variants that don't make sense otherwise.
459 (or (member "--amend" args)
460 (member "--allow-empty" args)
461 (member "--reset-author" args)
462 (member "--signoff" args)
463 (transient-arg-value "--author=" args)
464 (transient-arg-value "--date=" args))))
465 (or args (list "--")))
466 ((and (magit-rebase-in-progress-p)
467 (not (magit-anything-unstaged-p))
468 (y-or-n-p "Nothing staged. Continue in-progress rebase? "))
469 (setq this-command #'magit-rebase-continue)
470 (magit-run-git-sequencer "rebase" "--continue")
471 nil)
472 ((file-exists-p (expand-file-name "MERGE_MSG" (magit-gitdir)))
473 (cond ((magit-anything-unmerged-p)
474 (user-error "Unresolved conflicts"))
475 ((and (magit-anything-unstaged-p)
476 (not (y-or-n-p
477 "Proceed with merge despite unstaged changes? ")))
478 (user-error "Abort"))
479 ((or args (list "--")))))
480 ((not (magit-anything-unstaged-p))
481 (user-error "Nothing staged (or unstaged)"))
482 (magit-commit-ask-to-stage
483 (when (eq magit-commit-ask-to-stage 'verbose)
484 (apply #'magit-diff-unstaged (magit-diff-arguments)))
485 (prog1 (when (or (eq magit-commit-ask-to-stage 'stage)
486 (y-or-n-p
487 "Nothing staged. Commit all uncommitted changes? "))
488 (setq this-command 'magit-commit--all)
489 (cons "--all" (or args (list "--"))))
490 (when (and (eq magit-commit-ask-to-stage 'verbose)
491 (derived-mode-p 'magit-diff-mode))
492 (magit-mode-bury-buffer))))
493 (t
494 (user-error "Nothing staged"))))
495
496 ;;;; Reshelve
497
498 (defvar magit--reshelve-history nil)
499
500 ;;;###autoload
501 (defun magit-commit-reshelve (date update-author &optional args)
502 "Change committer (and possibly author) date of the last commit.
503
504 The current time is used as the initial minibuffer input and the
505 original author or committer date is available as the previous
506 history element.
507
508 Both the author and the committer dates are changed, unless one
509 of the following is true, in which case only the committer date
510 is updated:
511 - You are not the author of the commit that is being reshelved.
512 - The command was invoked with a prefix argument.
513 - Non-interactively if UPDATE-AUTHOR is nil."
514 (interactive
515 (let ((update-author (and (magit-rev-author-p "HEAD")
516 (not current-prefix-arg))))
517 (push (magit-rev-format (if update-author "%ad" "%cd") "HEAD"
518 (concat "--date=format:%F %T %z"))
519 magit--reshelve-history)
520 (list (read-string (if update-author
521 "Change author and committer dates to: "
522 "Change committer date to: ")
523 (cons (format-time-string "%F %T %z") 17)
524 'magit--reshelve-history)
525 update-author
526 (magit-commit-arguments))))
527 (with-environment-variables (("GIT_COMMITTER_DATE" date))
528 (magit-run-git "commit" "--amend" "--no-edit"
529 (and update-author (concat "--date=" date))
530 args)))
531
532 ;;;; Spread
533
534 ;;;###autoload
535 (defun magit-commit-absorb-modules (phase commit)
536 "Spread modified modules across recent commits."
537 (interactive (list 'select (magit-get-upstream-branch)))
538 (let ((modules (magit-list-modified-modules)))
539 (unless modules
540 (user-error "There are no modified modules that could be absorbed"))
541 (when commit
542 (setq commit (magit-rebase-interactive-assert commit t)))
543 (cond ((and commit (eq phase 'run))
544 (dolist (module modules)
545 (when-let ((msg (magit-git-string
546 "log" "-1" "--format=%s"
547 (concat commit "..") "--" module)))
548 (magit-git "commit" "-m" (concat "fixup! " msg)
549 "--only" "--" module)))
550 (magit-refresh)
551 t)
552 ((magit-log-select
553 (lambda (commit)
554 (magit-commit-absorb-modules 'run commit))
555 nil nil nil nil commit)))))
556
557 ;;;###autoload(autoload 'magit-commit-absorb "magit-commit" nil t)
558 (transient-define-prefix magit-commit-absorb (phase commit args)
559 "Spread staged changes across recent commits.
560 With a prefix argument use a transient command to select infix
561 arguments. This command requires git-absorb executable, which
562 is available from https://github.com/tummychow/git-absorb.
563 See `magit-commit-autofixup' for an alternative implementation."
564 :value '("-v")
565 ["Arguments"
566 ("-f" "Skip safety checks" ("-f" "--force"))
567 ("-v" "Increase verbosity" ("-v" "--verbose"))]
568 ["Actions"
569 ("x" "Absorb" magit-commit-absorb)]
570 (interactive (if current-prefix-arg
571 (list 'transient nil nil)
572 (list 'select
573 (and$ (magit-get-upstream-branch)
574 (magit-merge-base $ "HEAD"))
575 (transient-args 'magit-commit-absorb))))
576 (if (eq phase 'transient)
577 (transient-setup 'magit-commit-absorb)
578 (unless (magit-git-executable-find "git-absorb")
579 (user-error "This command requires the git-absorb executable, which %s"
580 "is available from https://github.com/tummychow/git-absorb"))
581 (unless (magit-anything-staged-p)
582 (if (magit-anything-unstaged-p)
583 (if (y-or-n-p "Nothing staged. Absorb all unstaged changes? ")
584 (magit-with-toplevel
585 (magit-run-git "add" "-u" "."))
586 (user-error "Abort"))
587 (user-error "There are no changes that could be absorbed")))
588 (when commit
589 (setq commit (magit-rebase-interactive-assert commit t)))
590 (if (and commit (eq phase 'run))
591 (prog1 t (magit-run-git-async "absorb" args "-b" commit))
592 (magit-log-select
593 (lambda (commit)
594 (with-no-warnings ; about non-interactive use
595 (magit-commit-absorb 'run commit args)))
596 nil nil nil nil commit))))
597
598 (transient-augment-suffix magit-commit-absorb :transient 'transient--do-exit)
599
600 ;;;###autoload(autoload 'magit-commit-autofixup "magit-commit" nil t)
601 (transient-define-prefix magit-commit-autofixup (phase commit args)
602 "Spread staged or unstaged changes across recent commits.
603
604 If there are any staged then spread only those, otherwise spread all
605 unstaged changes. With a prefix argument use a transient command to
606 select infix arguments.
607
608 This command requires the git-autofixup script, which is available from
609 https://github.com/torbiak/git-autofixup. See `magit-commit-absorb' for
610 an alternative implementation."
611 :value '("-vv")
612 ["Arguments"
613 (magit-autofixup:--context)
614 (magit-autofixup:--strict)
615 ("-v" "Increase verbosity" "-vv")]
616 ["Actions"
617 ("x" "Absorb" magit-commit-autofixup)]
618 (interactive (if current-prefix-arg
619 (list 'transient nil nil)
620 (list 'select
621 (and$ (magit-get-upstream-branch)
622 (magit-merge-base $ "HEAD"))
623 (transient-args 'magit-commit-autofixup))))
624 (if (eq phase 'transient)
625 (transient-setup 'magit-commit-autofixup)
626 (unless (magit-git-executable-find "git-autofixup")
627 (user-error "This command requires the git-autofixup script, which %s"
628 "is available from https://github.com/torbiak/git-autofixup"))
629 (unless (magit-anything-modified-p)
630 (user-error "There are no changes that could be absorbed"))
631 (when commit
632 (setq commit (magit-rebase-interactive-assert commit t)))
633 (if (and commit (eq phase 'run))
634 (prog1 t (magit-run-git-async "autofixup" args commit))
635 (magit-log-select
636 (lambda (commit)
637 (with-no-warnings ; about non-interactive use
638 (magit-commit-autofixup 'run commit args)))
639 nil nil nil nil commit))))
640
641 (transient-augment-suffix magit-commit-autofixup :transient 'transient--do-exit)
642
643 (transient-define-argument magit-autofixup:--context ()
644 :description "Diff context lines"
645 :class 'transient-option
646 :shortarg "-c"
647 :argument "--context="
648 :reader #'transient-read-number-N0)
649
650 (transient-define-argument magit-autofixup:--strict ()
651 :description "Strictness"
652 :class 'transient-option
653 :shortarg "-s"
654 :argument "--strict="
655 :reader #'transient-read-number-N0)
656
657 ;;; Pending Diff
658
659 (defun magit-commit-diff ()
660 (magit-repository-local-set 'this-commit-command
661 (if (eq this-command 'with-editor-finish)
662 'magit-commit--rebase
663 last-command))
664 (when (and git-commit-mode magit-commit-show-diff)
665 (when-let ((diff-buffer
666 ;; This signals an error if not inside a Git repository,
667 ;; but the user may be visiting COMMIT_EDITMSG using a
668 ;; tool other than git, which can be used outside a Git
669 ;; repository. See #5527.
670 (ignore-error magit-outside-git-repo
671 (magit-get-mode-buffer 'magit-diff-mode))))
672 ;; This window just started displaying the commit message buffer.
673 ;; Without unrecording that buffer would immediately be replaced
674 ;; with the diff buffer. See #2632.
675 (unrecord-window-buffer nil diff-buffer))
676 (message "Diffing changes to be committed (C-g to abort diffing)")
677 (let ((inhibit-quit nil))
678 (condition-case nil
679 (with-demoted-errors "Error showing commit diff: %S"
680 (magit-commit-diff--show))
681 (quit)))))
682
683 (defun magit-commit-diff--args ()
684 (let ((rev nil)
685 (arg "--cached")
686 (command (magit-repository-local-get 'this-commit-command))
687 (staged (magit-anything-staged-p))
688 (unstaged
689 ;; Escape $GIT_DIR because `magit-anything-unstaged-p'
690 ;; requires a working tree.
691 (magit-with-toplevel
692 (magit-anything-unstaged-p)))
693 (squash (let ((f (expand-file-name "rebase-merge/rewritten-pending"
694 (magit-gitdir))))
695 (and (file-exists-p f) (length (magit-file-lines f)))))
696 (noalt nil))
697 (pcase (list staged unstaged command)
698 ((guard (not (magit-commit-p "HEAD^")))
699 (setq rev "HEAD")
700 (setq arg nil))
701 ((and `(,_ ,_ magit-commit--rebase)
702 (guard (integerp squash)))
703 (setq rev (format "HEAD~%s" squash)))
704 (`(,_ ,_ magit-commit-amend)
705 (setq rev "HEAD^"))
706 (`(nil nil magit-commit--allow-empty)
707 (setq rev "HEAD")
708 (setq arg nil))
709 ((or `(,_ ,_ magit-commit-reword)
710 `(nil nil ,_))
711 (setq rev "HEAD^..HEAD")
712 (setq arg nil))
713 (`(,_ t magit-commit--all)
714 (setq rev "HEAD")
715 (setq arg nil))
716 (`(nil t handle-switch-frame)
717 ;; Either --all or --allow-empty. Assume it is the former.
718 (setq rev "HEAD")
719 (setq arg nil)))
720 (cond
721 ((not
722 (and-let*
723 ((_(eq this-command 'magit-diff-while-committing))
724 (buf (magit-get-mode-buffer 'magit-diff-mode nil 'selected))
725 (_(equal rev (buffer-local-value 'magit-buffer-diff-range buf)))
726 (_(equal arg (buffer-local-value 'magit-buffer-diff-typearg buf)))))))
727 ((eq command 'magit-commit-amend)
728 (setq rev nil))
729 ((or squash
730 (file-exists-p (expand-file-name "rebase-merge/amend" (magit-gitdir))))
731 (setq rev "HEAD^"))
732 ((setq noalt t)))
733 (list rev arg noalt)))
734
735 (defun magit-commit-diff--show ()
736 (pcase-let ((`(,rev ,arg ,noalt) (magit-commit-diff--args)))
737 (if noalt
738 (message "No alternative diff while committing")
739 (let ((magit-inhibit-save-previous-winconf 'unset)
740 (magit-display-buffer-noselect t)
741 (display-buffer-overriding-action
742 display-buffer-overriding-action))
743 (when magit-commit-diff-inhibit-same-window
744 (setq display-buffer-overriding-action
745 '(nil (inhibit-same-window . t))))
746 (magit-diff-setup-buffer rev arg (car (magit-diff-arguments)) nil
747 (cond ((equal rev "HEAD") 'staged)
748 ((equal rev "HEAD^..HEAD") 'committed)
749 ('undefined)))))))
750
751 (add-hook 'server-switch-hook #'magit-commit-diff)
752 (add-hook 'with-editor-filter-visit-hook #'magit-commit-diff)
753
754 (add-to-list 'with-editor-server-window-alist
755 (cons git-commit-filename-regexp #'switch-to-buffer))
756
757 (defun magit-commit--reset-command ()
758 (magit-repository-local-delete 'this-commit-command))
759
760 ;;; Message Utilities
761
762 (defun magit-commit-message-buffer ()
763 (let* ((find-file-visit-truename t) ; git uses truename of COMMIT_EDITMSG
764 (topdir (magit-toplevel)))
765 (seq-find (##equal topdir (with-current-buffer %
766 (and git-commit-mode (magit-toplevel))))
767 (append (buffer-list (selected-frame))
768 (buffer-list)))))
769
770 (defvar magit-commit-add-log-insert-function #'magit-commit-add-log-insert
771 "Used by `magit-commit-add-log' to insert a single entry.")
772
773 (defun magit-commit-add-log ()
774 "Add a stub for the current change into the commit message buffer.
775 If no commit is in progress, then initiate it. Use the function
776 specified by variable `magit-commit-add-log-insert-function' to
777 actually insert the entry."
778 (interactive)
779 (pcase-let* ((hunk (and (magit-section-match 'hunk)
780 (magit-current-section)))
781 (log (magit-commit-message-buffer))
782 (`(,buf ,pos) (magit-diff-visit-file--noselect)))
783 (unless log
784 (unless (magit-commit-assert nil)
785 (user-error "Abort"))
786 (magit-commit-create)
787 (while (not (setq log (magit-commit-message-buffer)))
788 (sit-for 0.01)))
789 (magit--with-temp-position buf pos
790 (funcall magit-commit-add-log-insert-function log
791 (magit-file-relative-name)
792 (and hunk (add-log-current-defun))))))
793
794 (defun magit-commit-add-log-insert (buffer file defun)
795 (with-current-buffer buffer
796 (undo-boundary)
797 (goto-char (point-max))
798 (git-commit--goto-insert-position nil)
799 (while (re-search-backward (git-commit--trailer-regexp) nil t))
800 (save-restriction
801 (narrow-to-region (point-min) (point))
802 (cond ((re-search-backward (format "* %s\\(?: (\\([^)]+\\))\\)?: " file)
803 nil t)
804 (when (equal (match-str 1) defun)
805 (setq defun nil))
806 (re-search-forward ": "))
807 (t
808 (when (re-search-backward "^[\\*(].+\n" nil t)
809 (goto-char (match-end 0)))
810 (while (re-search-forward "^[^\\*\n].*\n" nil t))
811 (if defun
812 (progn (insert (format "* %s (%s): \n" file defun))
813 (setq defun nil))
814 (insert (format "* %s: \n" file)))
815 (backward-char)
816 (unless (looking-at "\n[\n\\']")
817 (insert ?\n)
818 (backward-char))))
819 (when defun
820 (forward-line)
821 (let ((limit (save-excursion
822 (and (re-search-forward "^\\*" nil t)
823 (point)))))
824 (unless (or (looking-back (format "(%s): " defun)
825 (line-beginning-position))
826 (re-search-forward (format "^(%s): " defun) limit t))
827 (while (re-search-forward "^[^\\*\n].*\n" limit t))
828 (insert (format "(%s): \n" defun))
829 (backward-char)))))))
830
831 ;;; Post Hook
832
833 (defcustom magit-post-commit-hook nil
834 "Hook run after creating a commit without the user editing a message.
835
836 This hook is run by `magit-refresh' if `this-command' is a member
837 of `magit-post-commit-hook-commands'. This only includes commands
838 named `magit-commit-*' that do *not* require that the user edits
839 the commit message in a buffer and then finishes by pressing
840 \\<with-editor-mode-map>\\[with-editor-finish].
841
842 Also see `git-commit-post-finish-hook'."
843 :package-version '(magit . "2.90.0")
844 :group 'magit-commands
845 :type 'hook)
846
847 (defvar magit-post-commit-hook-commands
848 (list #'magit-commit-extend
849 #'magit-commit-fixup
850 #'magit-commit-augment
851 #'magit-commit-instant-fixup
852 #'magit-commit-instant-squash))
853
854 ;;;###autoload
855 (defun magit-run-post-commit-hook ()
856 (when (and (not this-command)
857 (memq last-command magit-post-commit-hook-commands))
858 (run-hooks 'magit-post-commit-hook)))
859
860 ;;; _
861 (provide 'magit-commit)
862 ;; Local Variables:
863 ;; read-symbol-shorthands: (
864 ;; ("and$" . "cond-let--and$")
865 ;; ("thread$" . "cond-let--thread$")
866 ;; ("when$" . "cond-let--when$")
867 ;; ("and-let*" . "cond-let--and-let*")
868 ;; ("and-let" . "cond-let--and-let")
869 ;; ("if-let*" . "cond-let--if-let*")
870 ;; ("if-let" . "cond-let--if-let")
871 ;; ("when-let*" . "cond-let--when-let*")
872 ;; ("when-let" . "cond-let--when-let")
873 ;; ("while-let*" . "cond-let--while-let*")
874 ;; ("while-let" . "cond-let--while-let")
875 ;; ("match-string" . "match-string")
876 ;; ("match-str" . "match-string-no-properties"))
877 ;; End:
878 ;;; magit-commit.el ends here