config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

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