git-commit.el (49174B)
1 ;;; git-commit.el --- Edit Git commit messages -*- lexical-binding:t; coding:utf-8 -*- 2 3 ;; Copyright (C) 2008-2024 The Magit Project Contributors 4 5 ;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev> 6 ;; Sebastian Wiesner <lunaryorn@gmail.com> 7 ;; Florian Ragwitz <rafl@debian.org> 8 ;; Marius Vollmer <marius.vollmer@gmail.com> 9 ;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev> 10 11 ;; SPDX-License-Identifier: GPL-3.0-or-later 12 13 ;; Magit is free software: you can redistribute it and/or modify 14 ;; it under the terms of the GNU General Public License as published 15 ;; by the Free Software Foundation, either version 3 of the License, 16 ;; or (at your option) any later version. 17 ;; 18 ;; Magit is distributed in the hope that it will be useful, 19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 ;; GNU General Public License for more details. 22 ;; 23 ;; You should have received a copy of the GNU General Public License 24 ;; along with Magit. If not, see <https://www.gnu.org/licenses/>. 25 26 ;; You should have received a copy of the AUTHORS.md file, which 27 ;; lists all contributors. If not, see https://magit.vc/authors. 28 29 ;;; Commentary: 30 31 ;; This package assists the user in writing good Git commit messages. 32 33 ;; While Git allows for the message to be provided on the command 34 ;; line, it is preferable to tell Git to create the commit without 35 ;; actually passing it a message. Git then invokes the `$GIT_EDITOR' 36 ;; (or if that is undefined `$EDITOR') asking the user to provide the 37 ;; message by editing the file ".git/COMMIT_EDITMSG" (or another file 38 ;; in that directory, e.g., ".git/MERGE_MSG" for merge commits). 39 40 ;; When `global-git-commit-mode' is enabled, which it is by default, 41 ;; then opening such a file causes the features described below, to 42 ;; be enabled in that buffer. Normally this would be done using a 43 ;; major-mode but to allow the use of any major-mode, as the user sees 44 ;; fit, it is done here by running a setup function, which among other 45 ;; things turns on the preferred major-mode, by default `text-mode'. 46 47 ;; Git waits for the `$EDITOR' to finish and then either creates the 48 ;; commit using the contents of the file as commit message, or, if the 49 ;; editor process exited with a non-zero exit status, aborts without 50 ;; creating a commit. Unfortunately Emacsclient (which is what Emacs 51 ;; users should be using as `$EDITOR' or at least as `$GIT_EDITOR') 52 ;; does not differentiate between "successfully" editing a file and 53 ;; aborting; not out of the box that is. 54 55 ;; By making use of the `with-editor' package this package provides 56 ;; both ways of finish an editing session. In either case the file 57 ;; is saved, but Emacseditor's exit code differs. 58 ;; 59 ;; C-c C-c Finish the editing session successfully by returning 60 ;; with exit code 0. Git then creates the commit using 61 ;; the message it finds in the file. 62 ;; 63 ;; C-c C-k Aborts the edit editing session by returning with exit 64 ;; code 1. Git then aborts the commit. 65 66 ;; Aborting the commit does not cause the message to be lost, but 67 ;; relying solely on the file not being tampered with is risky. This 68 ;; package additionally stores all aborted messages for the duration 69 ;; of the current session (i.e., until you close Emacs). To get back 70 ;; an aborted message use M-p and M-n while editing a message. 71 ;; 72 ;; M-p Replace the buffer contents with the previous message 73 ;; from the message ring. Of course only after storing 74 ;; the current content there too. 75 ;; 76 ;; M-n Replace the buffer contents with the next message from 77 ;; the message ring, after storing the current content. 78 79 ;; Support for inserting Git trailers (as described in the manpage 80 ;; git-interpret-trailers(1)) is available. 81 ;; 82 ;; C-c C-i Insert a trailer selected from a transient menu. 83 84 ;; When Git requests a commit message from the user, it does so by 85 ;; having her edit a file which initially contains some comments, 86 ;; instructing her what to do, and providing useful information, such 87 ;; as which files were modified. These comments, even when left 88 ;; intact by the user, do not become part of the commit message. This 89 ;; package ensures these comments are propertizes as such and further 90 ;; prettifies them by using different faces for various parts, such as 91 ;; files. 92 93 ;; Finally this package highlights style errors, like lines that are 94 ;; too long, or when the second line is not empty. It may even nag 95 ;; you when you attempt to finish the commit without having fixed 96 ;; these issues. The style checks and many other settings can easily 97 ;; be configured: 98 ;; 99 ;; M-x customize-group RET git-commit RET 100 101 ;;; Code: 102 103 (require 'magit-mode) 104 105 (require 'log-edit) 106 (require 'ring) 107 (require 'server) 108 (require 'transient) 109 (require 'with-editor) 110 111 (defvar diff-default-read-only) 112 (defvar flyspell-generic-check-word-predicate) 113 (defvar font-lock-beg) 114 (defvar font-lock-end) 115 (defvar recentf-exclude) 116 117 (defvar git-commit-need-summary-line) 118 119 (define-obsolete-variable-alias 120 'git-commit-known-pseudo-headers 121 'git-commit-trailers 122 "git-commit 4.0.0") 123 124 ;;; Options 125 ;;;; Variables 126 127 (defgroup git-commit nil 128 "Edit Git commit messages." 129 :prefix "git-commit-" 130 :link '(info-link "(magit)Editing Commit Messages") 131 :group 'tools) 132 133 (define-minor-mode global-git-commit-mode 134 "Edit Git commit messages. 135 136 This global mode arranges for `git-commit-setup' to be called 137 when a Git commit message file is opened. That usually happens 138 when Git uses the Emacsclient as $GIT_EDITOR to have the user 139 provide such a commit message. 140 141 Loading the library `git-commit' by default enables this mode, 142 but the library is not automatically loaded because doing that 143 would pull in many dependencies and increase startup time too 144 much. You can either rely on `magit' loading this library or 145 you can load it explicitly. Autoloading is not an alternative 146 because in this case autoloading would immediately trigger 147 full loading." 148 :group 'git-commit 149 :type 'boolean 150 :global t 151 :init-value t 152 :initialize 153 (lambda (symbol exp) 154 (custom-initialize-default symbol exp) 155 (when global-git-commit-mode 156 (add-hook 'find-file-hook #'git-commit-setup-check-buffer) 157 (remove-hook 'after-change-major-mode-hook 158 #'git-commit-setup-font-lock-in-buffer))) 159 (cond 160 (global-git-commit-mode 161 (add-hook 'find-file-hook #'git-commit-setup-check-buffer) 162 (add-hook 'after-change-major-mode-hook 163 #'git-commit-setup-font-lock-in-buffer)) 164 (t 165 (remove-hook 'find-file-hook #'git-commit-setup-check-buffer) 166 (remove-hook 'after-change-major-mode-hook 167 #'git-commit-setup-font-lock-in-buffer)))) 168 169 (defcustom git-commit-major-mode #'text-mode 170 "Major mode used to edit Git commit messages. 171 172 The major mode configured here is turned on by the minor mode 173 `git-commit-mode'." 174 :group 'git-commit 175 :type '(choice (function-item text-mode) 176 (function-item markdown-mode) 177 (function-item org-mode) 178 (function-item fundamental-mode) 179 (function-item git-commit-elisp-text-mode) 180 (function :tag "Another mode") 181 (const :tag "No major mode"))) 182 ;;;###autoload(put 'git-commit-major-mode 'safe-local-variable 183 ;;;###autoload (lambda (val) 184 ;;;###autoload (memq val '(text-mode 185 ;;;###autoload markdown-mode 186 ;;;###autoload org-mode 187 ;;;###autoload fundamental-mode 188 ;;;###autoload git-commit-elisp-text-mode)))) 189 190 (defvaralias 'git-commit-mode-hook 'git-commit-setup-hook 191 "This variable is an alias for `git-commit-setup-hook' (which see). 192 Also note that `git-commit-mode' (which see) is not a major-mode.") 193 194 (defcustom git-commit-setup-hook 195 '(git-commit-ensure-comment-gap 196 git-commit-save-message 197 git-commit-setup-changelog-support 198 git-commit-turn-on-auto-fill 199 git-commit-propertize-diff 200 bug-reference-mode) 201 "Hook run at the end of `git-commit-setup'." 202 :group 'git-commit 203 :type 'hook 204 :get #'magit-hook-custom-get 205 :options '(git-commit-ensure-comment-gap 206 git-commit-save-message 207 git-commit-setup-changelog-support 208 magit-generate-changelog 209 git-commit-turn-on-auto-fill 210 git-commit-turn-on-orglink 211 git-commit-turn-on-flyspell 212 git-commit-propertize-diff 213 bug-reference-mode)) 214 215 (defcustom git-commit-post-finish-hook nil 216 "Hook run after the user finished writing a commit message. 217 218 \\<with-editor-mode-map>\ 219 This hook is only run after pressing \\[with-editor-finish] in a buffer used 220 to edit a commit message. If a commit is created without the 221 user typing a message into a buffer, then this hook is not run. 222 223 This hook is not run until the new commit has been created. If 224 that takes Git longer than `git-commit-post-finish-hook-timeout' 225 seconds, then this hook isn't run at all. For certain commands 226 such as `magit-rebase-continue' this hook is never run because 227 doing so would lead to a race condition. 228 229 Also see `magit-post-commit-hook'." 230 :group 'git-commit 231 :type 'hook 232 :get #'magit-hook-custom-get) 233 234 (defcustom git-commit-post-finish-hook-timeout 1 235 "Time in seconds to wait for git to create a commit. 236 237 The hook `git-commit-post-finish-hook' (which see) is run only 238 after git is done creating a commit. If it takes longer than 239 `git-commit-post-finish-hook-timeout' seconds to create the 240 commit, then the hook is not run at all." 241 :group 'git-commit 242 :safe 'numberp 243 :type 'number) 244 245 (defcustom git-commit-finish-query-functions 246 '(git-commit-check-style-conventions) 247 "List of functions called to query before performing commit. 248 249 The commit message buffer is current while the functions are 250 called. If any of them returns nil, then the commit is not 251 performed and the buffer is not killed. The user should then 252 fix the issue and try again. 253 254 The functions are called with one argument. If it is non-nil, 255 then that indicates that the user used a prefix argument to 256 force finishing the session despite issues. Functions should 257 usually honor this wish and return non-nil." 258 :options '(git-commit-check-style-conventions) 259 :type 'hook 260 :group 'git-commit) 261 262 (defcustom git-commit-style-convention-checks '(non-empty-second-line) 263 "List of checks performed by `git-commit-check-style-conventions'. 264 265 Valid members are `non-empty-second-line' and `overlong-summary-line'. 266 That function is a member of `git-commit-finish-query-functions'." 267 :options '(non-empty-second-line overlong-summary-line) 268 :type '(list :convert-widget custom-hook-convert-widget) 269 :group 'git-commit) 270 271 (defcustom git-commit-summary-max-length 68 272 "Column beyond which characters in the summary lines are highlighted. 273 274 The highlighting indicates that the summary is getting too long 275 by some standards. It does in no way imply that going over the 276 limit a few characters or in some cases even many characters is 277 anything that deserves shaming. It's just a friendly reminder 278 that if you can make the summary shorter, then you might want 279 to consider doing so." 280 :group 'git-commit 281 :safe 'numberp 282 :type 'number) 283 284 (defcustom git-commit-trailers 285 '("Acked-by" 286 "Modified-by" 287 "Reviewed-by" 288 "Signed-off-by" 289 "Tested-by" 290 "Cc" 291 "Reported-by" 292 "Suggested-by" 293 "Co-authored-by" 294 "Co-developed-by") 295 "A list of Git trailers to be highlighted. 296 297 See also manpage git-interpret-trailer(1). This package does 298 not use that Git command, but the initial description still 299 serves as a good introduction." 300 :group 'git-commit 301 :safe (lambda (val) (and (listp val) (seq-every-p #'stringp val))) 302 :type '(repeat string)) 303 304 (defcustom git-commit-use-local-message-ring nil 305 "Whether to use a local message ring instead of the global one. 306 307 This can be set globally, in which case every repository gets its 308 own commit message ring, or locally for a single repository." 309 :group 'git-commit 310 :safe 'booleanp 311 :type 'boolean) 312 313 (defcustom git-commit-cd-to-toplevel nil 314 "Whether to set `default-directory' to the worktree in message buffer. 315 316 Editing a commit message is done by visiting a file located in the git 317 directory, usually \"COMMIT_EDITMSG\". As is done when visiting any 318 file, the local value of `default-directory' is set to the directory 319 that contains the file. 320 321 If this option is non-nil, then the local `default-directory' is changed 322 to the working tree from which the commit command was invoked. You may 323 wish to do that, to make it easier to open a file that is located in the 324 working tree, directly from the commit message buffer. 325 326 If the git variable `safe.bareRepository' is set to \"explicit\", then 327 you have to enable this, to be able to commit at all. See issue #5100. 328 329 This option only has an effect if the commit was initiated from Magit." 330 :group 'git-commit 331 :type 'boolean) 332 333 ;;;; Faces 334 335 (defgroup git-commit-faces nil 336 "Faces used for highlighting Git commit messages." 337 :prefix "git-commit-" 338 :group 'git-commit 339 :group 'faces) 340 341 (defface git-commit-summary 342 '((t :inherit font-lock-type-face)) 343 "Face used for the summary in commit messages." 344 :group 'git-commit-faces) 345 346 (defface git-commit-overlong-summary 347 '((t :inherit font-lock-warning-face)) 348 "Face used for the tail of overlong commit message summaries." 349 :group 'git-commit-faces) 350 351 (defface git-commit-nonempty-second-line 352 '((t :inherit font-lock-warning-face)) 353 "Face used for non-whitespace on the second line of commit messages." 354 :group 'git-commit-faces) 355 356 (defface git-commit-keyword 357 '((t :inherit font-lock-string-face)) 358 "Face used for keywords in commit messages. 359 In this context a \"keyword\" is text surrounded by brackets." 360 :group 'git-commit-faces) 361 362 (defface git-commit-trailer-token 363 '((t :inherit font-lock-keyword-face)) 364 "Face used for Git trailer tokens in commit messages." 365 :group 'git-commit-faces) 366 367 (defface git-commit-trailer-value 368 '((t :inherit font-lock-string-face)) 369 "Face used for Git trailer values in commit messages." 370 :group 'git-commit-faces) 371 372 (defface git-commit-comment-branch-local 373 '((t :inherit magit-branch-local)) 374 "Face used for names of local branches in commit message comments." 375 :group 'git-commit-faces) 376 377 (defface git-commit-comment-branch-remote 378 '((t :inherit magit-branch-remote)) 379 "Face used for names of remote branches in commit message comments." 380 :group 'git-commit-faces) 381 382 (defface git-commit-comment-detached 383 '((t :inherit git-commit-comment-branch-local)) 384 "Face used for detached `HEAD' in commit message comments." 385 :group 'git-commit-faces) 386 387 (defface git-commit-comment-heading 388 '((t :inherit git-commit-trailer-token)) 389 "Face used for headings in commit message comments." 390 :group 'git-commit-faces) 391 392 (defface git-commit-comment-file 393 '((t :inherit git-commit-trailer-value)) 394 "Face used for file names in commit message comments." 395 :group 'git-commit-faces) 396 397 (defface git-commit-comment-action 398 '((t :inherit bold)) 399 "Face used for actions in commit message comments." 400 :group 'git-commit-faces) 401 402 ;;; Keymap 403 404 (defvar-keymap git-commit-redundant-bindings 405 :doc "Bindings made redundant by `git-commit-insert-trailer'. 406 This keymap is used as the parent of `git-commit-mode-map', 407 to avoid upsetting muscle-memory. If you would rather avoid 408 the redundant bindings, then set this to nil, before loading 409 `git-commit'." 410 "C-c C-a" #'git-commit-ack 411 "C-c M-i" #'git-commit-suggested 412 "C-c C-m" #'git-commit-modified 413 "C-c C-o" #'git-commit-cc 414 "C-c C-p" #'git-commit-reported 415 "C-c C-r" #'git-commit-review 416 "C-c C-s" #'git-commit-signoff 417 "C-c C-t" #'git-commit-test) 418 419 (defvar-keymap git-commit-mode-map 420 :doc "Keymap used by `git-commit-mode'." 421 :parent git-commit-redundant-bindings 422 "M-p" #'git-commit-prev-message 423 "M-n" #'git-commit-next-message 424 "C-c M-p" #'git-commit-search-message-backward 425 "C-c M-n" #'git-commit-search-message-forward 426 "C-c C-i" #'git-commit-insert-trailer 427 "C-c M-s" #'git-commit-save-message 428 "C-c C-d" 'magit-diff-while-committing 429 "C-c C-w" 'magit-pop-revision-stack) 430 431 ;;; Menu 432 433 (require 'easymenu) 434 (easy-menu-define git-commit-mode-menu git-commit-mode-map 435 "Git Commit Mode Menu" 436 '("Commit" 437 ["Previous" git-commit-prev-message t] 438 ["Next" git-commit-next-message t] 439 "-" 440 ["Ack" git-commit-ack t 441 :help "Insert an 'Acked-by' trailer"] 442 ["Modified-by" git-commit-modified t 443 :help "Insert a 'Modified-by' trailer"] 444 ["Reviewed-by" git-commit-review t 445 :help "Insert a 'Reviewed-by' trailer"] 446 ["Sign-Off" git-commit-signoff t 447 :help "Insert a 'Signed-off-by' trailer"] 448 ["Tested-by" git-commit-test t 449 :help "Insert a 'Tested-by' trailer"] 450 "-" 451 ["CC" git-commit-cc t 452 :help "Insert a 'Cc' trailer"] 453 ["Reported" git-commit-reported t 454 :help "Insert a 'Reported-by' trailer"] 455 ["Suggested" git-commit-suggested t 456 :help "Insert a 'Suggested-by' trailer"] 457 ["Co-authored-by" git-commit-co-authored t 458 :help "Insert a 'Co-authored-by' trailer"] 459 ["Co-developed-by" git-commit-co-developed t 460 :help "Insert a 'Co-developed-by' trailer"] 461 "-" 462 ["Save" git-commit-save-message t] 463 ["Cancel" with-editor-cancel t] 464 ["Commit" with-editor-finish t])) 465 466 ;;; Hooks 467 468 (defconst git-commit-filename-regexp "/\\(\ 469 \\(\\(COMMIT\\|NOTES\\|PULLREQ\\|MERGEREQ\\|TAG\\)_EDIT\\|MERGE_\\|\\)MSG\ 470 \\|\\(BRANCH\\|EDIT\\)_DESCRIPTION\\)\\'") 471 472 (with-eval-after-load 'recentf 473 (add-to-list 'recentf-exclude git-commit-filename-regexp)) 474 475 (add-to-list 'with-editor-file-name-history-exclude git-commit-filename-regexp) 476 477 (defun git-commit-setup-font-lock-in-buffer () 478 (when (and buffer-file-name 479 (string-match-p git-commit-filename-regexp buffer-file-name)) 480 (git-commit-setup-font-lock))) 481 482 (defun git-commit-setup-check-buffer () 483 (when (and buffer-file-name 484 (string-match-p git-commit-filename-regexp buffer-file-name)) 485 (git-commit-setup))) 486 487 (defvar git-commit-mode) 488 489 (defun git-commit-file-not-found () 490 ;; cygwin git will pass a cygwin path (/cygdrive/c/foo/.git/...), 491 ;; try to handle this in window-nt Emacs. 492 (when-let 493 ((file (and (or (string-match-p git-commit-filename-regexp 494 buffer-file-name) 495 (and (boundp 'git-rebase-filename-regexp) 496 (string-match-p git-rebase-filename-regexp 497 buffer-file-name))) 498 (not (file-accessible-directory-p 499 (file-name-directory buffer-file-name))) 500 (magit-expand-git-file-name (substring buffer-file-name 2))))) 501 (when (file-accessible-directory-p (file-name-directory file)) 502 (let ((inhibit-read-only t)) 503 (insert-file-contents file t) 504 t)))) 505 506 (when (eq system-type 'windows-nt) 507 (add-hook 'find-file-not-found-functions #'git-commit-file-not-found)) 508 509 (defconst git-commit-default-usage-message "\ 510 Type \\[with-editor-finish] to finish, \ 511 \\[with-editor-cancel] to cancel, and \ 512 \\[git-commit-prev-message] and \\[git-commit-next-message] \ 513 to recover older messages") 514 515 (defvar git-commit-usage-message git-commit-default-usage-message 516 "Message displayed when editing a commit message. 517 When this is nil, then `with-editor-usage-message' is displayed 518 instead. One of these messages has to be displayed; otherwise 519 the user gets to see the message displayed by `server-execute'. 520 That message is misleading and because we cannot prevent it from 521 being displayed, we have to immediately show another message to 522 prevent the user from seeing it.") 523 524 (defvar git-commit-header-line-format nil 525 "If non-nil, header line format used by `git-commit-mode'. 526 Used as the local value of `header-line-format', in buffer using 527 `git-commit-mode'. If it is a string, then it is passed through 528 `substitute-command-keys' first. A useful setting may be: 529 (setq git-commit-header-line-format git-commit-default-usage-message) 530 (setq git-commit-usage-message nil) ; show a shorter message") 531 532 (defun git-commit-setup () 533 (let ((gitdir default-directory) 534 (cd (and git-commit-cd-to-toplevel 535 (or (car (rassoc default-directory magit--separated-gitdirs)) 536 (magit-toplevel))))) 537 ;; Pretend that git-commit-mode is a major-mode, 538 ;; so that directory-local settings can be used. 539 (let ((default-directory 540 (or (and (not (file-exists-p 541 (expand-file-name ".dir-locals.el" gitdir))) 542 ;; When $GIT_DIR/.dir-locals.el doesn't exist, 543 ;; fallback to $GIT_WORK_TREE/.dir-locals.el, 544 ;; because the maintainer can use the latter 545 ;; to enforce conventions, while s/he has no 546 ;; control over the former. 547 (or cd (magit-toplevel))) 548 gitdir))) 549 (let ((buffer-file-name nil) ; trick hack-dir-local-variables 550 (major-mode 'git-commit-mode)) ; trick dir-locals-collect-variables 551 (hack-dir-local-variables) 552 (hack-local-variables-apply))) 553 (when cd 554 (setq default-directory cd))) 555 (when git-commit-major-mode 556 (let ((auto-mode-alist 557 ;; `set-auto-mode--apply-alist' removes the remote part from 558 ;; the file-name before looking it up in `auto-mode-alist'. 559 ;; For our temporary entry to be found, we have to modify the 560 ;; file-name the same way. 561 (list (cons (concat "\\`" 562 (regexp-quote 563 (or (file-remote-p buffer-file-name 'localname) 564 buffer-file-name)) 565 "\\'") 566 git-commit-major-mode))) 567 ;; The major-mode hook might want to consult these minor 568 ;; modes, while the minor-mode hooks might want to consider 569 ;; the major mode. 570 (git-commit-mode t) 571 (with-editor-mode t)) 572 (normal-mode t))) 573 ;; Below we instead explicitly show a message. 574 (setq with-editor-show-usage nil) 575 (unless with-editor-mode 576 ;; Maybe already enabled when using `shell-command' or an Emacs shell. 577 (with-editor-mode 1)) 578 (add-hook 'with-editor-finish-query-functions 579 #'git-commit-finish-query-functions nil t) 580 (add-hook 'with-editor-pre-finish-hook #'git-commit-save-message nil t) 581 (add-hook 'with-editor-pre-cancel-hook #'git-commit-save-message nil t) 582 (when (fboundp 'magit-commit--reset-command) 583 (add-hook 'with-editor-post-finish-hook #'magit-commit--reset-command) 584 (add-hook 'with-editor-post-cancel-hook #'magit-commit--reset-command)) 585 (unless (memq last-command 586 '(magit-sequencer-continue 587 magit-sequencer-skip 588 magit-am-continue 589 magit-am-skip 590 magit-rebase-continue 591 magit-rebase-skip)) 592 (add-hook 'with-editor-post-finish-hook 593 (apply-partially #'git-commit-run-post-finish-hook 594 (magit-rev-parse "HEAD")) 595 nil t) 596 (when (fboundp 'magit-wip-maybe-add-commit-hook) 597 (magit-wip-maybe-add-commit-hook))) 598 (setq with-editor-cancel-message 599 #'git-commit-cancel-message) 600 (git-commit-setup-font-lock) 601 (git-commit-prepare-message-ring) 602 (when (boundp 'save-place) 603 (setq save-place nil)) 604 (let ((git-commit-mode-hook nil)) 605 (git-commit-mode 1)) 606 (with-demoted-errors "Error running git-commit-setup-hook: %S" 607 (run-hooks 'git-commit-setup-hook)) 608 (set-buffer-modified-p nil) 609 (when-let ((format git-commit-header-line-format)) 610 (setq header-line-format 611 (if (stringp format) (substitute-command-keys format) format))) 612 (when git-commit-usage-message 613 (setq with-editor-usage-message git-commit-usage-message)) 614 (with-editor-usage-message)) 615 616 (defun git-commit-run-post-finish-hook (previous) 617 (when git-commit-post-finish-hook 618 (cl-block nil 619 (let ((break (time-add (current-time) 620 (seconds-to-time 621 git-commit-post-finish-hook-timeout)))) 622 (while (equal (magit-rev-parse "HEAD") previous) 623 (if (time-less-p (current-time) break) 624 (sit-for 0.01) 625 (message "No commit created after 1 second. Not running %s." 626 'git-commit-post-finish-hook) 627 (cl-return)))) 628 (run-hooks 'git-commit-post-finish-hook)))) 629 630 (define-minor-mode git-commit-mode 631 "Auxiliary minor mode used when editing Git commit messages. 632 This mode is only responsible for setting up some key bindings. 633 Don't use it directly; instead enable `global-git-commit-mode'. 634 Variable `git-commit-major-mode' controls which major-mode is 635 used." 636 :lighter "") 637 638 (put 'git-commit-mode 'permanent-local t) 639 640 (defun git-commit-ensure-comment-gap () 641 "Separate initial empty line from initial comment. 642 If the buffer begins with an empty line followed by a comment, insert 643 an additional newline in between, so that once the users start typing, 644 the input isn't tacked to the comment." 645 (save-excursion 646 (goto-char (point-min)) 647 (when (looking-at (format "\\`\n%s" comment-start)) 648 (open-line 1)))) 649 650 (defun git-commit-setup-changelog-support () 651 "Treat ChangeLog entries as unindented paragraphs." 652 (when (fboundp 'log-indent-fill-entry) ; New in Emacs 27. 653 (setq-local fill-paragraph-function #'log-indent-fill-entry)) 654 (setq-local fill-indent-according-to-mode t) 655 (setq-local paragraph-start (concat paragraph-start "\\|\\*\\|("))) 656 657 (defun git-commit-turn-on-auto-fill () 658 "Unconditionally turn on Auto Fill mode. 659 Ensure auto filling happens everywhere, except in the summary line." 660 (turn-on-auto-fill) 661 (setq-local comment-auto-fill-only-comments nil) 662 (when git-commit-need-summary-line 663 (setq-local auto-fill-function #'git-commit-auto-fill-except-summary))) 664 665 (defun git-commit-auto-fill-except-summary () 666 (unless (eq (line-beginning-position) 1) 667 (do-auto-fill))) 668 669 (defun git-commit-turn-on-orglink () 670 "Turn on Orglink mode if it is available. 671 If `git-commit-major-mode' is `org-mode', then silently forgo 672 turning on `orglink-mode'." 673 (when (and (not (derived-mode-p 'org-mode)) 674 (boundp 'orglink-match-anywhere) 675 (fboundp 'orglink-mode)) 676 (setq-local orglink-match-anywhere t) 677 (orglink-mode 1))) 678 679 (defun git-commit-turn-on-flyspell () 680 "Unconditionally turn on Flyspell mode. 681 Also check text that is already in the buffer, while avoiding to check 682 most text that Git will strip from the final message, such as the last 683 comment and anything below the cut line (\"--- >8 ---\")." 684 (require 'flyspell) 685 (turn-on-flyspell) 686 (setq flyspell-generic-check-word-predicate 687 #'git-commit-flyspell-verify) 688 (let ((end nil) 689 ;; The "cut line" is defined in "git/wt-status.c". It appears 690 ;; in the commit message when `commit.verbose' is set to true. 691 (cut-line-regex (format "^%s -\\{8,\\} >8 -\\{8,\\}$" comment-start)) 692 (comment-start-regex (format "^\\(%s\\|$\\)" comment-start))) 693 (save-excursion 694 (goto-char (or (re-search-forward cut-line-regex nil t) 695 (point-max))) 696 (while (and (not (bobp)) (looking-at comment-start-regex)) 697 (forward-line -1)) 698 (unless (looking-at comment-start-regex) 699 (forward-line)) 700 (setq end (point))) 701 (flyspell-region (point-min) end))) 702 703 (defun git-commit-flyspell-verify () 704 (not (= (char-after (line-beginning-position)) 705 (aref comment-start 0)))) 706 707 (defun git-commit-finish-query-functions (force) 708 (run-hook-with-args-until-failure 709 'git-commit-finish-query-functions force)) 710 711 (defun git-commit-check-style-conventions (force) 712 "Check for violations of certain basic style conventions. 713 714 For each violation ask the user if she wants to proceed anyway. 715 Option `git-commit-style-convention-checks' controls which 716 conventions are checked." 717 (or force 718 (save-excursion 719 (goto-char (point-min)) 720 (re-search-forward (git-commit-summary-regexp) nil t) 721 (if (equal (match-string 1) "") 722 t ; Just try; we don't know whether --allow-empty-message was used. 723 (and (or (not (memq 'overlong-summary-line 724 git-commit-style-convention-checks)) 725 (equal (match-string 2) "") 726 (y-or-n-p "Summary line is too long. Commit anyway? ")) 727 (or (not (memq 'non-empty-second-line 728 git-commit-style-convention-checks)) 729 (not (match-string 3)) 730 (y-or-n-p "Second line is not empty. Commit anyway? "))))))) 731 732 (defun git-commit-cancel-message () 733 (message 734 (concat "Commit canceled" 735 (and (memq 'git-commit-save-message with-editor-pre-cancel-hook) 736 ". Message saved to `log-edit-comment-ring'")))) 737 738 ;;; History 739 740 (defun git-commit-prev-message (arg) 741 "Cycle backward through message history, after saving current message. 742 With a numeric prefix ARG, go back ARG comments." 743 (interactive "*p") 744 (let ((len (ring-length log-edit-comment-ring))) 745 (if (<= len 0) 746 (progn (message "Empty comment ring") (ding)) 747 ;; Unlike `log-edit-previous-comment' we save the current 748 ;; non-empty and newly written comment, because otherwise 749 ;; it would be irreversibly lost. 750 (when-let ((message (git-commit-buffer-message))) 751 (unless (ring-member log-edit-comment-ring message) 752 (ring-insert log-edit-comment-ring message) 753 (cl-incf arg) 754 (setq len (ring-length log-edit-comment-ring)))) 755 ;; Delete the message but not the instructions at the end. 756 (save-restriction 757 (goto-char (point-min)) 758 (narrow-to-region 759 (point) 760 (if (re-search-forward (concat "^" comment-start) nil t) 761 (max 1 (- (point) 2)) 762 (point-max))) 763 (delete-region (point-min) (point))) 764 (setq log-edit-comment-ring-index (log-edit-new-comment-index arg len)) 765 (message "Comment %d" (1+ log-edit-comment-ring-index)) 766 (insert (ring-ref log-edit-comment-ring log-edit-comment-ring-index))))) 767 768 (defun git-commit-next-message (arg) 769 "Cycle forward through message history, after saving current message. 770 With a numeric prefix ARG, go forward ARG comments." 771 (interactive "*p") 772 (git-commit-prev-message (- arg))) 773 774 (defun git-commit-search-message-backward (string) 775 "Search backward through message history for a match for STRING. 776 Save current message first." 777 (interactive 778 (list (read-string (format-prompt "Comment substring" 779 log-edit-last-comment-match) 780 nil nil log-edit-last-comment-match))) 781 (cl-letf (((symbol-function #'log-edit-previous-comment) 782 (symbol-function #'git-commit-prev-message))) 783 (log-edit-comment-search-backward string))) 784 785 (defun git-commit-search-message-forward (string) 786 "Search forward through message history for a match for STRING. 787 Save current message first." 788 (interactive 789 (list (read-string (format-prompt "Comment substring" 790 log-edit-last-comment-match) 791 nil nil log-edit-last-comment-match))) 792 (cl-letf (((symbol-function #'log-edit-previous-comment) 793 (symbol-function #'git-commit-prev-message))) 794 (log-edit-comment-search-forward string))) 795 796 (defun git-commit-save-message () 797 "Save current message to `log-edit-comment-ring'." 798 (interactive) 799 (if-let ((message (git-commit-buffer-message))) 800 (progn 801 (when-let ((index (ring-member log-edit-comment-ring message))) 802 (ring-remove log-edit-comment-ring index)) 803 (ring-insert log-edit-comment-ring message) 804 (when git-commit-use-local-message-ring 805 (magit-repository-local-set 'log-edit-comment-ring 806 log-edit-comment-ring)) 807 (message "Message saved")) 808 (message "Only whitespace and/or comments; message not saved"))) 809 810 (defun git-commit-prepare-message-ring () 811 (make-local-variable 'log-edit-comment-ring-index) 812 (when git-commit-use-local-message-ring 813 (setq-local log-edit-comment-ring 814 (magit-repository-local-get 815 'log-edit-comment-ring 816 (make-ring log-edit-maximum-comment-ring-size))))) 817 818 (defun git-commit-buffer-message () 819 (let ((flush (concat "^" comment-start)) 820 (str (buffer-substring-no-properties (point-min) (point-max)))) 821 (with-temp-buffer 822 (insert str) 823 (goto-char (point-min)) 824 (when (re-search-forward (concat flush " -+ >8 -+$") nil t) 825 (delete-region (line-beginning-position) (point-max))) 826 (goto-char (point-min)) 827 (flush-lines flush) 828 (goto-char (point-max)) 829 (unless (eq (char-before) ?\n) 830 (insert ?\n)) 831 (setq str (buffer-string))) 832 (and (not (string-match "\\`[ \t\n\r]*\\'" str)) 833 (progn 834 (when (string-match "\\`\n\\{2,\\}" str) 835 (setq str (replace-match "\n" t t str))) 836 (when (string-match "\n\\{2,\\}\\'" str) 837 (setq str (replace-match "\n" t t str))) 838 str)))) 839 840 ;;; Trailers 841 842 (transient-define-prefix git-commit-insert-trailer () 843 "Insert a commit message trailer. 844 845 See also manpage git-interpret-trailer(1). This command does 846 not use that Git command, but the initial description still 847 serves as a good introduction." 848 [[:description (lambda () 849 (cond (prefix-arg 850 "Insert ... by someone ") 851 ("Insert ... by yourself"))) 852 ("a" "Ack" git-commit-ack) 853 ("m" "Modified" git-commit-modified) 854 ("r" "Reviewed" git-commit-review) 855 ("s" "Signed-off" git-commit-signoff) 856 ("t" "Tested" git-commit-test)] 857 ["Insert ... by someone" 858 ("C-c" "Cc" git-commit-cc) 859 ("C-r" "Reported" git-commit-reported) 860 ("C-i" "Suggested" git-commit-suggested) 861 ("C-a" "Co-authored" git-commit-co-authored) 862 ("C-d" "Co-developed" git-commit-co-developed)]]) 863 864 (defun git-commit-ack (name mail) 865 "Insert a trailer acknowledging that you have looked at the commit." 866 (interactive (git-commit-get-ident "Acked-by")) 867 (git-commit--insert-ident-trailer "Acked-by" name mail)) 868 869 (defun git-commit-modified (name mail) 870 "Insert a trailer to signal that you have modified the commit." 871 (interactive (git-commit-get-ident "Modified-by")) 872 (git-commit--insert-ident-trailer "Modified-by" name mail)) 873 874 (defun git-commit-review (name mail) 875 "Insert a trailer acknowledging that you have reviewed the commit. 876 With a prefix argument, prompt for another person who performed a 877 review." 878 (interactive (git-commit-get-ident "Reviewed-by")) 879 (git-commit--insert-ident-trailer "Reviewed-by" name mail)) 880 881 (defun git-commit-signoff (name mail) 882 "Insert a trailer to sign off the commit. 883 With a prefix argument, prompt for another person who signed off." 884 (interactive (git-commit-get-ident "Signed-off-by")) 885 (git-commit--insert-ident-trailer "Signed-off-by" name mail)) 886 887 (defun git-commit-test (name mail) 888 "Insert a trailer acknowledging that you have tested the commit. 889 With a prefix argument, prompt for another person who tested." 890 (interactive (git-commit-get-ident "Tested-by")) 891 (git-commit--insert-ident-trailer "Tested-by" name mail)) 892 893 (defun git-commit-cc (name mail) 894 "Insert a trailer mentioning someone who might be interested." 895 (interactive (git-commit-read-ident "Cc")) 896 (git-commit--insert-ident-trailer "Cc" name mail)) 897 898 (defun git-commit-reported (name mail) 899 "Insert a trailer mentioning the person who reported the issue." 900 (interactive (git-commit-read-ident "Reported-by")) 901 (git-commit--insert-ident-trailer "Reported-by" name mail)) 902 903 (defun git-commit-suggested (name mail) 904 "Insert a trailer mentioning the person who suggested the change." 905 (interactive (git-commit-read-ident "Suggested-by")) 906 (git-commit--insert-ident-trailer "Suggested-by" name mail)) 907 908 (defun git-commit-co-authored (name mail) 909 "Insert a trailer mentioning the person who co-authored the commit." 910 (interactive (git-commit-read-ident "Co-authored-by")) 911 (git-commit--insert-ident-trailer "Co-authored-by" name mail)) 912 913 (defun git-commit-co-developed (name mail) 914 "Insert a trailer mentioning the person who co-developed the commit." 915 (interactive (git-commit-read-ident "Co-developed-by")) 916 (git-commit--insert-ident-trailer "Co-developed-by" name mail)) 917 918 (defun git-commit-get-ident (&optional prompt) 919 "Return name and email of the user or read another name and email. 920 If PROMPT and `current-prefix-arg' are both non-nil, read name 921 and email using `git-commit-read-ident' (which see), otherwise 922 return name and email of the current user (you)." 923 (if (and prompt current-prefix-arg) 924 (git-commit-read-ident prompt) 925 (list (or (getenv "GIT_AUTHOR_NAME") 926 (getenv "GIT_COMMITTER_NAME") 927 (with-demoted-errors "Error running 'git config user.name': %S" 928 (magit-get "user.name")) 929 user-full-name 930 (read-string "Name: ")) 931 (or (getenv "GIT_AUTHOR_EMAIL") 932 (getenv "GIT_COMMITTER_EMAIL") 933 (getenv "EMAIL") 934 (with-demoted-errors "Error running 'git config user.email': %S" 935 (magit-get "user.email")) 936 (read-string "Email: "))))) 937 938 (defalias 'git-commit-self-ident #'git-commit-get-ident) 939 940 (defvar git-commit-read-ident-history nil) 941 942 (defun git-commit-read-ident (prompt) 943 "Read a name and email, prompting with PROMPT, and return them. 944 Read them using a single prompt, offering past commit authors as 945 completion candidates. The input must have the form \"NAME <EMAIL>\"." 946 (let ((str (magit-completing-read 947 prompt 948 (sort (delete-dups 949 (magit-git-lines "log" "-n9999" "--format=%aN <%ae>")) 950 #'string<) 951 nil nil nil 'git-commit-read-ident-history))) 952 (save-match-data 953 (if (string-match "\\`\\([^<]+\\) *<\\([^>]+\\)>\\'" str) 954 (list (save-match-data (string-trim (match-string 1 str))) 955 (string-trim (match-string 2 str))) 956 (user-error "Invalid input"))))) 957 958 (defun git-commit--insert-ident-trailer (trailer name email) 959 (git-commit--insert-trailer trailer (format "%s <%s>" name email))) 960 961 (defun git-commit--insert-trailer (trailer value) 962 (save-excursion 963 (let ((string (format "%s: %s" trailer value)) 964 (leading-comment-end nil)) 965 ;; Make sure we skip forward past any leading comments. 966 (goto-char (point-min)) 967 (while (looking-at comment-start) 968 (forward-line)) 969 (setq leading-comment-end (point)) 970 (goto-char (point-max)) 971 (cond 972 ;; Look backwards for existing trailers. 973 ((re-search-backward (git-commit--trailer-regexp) nil t) 974 (end-of-line) 975 (insert ?\n string) 976 (unless (= (char-after) ?\n) 977 (insert ?\n))) 978 ;; Or place the new trailer right before the first non-leading 979 ;; comments. 980 (t 981 (while (re-search-backward (concat "^" comment-start) 982 leading-comment-end t)) 983 (unless (looking-back "\n\n" nil) 984 (insert ?\n)) 985 (insert string ?\n)))) 986 (unless (or (eobp) (= (char-after) ?\n)) 987 (insert ?\n)))) 988 989 ;;; Font-Lock 990 991 (defvar-local git-commit-need-summary-line t 992 "Whether the text should have a heading that is separated from the body. 993 994 For commit messages that is a convention that should not 995 be violated. For notes it is up to the user. If you do 996 not want to insist on an empty second line here, then use 997 something like: 998 999 (add-hook \\='git-commit-setup-hook 1000 (lambda () 1001 (when (equal (file-name-nondirectory (buffer-file-name)) 1002 \"NOTES_EDITMSG\") 1003 (setq git-commit-need-summary-line nil))))") 1004 1005 (defun git-commit--trailer-regexp () 1006 (format 1007 "^\\(?:\\(%s:\\)\\( .*\\)\\|\\([-a-zA-Z]+\\): \\([^<\n]+? <[^>\n]+>\\)\\)" 1008 (regexp-opt git-commit-trailers))) 1009 1010 (defun git-commit-summary-regexp () 1011 (if git-commit-need-summary-line 1012 (concat 1013 ;; Leading empty lines and comments 1014 (format "\\`\\(?:^\\(?:\\s-*\\|%s.*\\)\n\\)*" comment-start) 1015 ;; Summary line 1016 (format "\\(.\\{0,%d\\}\\)\\(.*\\)" git-commit-summary-max-length) 1017 ;; Non-empty non-comment second line 1018 (format "\\(?:\n%s\\|\n\\(.+\\)\\)?" comment-start)) 1019 "\\(EASTER\\) \\(EGG\\)")) 1020 1021 (defun git-commit-extend-region-summary-line () 1022 "Identify the multiline summary-regexp construct. 1023 Added to `font-lock-extend-region-functions'." 1024 (save-excursion 1025 (save-match-data 1026 (goto-char (point-min)) 1027 (when (looking-at (git-commit-summary-regexp)) 1028 (let ((summary-beg (match-beginning 0)) 1029 (summary-end (match-end 0))) 1030 (when (or (< summary-beg font-lock-beg summary-end) 1031 (< summary-beg font-lock-end summary-end)) 1032 (setq font-lock-beg (min font-lock-beg summary-beg)) 1033 (setq font-lock-end (max font-lock-end summary-end)))))))) 1034 1035 (defvar-local git-commit--branch-name-regexp nil) 1036 1037 (defconst git-commit-comment-headings 1038 '("Changes to be committed:" 1039 "Untracked files:" 1040 "Changed but not updated:" 1041 "Changes not staged for commit:" 1042 "Unmerged paths:" 1043 "Author:" 1044 "Date:") 1045 "Also fontified outside of comments in `git-commit-font-lock-keywords-2'.") 1046 1047 (defconst git-commit-font-lock-keywords-1 1048 '(;; Trailers 1049 (eval . `(,(git-commit--trailer-regexp) 1050 (1 'git-commit-trailer-token) 1051 (2 'git-commit-trailer-value) 1052 (3 'git-commit-trailer-token) 1053 (4 'git-commit-trailer-value))) 1054 ;; Summary 1055 (eval . `(,(git-commit-summary-regexp) 1056 (1 'git-commit-summary))) 1057 ;; - Keyword [aka "text in brackets"] (overrides summary) 1058 ("\\[[^][]+?\\]" 1059 (0 'git-commit-keyword t)) 1060 ;; - Non-empty second line (overrides summary and note) 1061 (eval . `(,(git-commit-summary-regexp) 1062 (2 'git-commit-overlong-summary t t) 1063 (3 'git-commit-nonempty-second-line t t))))) 1064 1065 (defconst git-commit-font-lock-keywords-2 1066 `(,@git-commit-font-lock-keywords-1 1067 ;; Comments 1068 (eval . `(,(format "^%s.*" comment-start) 1069 (0 'font-lock-comment-face append))) 1070 (eval . `(,(format "^%s On branch \\(.*\\)" comment-start) 1071 (1 'git-commit-comment-branch-local t))) 1072 (eval . `(,(format "^%s \\(HEAD\\) detached at" comment-start) 1073 (1 'git-commit-comment-detached t))) 1074 (eval . `(,(format "^%s %s" comment-start 1075 (regexp-opt git-commit-comment-headings t)) 1076 (1 'git-commit-comment-heading t))) 1077 (eval . `(,(format "^%s\t\\(?:\\([^:\n]+\\):\\s-+\\)?\\(.*\\)" comment-start) 1078 (1 'git-commit-comment-action t t) 1079 (2 'git-commit-comment-file t))) 1080 ;; "commit HASH" 1081 (eval . '("^commit [[:alnum:]]+$" 1082 (0 'git-commit-trailer-value))) 1083 ;; `git-commit-comment-headings' (but not in commented lines) 1084 (eval . `(,(format "\\(?:^%s[[:blank:]]+.+$\\)" 1085 (regexp-opt git-commit-comment-headings)) 1086 (0 'git-commit-trailer-value))))) 1087 1088 (defconst git-commit-font-lock-keywords-3 1089 `(,@git-commit-font-lock-keywords-2 1090 ;; More comments 1091 (eval 1092 ;; Your branch is ahead of 'master' by 3 commits. 1093 ;; Your branch is behind 'master' by 2 commits, and can be fast-forwarded. 1094 . `(,(format 1095 "^%s Your branch is \\(?:ahead\\|behind\\) of '%s' by \\([0-9]*\\)" 1096 comment-start git-commit--branch-name-regexp) 1097 (1 'git-commit-comment-branch-local t) 1098 (2 'git-commit-comment-branch-remote t) 1099 (3 'bold t))) 1100 (eval 1101 ;; Your branch is up to date with 'master'. 1102 ;; Your branch and 'master' have diverged, 1103 . `(,(format 1104 "^%s Your branch \\(?:is up[- ]to[- ]date with\\|and\\) '%s'" 1105 comment-start git-commit--branch-name-regexp) 1106 (1 'git-commit-comment-branch-local t) 1107 (2 'git-commit-comment-branch-remote t))) 1108 (eval 1109 ;; and have 1 and 2 different commits each, respectively. 1110 . `(,(format 1111 "^%s and have \\([0-9]*\\) and \\([0-9]*\\) commits each" 1112 comment-start) 1113 (1 'bold t) 1114 (2 'bold t))))) 1115 1116 (defvar git-commit-font-lock-keywords git-commit-font-lock-keywords-3 1117 "Font-Lock keywords for Git-Commit mode.") 1118 1119 (defun git-commit-setup-font-lock () 1120 (with-demoted-errors "Error running git-commit-setup-font-lock: %S" 1121 (let ((table (make-syntax-table (syntax-table)))) 1122 (when comment-start 1123 (modify-syntax-entry (string-to-char comment-start) "." table)) 1124 (modify-syntax-entry ?# "." table) 1125 (modify-syntax-entry ?\" "." table) 1126 (modify-syntax-entry ?\' "." table) 1127 (modify-syntax-entry ?` "." table) 1128 (set-syntax-table table)) 1129 (setq-local comment-start (or (magit-get "core.commentchar") "#")) 1130 (setq-local comment-start-skip (format "^%s+[\s\t]*" comment-start)) 1131 (setq-local comment-end "") 1132 (setq-local comment-end-skip "\n") 1133 (setq-local comment-use-syntax nil) 1134 (when (and (derived-mode-p 'markdown-mode) 1135 (fboundp 'markdown-fill-paragraph)) 1136 (setq-local fill-paragraph-function 1137 (lambda (&optional justify) 1138 (and (not (= (char-after (line-beginning-position)) 1139 (aref comment-start 0))) 1140 (markdown-fill-paragraph justify))))) 1141 (setq-local git-commit--branch-name-regexp 1142 ;; When using cygwin git, we may end up in a 1143 ;; non-existing directory, which would cause 1144 ;; any git calls to signal an error. 1145 (if (file-accessible-directory-p default-directory) 1146 ;; Font-Lock wants every submatch to succeed, so 1147 ;; also match the empty string. Avoid listing 1148 ;; remote branches and using `regexp-quote', 1149 ;; because in repositories that have thousands of 1150 ;; branches that would be very slow. See #4353. 1151 (format "\\(\\(?:%s\\)\\|\\)\\([^']+\\)" 1152 (string-join (magit-list-local-branch-names) "\\|")) 1153 "\\([^']*\\)")) 1154 (setq-local font-lock-multiline t) 1155 (add-hook 'font-lock-extend-region-functions 1156 #'git-commit-extend-region-summary-line 1157 t t) 1158 (font-lock-add-keywords nil git-commit-font-lock-keywords))) 1159 1160 (defun git-commit-propertize-diff () 1161 (require 'diff-mode) 1162 (save-excursion 1163 (goto-char (point-min)) 1164 (when (re-search-forward "^diff --git" nil t) 1165 (beginning-of-line) 1166 (let ((buffer (current-buffer))) 1167 (insert 1168 (with-temp-buffer 1169 (insert 1170 (with-current-buffer buffer 1171 (prog1 (buffer-substring-no-properties (point) (point-max)) 1172 (delete-region (point) (point-max))))) 1173 (let ((diff-default-read-only nil)) 1174 (diff-mode)) 1175 (let ((font-lock-verbose nil) 1176 (font-lock-support-mode nil)) 1177 (font-lock-ensure)) 1178 (let ((pos (point-min))) 1179 (while-let ((next (next-single-property-change pos 'face))) 1180 (put-text-property pos next 'font-lock-face 1181 (get-text-property pos 'face)) 1182 (setq pos next)) 1183 (put-text-property pos (point-max) 'font-lock-face 1184 (get-text-property pos 'face))) 1185 (buffer-string))))))) 1186 1187 ;;; Elisp Text Mode 1188 1189 (define-derived-mode git-commit-elisp-text-mode text-mode "ElText" 1190 "Major mode for editing commit messages of elisp projects. 1191 This is intended for use as `git-commit-major-mode' for projects 1192 that expect `symbols' to look like this. I.e., like they look in 1193 Elisp doc-strings, including this one. Unlike in doc-strings, 1194 \"strings\" also look different than the other text." 1195 (setq font-lock-defaults '(git-commit-elisp-text-mode-keywords))) 1196 1197 (defvar git-commit-elisp-text-mode-keywords 1198 `((,(concat "[`‘]\\(" lisp-mode-symbol-regexp "\\)['’]") 1199 (1 font-lock-constant-face prepend)) 1200 ("\"[^\"]*\"" (0 font-lock-string-face prepend)))) 1201 1202 ;;; _ 1203 1204 (define-obsolete-function-alias 1205 'git-commit-insert-pseudo-header 1206 'git-commit-insert-trailer 1207 "git-commit 4.0.0") 1208 (define-obsolete-function-alias 1209 'git-commit-insert-header 1210 'git-commit--insert-ident-trailer 1211 "git-commit 4.0.0") 1212 (define-obsolete-face-alias 1213 'git-commit-pseudo-header 1214 'git-commit-trailer-value 1215 "git-commit 4.0.0") 1216 (define-obsolete-face-alias 1217 'git-commit-known-pseudo-header 1218 'git-commit-trailer-token 1219 "git-commit 4.0.0") 1220 1221 (provide 'git-commit) 1222 ;;; git-commit.el ends here