Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

982 lines
38 KiB

4 years ago
  1. ;;; git-commit.el --- Edit Git commit messages -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2010-2019 The Magit Project Contributors
  3. ;;
  4. ;; You should have received a copy of the AUTHORS.md file which
  5. ;; lists all contributors. If not, see http://magit.vc/authors.
  6. ;; Authors: Jonas Bernoulli <jonas@bernoul.li>
  7. ;; Sebastian Wiesner <lunaryorn@gmail.com>
  8. ;; Florian Ragwitz <rafl@debian.org>
  9. ;; Marius Vollmer <marius.vollmer@gmail.com>
  10. ;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
  11. ;; Package-Requires: ((emacs "25.1") (dash "20180910") (with-editor "20181103"))
  12. ;; Package-Version: 20190717.29
  13. ;; Keywords: git tools vc
  14. ;; Homepage: https://github.com/magit/magit
  15. ;; This file is not part of GNU Emacs.
  16. ;; This file is free software; you can redistribute it and/or modify
  17. ;; it under the terms of the GNU General Public License as published by
  18. ;; the Free Software Foundation; either version 3, or (at your option)
  19. ;; any later version.
  20. ;; This file is distributed in the hope that it will be useful,
  21. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. ;; GNU General Public License for more details.
  24. ;; You should have received a copy of the GNU General Public License
  25. ;; along with this file. If not, see <http://www.gnu.org/licenses/>.
  26. ;;; Commentary:
  27. ;; This package assists the user in writing good Git commit messages.
  28. ;; While Git allows for the message to be provided on the command
  29. ;; line, it is preferable to tell Git to create the commit without
  30. ;; actually passing it a message. Git then invokes the `$GIT_EDITOR'
  31. ;; (or if that is undefined `$EDITOR') asking the user to provide the
  32. ;; message by editing the file ".git/COMMIT_EDITMSG" (or another file
  33. ;; in that directory, e.g. ".git/MERGE_MSG" for merge commits).
  34. ;; When `global-git-commit-mode' is enabled, which it is by default,
  35. ;; then opening such a file causes the features described below, to
  36. ;; be enabled in that buffer. Normally this would be done using a
  37. ;; major-mode but to allow the use of any major-mode, as the user sees
  38. ;; fit, it is done here by running a setup function, which among other
  39. ;; things turns on the preferred major-mode, by default `text-mode'.
  40. ;; Git waits for the `$EDITOR' to finish and then either creates the
  41. ;; commit using the contents of the file as commit message, or, if the
  42. ;; editor process exited with a non-zero exit status, aborts without
  43. ;; creating a commit. Unfortunately Emacsclient (which is what Emacs
  44. ;; users should be using as `$EDITOR' or at least as `$GIT_EDITOR')
  45. ;; does not differentiate between "successfully" editing a file and
  46. ;; aborting; not out of the box that is.
  47. ;; By making use of the `with-editor' package this package provides
  48. ;; both ways of finish an editing session. In either case the file
  49. ;; is saved, but Emacseditor's exit code differs.
  50. ;;
  51. ;; C-c C-c Finish the editing session successfully by returning
  52. ;; with exit code 0. Git then creates the commit using
  53. ;; the message it finds in the file.
  54. ;;
  55. ;; C-c C-k Aborts the edit editing session by returning with exit
  56. ;; code 1. Git then aborts the commit.
  57. ;; Aborting the commit does not cause the message to be lost, but
  58. ;; relying solely on the file not being tampered with is risky. This
  59. ;; package additionally stores all aborted messages for the duration
  60. ;; of the current session (i.e. until you close Emacs). To get back
  61. ;; an aborted message use M-p and M-n while editing a message.
  62. ;;
  63. ;; M-p Replace the buffer contents with the previous message
  64. ;; from the message ring. Of course only after storing
  65. ;; the current content there too.
  66. ;;
  67. ;; M-n Replace the buffer contents with the next message from
  68. ;; the message ring, after storing the current content.
  69. ;; Some support for pseudo headers as used in some projects is
  70. ;; provided by these commands:
  71. ;;
  72. ;; C-c C-s Insert a Signed-off-by header.
  73. ;; C-c C-a Insert a Acked-by header.
  74. ;; C-c C-m Insert a Modified-by header.
  75. ;; C-c C-t Insert a Tested-by header.
  76. ;; C-c C-r Insert a Reviewed-by header.
  77. ;; C-c C-o Insert a Cc header.
  78. ;; C-c C-p Insert a Reported-by header.
  79. ;; C-c M-s Insert a Suggested-by header.
  80. ;; When Git requests a commit message from the user, it does so by
  81. ;; having her edit a file which initially contains some comments,
  82. ;; instructing her what to do, and providing useful information, such
  83. ;; as which files were modified. These comments, even when left
  84. ;; intact by the user, do not become part of the commit message. This
  85. ;; package ensures these comments are propertizes as such and further
  86. ;; prettifies them by using different faces for various parts, such as
  87. ;; files.
  88. ;; Finally this package highlights style errors, like lines that are
  89. ;; too long, or when the second line is not empty. It may even nag
  90. ;; you when you attempt to finish the commit without having fixed
  91. ;; these issues. The style checks and many other settings can easily
  92. ;; be configured:
  93. ;;
  94. ;; M-x customize-group RET git-commit RET
  95. ;;; Code:
  96. ;;;; Dependencies
  97. (require 'dash)
  98. (require 'log-edit)
  99. (require 'magit-git nil t)
  100. (require 'magit-utils nil t)
  101. (require 'ring)
  102. (require 'server)
  103. (require 'with-editor)
  104. (eval-when-compile (require 'recentf))
  105. ;;;; Declarations
  106. (defvar diff-default-read-only)
  107. (defvar flyspell-generic-check-word-predicate)
  108. (defvar font-lock-beg)
  109. (defvar font-lock-end)
  110. (declare-function magit-expand-git-file-name "magit-git" (filename))
  111. (declare-function magit-list-local-branch-names "magit-git" ())
  112. (declare-function magit-list-remote-branch-names "magit-git"
  113. (&optional remote relative))
  114. ;;; Options
  115. ;;;; Variables
  116. (defgroup git-commit nil
  117. "Edit Git commit messages."
  118. :prefix "git-commit-"
  119. :link '(info-link "(magit)Editing Commit Messages")
  120. :group 'tools)
  121. ;;;###autoload
  122. (define-minor-mode global-git-commit-mode
  123. "Edit Git commit messages.
  124. This global mode arranges for `git-commit-setup' to be called
  125. when a Git commit message file is opened. That usually happens
  126. when Git uses the Emacsclient as $GIT_EDITOR to have the user
  127. provide such a commit message."
  128. :group 'git-commit
  129. :type 'boolean
  130. :global t
  131. :init-value t
  132. :initialize (lambda (symbol exp)
  133. (custom-initialize-default symbol exp)
  134. (when global-git-commit-mode
  135. (add-hook 'find-file-hook 'git-commit-setup-check-buffer)))
  136. (if global-git-commit-mode
  137. (add-hook 'find-file-hook 'git-commit-setup-check-buffer)
  138. (remove-hook 'find-file-hook 'git-commit-setup-check-buffer)))
  139. (defcustom git-commit-major-mode 'text-mode
  140. "Major mode used to edit Git commit messages.
  141. The major mode configured here is turned on by the minor mode
  142. `git-commit-mode'."
  143. :group 'git-commit
  144. :type '(choice (function-item text-mode)
  145. (const :tag "No major mode")))
  146. (defcustom git-commit-setup-hook
  147. '(git-commit-save-message
  148. git-commit-setup-changelog-support
  149. git-commit-turn-on-auto-fill
  150. git-commit-propertize-diff
  151. bug-reference-mode
  152. with-editor-usage-message)
  153. "Hook run at the end of `git-commit-setup'."
  154. :group 'git-commit
  155. :type 'hook
  156. :get (and (featurep 'magit-utils) 'magit-hook-custom-get)
  157. :options '(git-commit-save-message
  158. git-commit-setup-changelog-support
  159. git-commit-turn-on-auto-fill
  160. git-commit-turn-on-flyspell
  161. git-commit-propertize-diff
  162. bug-reference-mode
  163. with-editor-usage-message))
  164. (defcustom git-commit-post-finish-hook nil
  165. "Hook run after the user finished writing a commit message.
  166. \\<with-editor-mode-map>\
  167. This hook is only run after pressing \\[with-editor-finish] in a buffer used
  168. to edit a commit message. If a commit is created without the
  169. user typing a message into a buffer, then this hook is not run.
  170. This hook is not run until the new commit has been created. If
  171. doing so takes Git longer than one second, then this hook isn't
  172. run at all. For certain commands such as `magit-rebase-continue'
  173. this hook is never run because doing so would lead to a race
  174. condition.
  175. This hook is only run if `magit' is available.
  176. Also see `magit-post-commit-hook'."
  177. :group 'git-commit
  178. :type 'hook
  179. :get (and (featurep 'magit-utils) 'magit-hook-custom-get))
  180. (defcustom git-commit-finish-query-functions
  181. '(git-commit-check-style-conventions)
  182. "List of functions called to query before performing commit.
  183. The commit message buffer is current while the functions are
  184. called. If any of them returns nil, then the commit is not
  185. performed and the buffer is not killed. The user should then
  186. fix the issue and try again.
  187. The functions are called with one argument. If it is non-nil,
  188. then that indicates that the user used a prefix argument to
  189. force finishing the session despite issues. Functions should
  190. usually honor this wish and return non-nil."
  191. :options '(git-commit-check-style-conventions)
  192. :type 'hook
  193. :group 'git-commit)
  194. (defcustom git-commit-style-convention-checks '(non-empty-second-line)
  195. "List of checks performed by `git-commit-check-style-conventions'.
  196. Valid members are `non-empty-second-line' and `overlong-summary-line'.
  197. That function is a member of `git-commit-finish-query-functions'."
  198. :options '(non-empty-second-line overlong-summary-line)
  199. :type '(list :convert-widget custom-hook-convert-widget)
  200. :group 'git-commit)
  201. (defcustom git-commit-summary-max-length 68
  202. "Column beyond which characters in the summary lines are highlighted.
  203. The highlighting indicates that the summary is getting too long
  204. by some standards. It does in no way imply that going over the
  205. limit a few characters or in some cases even many characters is
  206. anything that deserves shaming. It's just a friendly reminder
  207. that if you can make the summary shorter, then you might want
  208. to consider doing so."
  209. :group 'git-commit
  210. :safe 'numberp
  211. :type 'number)
  212. (defcustom git-commit-fill-column nil
  213. "Override `fill-column' in commit message buffers.
  214. If this is non-nil, then it should be an integer. If that is the
  215. case and the buffer-local value of `fill-column' is not already
  216. set by the time `git-commit-turn-on-auto-fill' is called as a
  217. member of `git-commit-setup-hook', then that function sets the
  218. buffer-local value of `fill-column' to the value of this option.
  219. This option exists mostly for historic reasons. If you are not
  220. already using it, then you probably shouldn't start doing so."
  221. :group 'git-commit
  222. :safe 'numberp
  223. :type '(choice (const :tag "use regular fill-column")
  224. number))
  225. (make-obsolete-variable 'git-commit-fill-column 'fill-column
  226. "Magit 2.11.0" 'set)
  227. (defcustom git-commit-known-pseudo-headers
  228. '("Signed-off-by" "Acked-by" "Modified-by" "Cc"
  229. "Suggested-by" "Reported-by" "Tested-by" "Reviewed-by")
  230. "A list of Git pseudo headers to be highlighted."
  231. :group 'git-commit
  232. :safe (lambda (val) (and (listp val) (-all-p 'stringp val)))
  233. :type '(repeat string))
  234. ;;;; Faces
  235. (defgroup git-commit-faces nil
  236. "Faces used for highlighting Git commit messages."
  237. :prefix "git-commit-"
  238. :group 'git-commit
  239. :group 'faces)
  240. (defface git-commit-summary
  241. '((t :inherit font-lock-type-face))
  242. "Face used for the summary in commit messages."
  243. :group 'git-commit-faces)
  244. (defface git-commit-overlong-summary
  245. '((t :inherit font-lock-warning-face))
  246. "Face used for the tail of overlong commit message summaries."
  247. :group 'git-commit-faces)
  248. (defface git-commit-nonempty-second-line
  249. '((t :inherit font-lock-warning-face))
  250. "Face used for non-whitespace on the second line of commit messages."
  251. :group 'git-commit-faces)
  252. (defface git-commit-keyword
  253. '((t :inherit font-lock-string-face))
  254. "Face used for keywords in commit messages.
  255. In this context a \"keyword\" is text surrounded be brackets."
  256. :group 'git-commit-faces)
  257. (define-obsolete-face-alias 'git-commit-note
  258. 'git-commit-keyword "Git-Commit 2.91.0")
  259. (defface git-commit-pseudo-header
  260. '((t :inherit font-lock-string-face))
  261. "Face used for pseudo headers in commit messages."
  262. :group 'git-commit-faces)
  263. (defface git-commit-known-pseudo-header
  264. '((t :inherit font-lock-keyword-face))
  265. "Face used for the keywords of known pseudo headers in commit messages."
  266. :group 'git-commit-faces)
  267. (defface git-commit-comment-branch-local
  268. (if (featurep 'magit)
  269. '((t :inherit magit-branch-local))
  270. '((t :inherit font-lock-variable-name-face)))
  271. "Face used for names of local branches in commit message comments."
  272. :group 'git-commit-faces)
  273. (define-obsolete-face-alias 'git-commit-comment-branch
  274. 'git-commit-comment-branch-local "Git-Commit 2.12.0")
  275. (defface git-commit-comment-branch-remote
  276. (if (featurep 'magit)
  277. '((t :inherit magit-branch-remote))
  278. '((t :inherit font-lock-variable-name-face)))
  279. "Face used for names of remote branches in commit message comments.
  280. This is only used if Magit is available."
  281. :group 'git-commit-faces)
  282. (defface git-commit-comment-detached
  283. '((t :inherit git-commit-comment-branch-local))
  284. "Face used for detached `HEAD' in commit message comments."
  285. :group 'git-commit-faces)
  286. (defface git-commit-comment-heading
  287. '((t :inherit git-commit-known-pseudo-header))
  288. "Face used for headings in commit message comments."
  289. :group 'git-commit-faces)
  290. (defface git-commit-comment-file
  291. '((t :inherit git-commit-pseudo-header))
  292. "Face used for file names in commit message comments."
  293. :group 'git-commit-faces)
  294. (defface git-commit-comment-action
  295. '((t :inherit bold))
  296. "Face used for actions in commit message comments."
  297. :group 'git-commit-faces)
  298. ;;; Keymap
  299. (defvar git-commit-mode-map
  300. (let ((map (make-sparse-keymap)))
  301. (cond ((featurep 'jkl)
  302. (define-key map (kbd "C-M-i") 'git-commit-prev-message)
  303. (define-key map (kbd "C-M-k") 'git-commit-next-message))
  304. (t
  305. (define-key map (kbd "M-p") 'git-commit-prev-message)
  306. (define-key map (kbd "M-n") 'git-commit-next-message)
  307. ;; Old bindings to avoid confusion
  308. (define-key map (kbd "C-c C-x a") 'git-commit-ack)
  309. (define-key map (kbd "C-c C-x i") 'git-commit-suggested)
  310. (define-key map (kbd "C-c C-x m") 'git-commit-modified)
  311. (define-key map (kbd "C-c C-x o") 'git-commit-cc)
  312. (define-key map (kbd "C-c C-x p") 'git-commit-reported)
  313. (define-key map (kbd "C-c C-x r") 'git-commit-review)
  314. (define-key map (kbd "C-c C-x s") 'git-commit-signoff)
  315. (define-key map (kbd "C-c C-x t") 'git-commit-test)))
  316. (define-key map (kbd "C-c C-a") 'git-commit-ack)
  317. (define-key map (kbd "C-c C-i") 'git-commit-suggested)
  318. (define-key map (kbd "C-c C-m") 'git-commit-modified)
  319. (define-key map (kbd "C-c C-o") 'git-commit-cc)
  320. (define-key map (kbd "C-c C-p") 'git-commit-reported)
  321. (define-key map (kbd "C-c C-r") 'git-commit-review)
  322. (define-key map (kbd "C-c C-s") 'git-commit-signoff)
  323. (define-key map (kbd "C-c C-t") 'git-commit-test)
  324. (define-key map (kbd "C-c M-s") 'git-commit-save-message)
  325. map)
  326. "Key map used by `git-commit-mode'.")
  327. ;;; Menu
  328. (require 'easymenu)
  329. (easy-menu-define git-commit-mode-menu git-commit-mode-map
  330. "Git Commit Mode Menu"
  331. '("Commit"
  332. ["Previous" git-commit-prev-message t]
  333. ["Next" git-commit-next-message t]
  334. "-"
  335. ["Ack" git-commit-ack :active t
  336. :help "Insert an 'Acked-by' header"]
  337. ["Sign-Off" git-commit-signoff :active t
  338. :help "Insert a 'Signed-off-by' header"]
  339. ["Modified-by" git-commit-modified :active t
  340. :help "Insert a 'Modified-by' header"]
  341. ["Tested-by" git-commit-test :active t
  342. :help "Insert a 'Tested-by' header"]
  343. ["Reviewed-by" git-commit-review :active t
  344. :help "Insert a 'Reviewed-by' header"]
  345. ["CC" git-commit-cc t
  346. :help "Insert a 'Cc' header"]
  347. ["Reported" git-commit-reported :active t
  348. :help "Insert a 'Reported-by' header"]
  349. ["Suggested" git-commit-suggested t
  350. :help "Insert a 'Suggested-by' header"]
  351. "-"
  352. ["Save" git-commit-save-message t]
  353. ["Cancel" with-editor-cancel t]
  354. ["Commit" with-editor-finish t]))
  355. ;;; Hooks
  356. ;;;###autoload
  357. (defconst git-commit-filename-regexp "/\\(\
  358. \\(\\(COMMIT\\|NOTES\\|PULLREQ\\|MERGEREQ\\|TAG\\)_EDIT\\|MERGE_\\|\\)MSG\
  359. \\|\\(BRANCH\\|EDIT\\)_DESCRIPTION\\)\\'")
  360. (eval-after-load 'recentf
  361. '(add-to-list 'recentf-exclude git-commit-filename-regexp))
  362. (add-to-list 'with-editor-file-name-history-exclude git-commit-filename-regexp)
  363. (defun git-commit-setup-font-lock-in-buffer ()
  364. (and buffer-file-name
  365. (string-match-p git-commit-filename-regexp buffer-file-name)
  366. (git-commit-setup-font-lock)))
  367. (add-hook 'after-change-major-mode-hook 'git-commit-setup-font-lock-in-buffer)
  368. ;;;###autoload
  369. (defun git-commit-setup-check-buffer ()
  370. (and buffer-file-name
  371. (string-match-p git-commit-filename-regexp buffer-file-name)
  372. (git-commit-setup)))
  373. (defvar git-commit-mode)
  374. (defun git-commit-file-not-found ()
  375. ;; cygwin git will pass a cygwin path (/cygdrive/c/foo/.git/...),
  376. ;; try to handle this in window-nt Emacs.
  377. (--when-let
  378. (and (or (string-match-p git-commit-filename-regexp buffer-file-name)
  379. (and (boundp 'git-rebase-filename-regexp)
  380. (string-match-p git-rebase-filename-regexp
  381. buffer-file-name)))
  382. (not (file-accessible-directory-p
  383. (file-name-directory buffer-file-name)))
  384. (if (require 'magit-git nil t)
  385. ;; Emacs prepends a "c:".
  386. (magit-expand-git-file-name (substring buffer-file-name 2))
  387. ;; Fallback if we can't load `magit-git'.
  388. (and (string-match "\\`[a-z]:/\\(cygdrive/\\)?\\([a-z]\\)/\\(.*\\)"
  389. buffer-file-name)
  390. (concat (match-string 2 buffer-file-name) ":/"
  391. (match-string 3 buffer-file-name)))))
  392. (when (file-accessible-directory-p (file-name-directory it))
  393. (let ((inhibit-read-only t))
  394. (insert-file-contents it t)
  395. t))))
  396. (when (eq system-type 'windows-nt)
  397. (add-hook 'find-file-not-found-functions #'git-commit-file-not-found))
  398. (defconst git-commit-usage-message "\
  399. Type \\[with-editor-finish] to finish, \
  400. \\[with-editor-cancel] to cancel, and \
  401. \\[git-commit-prev-message] and \\[git-commit-next-message] \
  402. to recover older messages")
  403. ;;;###autoload
  404. (defun git-commit-setup ()
  405. (when (fboundp 'magit-toplevel)
  406. ;; `magit-toplevel' is autoloaded and defined in magit-git.el,
  407. ;; That library declares this functions without loading
  408. ;; magit-process.el, which defines it.
  409. (require 'magit-process nil t))
  410. ;; Pretend that git-commit-mode is a major-mode,
  411. ;; so that directory-local settings can be used.
  412. (let ((default-directory
  413. (or (and (not (file-exists-p ".dir-locals.el"))
  414. ;; When $GIT_DIR/.dir-locals.el doesn't exist,
  415. ;; fallback to $GIT_WORK_TREE/.dir-locals.el,
  416. ;; because the maintainer can use the latter
  417. ;; to enforce conventions, while s/he has no
  418. ;; control over the former.
  419. (fboundp 'magit-toplevel) ; silence byte-compiler
  420. (magit-toplevel))
  421. default-directory)))
  422. (let ((buffer-file-name nil) ; trick hack-dir-local-variables
  423. (major-mode 'git-commit-mode)) ; trick dir-locals-collect-variables
  424. (hack-dir-local-variables)
  425. (hack-local-variables-apply)))
  426. (when git-commit-major-mode
  427. (let ((auto-mode-alist (list (cons (concat "\\`"
  428. (regexp-quote buffer-file-name)
  429. "\\'")
  430. git-commit-major-mode)))
  431. ;; The major-mode hook might want to consult these minor
  432. ;; modes, while the minor-mode hooks might want to consider
  433. ;; the major mode.
  434. (git-commit-mode t)
  435. (with-editor-mode t))
  436. (normal-mode t)))
  437. ;; Show our own message using our hook.
  438. (setq with-editor-show-usage nil)
  439. (setq with-editor-usage-message git-commit-usage-message)
  440. (unless with-editor-mode
  441. ;; Maybe already enabled when using `shell-command' or an Emacs shell.
  442. (with-editor-mode 1))
  443. (add-hook 'with-editor-finish-query-functions
  444. 'git-commit-finish-query-functions nil t)
  445. (add-hook 'with-editor-pre-finish-hook
  446. 'git-commit-save-message nil t)
  447. (add-hook 'with-editor-pre-cancel-hook
  448. 'git-commit-save-message nil t)
  449. (when (and (fboundp 'magit-rev-parse)
  450. (not (memq last-command
  451. '(magit-sequencer-continue
  452. magit-sequencer-skip
  453. magit-am-continue
  454. magit-am-skip
  455. magit-rebase-continue
  456. magit-rebase-skip))))
  457. (add-hook 'with-editor-post-finish-hook
  458. (apply-partially 'git-commit-run-post-finish-hook
  459. (magit-rev-parse "HEAD"))
  460. nil t)
  461. (when (fboundp 'magit-wip-maybe-add-commit-hook)
  462. (magit-wip-maybe-add-commit-hook)))
  463. (setq with-editor-cancel-message
  464. 'git-commit-cancel-message)
  465. (make-local-variable 'log-edit-comment-ring-index)
  466. (git-commit-mode 1)
  467. (git-commit-setup-font-lock)
  468. (when (boundp 'save-place)
  469. (setq save-place nil))
  470. (save-excursion
  471. (goto-char (point-min))
  472. (when (looking-at "\\`\\(\\'\\|\n[^\n]\\)")
  473. (open-line 1)))
  474. (with-demoted-errors "Error running git-commit-setup-hook: %S"
  475. (run-hooks 'git-commit-setup-hook))
  476. (set-buffer-modified-p nil))
  477. (defun git-commit-run-post-finish-hook (previous)
  478. (when (and git-commit-post-finish-hook
  479. (require 'magit nil t)
  480. (fboundp 'magit-rev-parse))
  481. (cl-block nil
  482. (let ((break (time-add (current-time)
  483. (seconds-to-time 1))))
  484. (while (equal (magit-rev-parse "HEAD") previous)
  485. (if (time-less-p (current-time) break)
  486. (sit-for 0.01)
  487. (message "No commit created after 1 second. Not running %s."
  488. 'git-commit-post-finish-hook)
  489. (cl-return))))
  490. (run-hooks 'git-commit-post-finish-hook))))
  491. (define-minor-mode git-commit-mode
  492. "Auxiliary minor mode used when editing Git commit messages.
  493. This mode is only responsible for setting up some key bindings.
  494. Don't use it directly, instead enable `global-git-commit-mode'."
  495. :lighter "")
  496. (put 'git-commit-mode 'permanent-local t)
  497. (defun git-commit-setup-changelog-support ()
  498. "Treat ChangeLog entries as unindented paragraphs."
  499. (setq-local fill-indent-according-to-mode t)
  500. (setq-local paragraph-start (concat paragraph-start "\\|\\*\\|(")))
  501. (defun git-commit-turn-on-auto-fill ()
  502. "Unconditionally turn on Auto Fill mode.
  503. If `git-commit-fill-column' is non-nil, and `fill-column'
  504. doesn't already have a buffer-local value, then set that
  505. to `git-commit-fill-column'."
  506. (when (and (numberp git-commit-fill-column)
  507. (not (local-variable-p 'fill-column)))
  508. (setq fill-column git-commit-fill-column))
  509. (setq-local comment-auto-fill-only-comments nil)
  510. (turn-on-auto-fill))
  511. (defun git-commit-turn-on-flyspell ()
  512. "Unconditionally turn on Flyspell mode.
  513. Also prevent comments from being checked and
  514. finally check current non-comment text."
  515. (require 'flyspell)
  516. (turn-on-flyspell)
  517. (setq flyspell-generic-check-word-predicate
  518. 'git-commit-flyspell-verify)
  519. (let ((end)
  520. (comment-start-regex (format "^\\(%s\\|$\\)" comment-start)))
  521. (save-excursion
  522. (goto-char (point-max))
  523. (while (and (not (bobp)) (looking-at comment-start-regex))
  524. (forward-line -1))
  525. (unless (looking-at comment-start-regex)
  526. (forward-line))
  527. (setq end (point)))
  528. (flyspell-region (point-min) end)))
  529. (defun git-commit-flyspell-verify ()
  530. (not (= (char-after (line-beginning-position))
  531. (aref comment-start 0))))
  532. (defun git-commit-finish-query-functions (force)
  533. (run-hook-with-args-until-failure
  534. 'git-commit-finish-query-functions force))
  535. (defun git-commit-check-style-conventions (force)
  536. "Check for violations of certain basic style conventions.
  537. For each violation ask the user if she wants to proceed anyway.
  538. Option `git-commit-check-style-conventions' controls which
  539. conventions are checked."
  540. (or force
  541. (save-excursion
  542. (goto-char (point-min))
  543. (re-search-forward (git-commit-summary-regexp) nil t)
  544. (if (equal (match-string 1) "")
  545. t ; Just try; we don't know whether --allow-empty-message was used.
  546. (and (or (not (memq 'overlong-summary-line
  547. git-commit-style-convention-checks))
  548. (equal (match-string 2) "")
  549. (y-or-n-p "Summary line is too long. Commit anyway? "))
  550. (or (not (memq 'non-empty-second-line
  551. git-commit-style-convention-checks))
  552. (not (match-string 3))
  553. (y-or-n-p "Second line is not empty. Commit anyway? ")))))))
  554. (defun git-commit-cancel-message ()
  555. (message
  556. (concat "Commit canceled"
  557. (and (memq 'git-commit-save-message with-editor-pre-cancel-hook)
  558. ". Message saved to `log-edit-comment-ring'"))))
  559. ;;; History
  560. (defun git-commit-prev-message (arg)
  561. "Cycle backward through message history, after saving current message.
  562. With a numeric prefix ARG, go back ARG comments."
  563. (interactive "*p")
  564. (when (and (git-commit-save-message) (> arg 0))
  565. (setq log-edit-comment-ring-index
  566. (log-edit-new-comment-index
  567. arg (ring-length log-edit-comment-ring))))
  568. (save-restriction
  569. (goto-char (point-min))
  570. (narrow-to-region (point)
  571. (if (re-search-forward (concat "^" comment-start) nil t)
  572. (max 1 (- (point) 2))
  573. (point-max)))
  574. (log-edit-previous-comment arg)))
  575. (defun git-commit-next-message (arg)
  576. "Cycle forward through message history, after saving current message.
  577. With a numeric prefix ARG, go forward ARG comments."
  578. (interactive "*p")
  579. (git-commit-prev-message (- arg)))
  580. (defun git-commit-save-message ()
  581. "Save current message to `log-edit-comment-ring'."
  582. (interactive)
  583. (--when-let (git-commit-buffer-message)
  584. (unless (ring-member log-edit-comment-ring it)
  585. (ring-insert log-edit-comment-ring it))))
  586. (defun git-commit-buffer-message ()
  587. (let ((flush (concat "^" comment-start))
  588. (str (buffer-substring-no-properties (point-min) (point-max))))
  589. (with-temp-buffer
  590. (insert str)
  591. (goto-char (point-min))
  592. (when (re-search-forward (concat flush " -+ >8 -+$") nil t)
  593. (delete-region (point-at-bol) (point-max)))
  594. (goto-char (point-min))
  595. (flush-lines flush)
  596. (goto-char (point-max))
  597. (unless (eq (char-before) ?\n)
  598. (insert ?\n))
  599. (setq str (buffer-string)))
  600. (unless (string-match "\\`[ \t\n\r]*\\'" str)
  601. (when (string-match "\\`\n\\{2,\\}" str)
  602. (setq str (replace-match "\n" t t str)))
  603. (when (string-match "\n\\{2,\\}\\'" str)
  604. (setq str (replace-match "\n" t t str)))
  605. str)))
  606. ;;; Headers
  607. (defun git-commit-ack (name mail)
  608. "Insert a header acknowledging that you have looked at the commit."
  609. (interactive (git-commit-self-ident))
  610. (git-commit-insert-header "Acked-by" name mail))
  611. (defun git-commit-modified (name mail)
  612. "Insert a header to signal that you have modified the commit."
  613. (interactive (git-commit-self-ident))
  614. (git-commit-insert-header "Modified-by" name mail))
  615. (defun git-commit-review (name mail)
  616. "Insert a header acknowledging that you have reviewed the commit."
  617. (interactive (git-commit-self-ident))
  618. (git-commit-insert-header "Reviewed-by" name mail))
  619. (defun git-commit-signoff (name mail)
  620. "Insert a header to sign off the commit."
  621. (interactive (git-commit-self-ident))
  622. (git-commit-insert-header "Signed-off-by" name mail))
  623. (defun git-commit-test (name mail)
  624. "Insert a header acknowledging that you have tested the commit."
  625. (interactive (git-commit-self-ident))
  626. (git-commit-insert-header "Tested-by" name mail))
  627. (defun git-commit-cc (name mail)
  628. "Insert a header mentioning someone who might be interested."
  629. (interactive (git-commit-read-ident))
  630. (git-commit-insert-header "Cc" name mail))
  631. (defun git-commit-reported (name mail)
  632. "Insert a header mentioning the person who reported the issue."
  633. (interactive (git-commit-read-ident))
  634. (git-commit-insert-header "Reported-by" name mail))
  635. (defun git-commit-suggested (name mail)
  636. "Insert a header mentioning the person who suggested the change."
  637. (interactive (git-commit-read-ident))
  638. (git-commit-insert-header "Suggested-by" name mail))
  639. (defun git-commit-self-ident ()
  640. (list (or (getenv "GIT_AUTHOR_NAME")
  641. (getenv "GIT_COMMITTER_NAME")
  642. (ignore-errors (car (process-lines "git" "config" "user.name")))
  643. user-full-name
  644. (read-string "Name: "))
  645. (or (getenv "GIT_AUTHOR_EMAIL")
  646. (getenv "GIT_COMMITTER_EMAIL")
  647. (getenv "EMAIL")
  648. (ignore-errors (car (process-lines "git" "config" "user.email")))
  649. (read-string "Email: "))))
  650. (defun git-commit-read-ident ()
  651. (list (read-string "Name: ")
  652. (read-string "Email: ")))
  653. (defun git-commit-insert-header (header name email)
  654. (setq header (format "%s: %s <%s>" header name email))
  655. (save-excursion
  656. (goto-char (point-max))
  657. (cond ((re-search-backward "^[-a-zA-Z]+: [^<]+? <[^>]+>" nil t)
  658. (end-of-line)
  659. (insert ?\n header)
  660. (unless (= (char-after) ?\n)
  661. (insert ?\n)))
  662. (t
  663. (while (re-search-backward (concat "^" comment-start) nil t))
  664. (unless (looking-back "\n\n" nil)
  665. (insert ?\n))
  666. (insert header ?\n)))
  667. (unless (or (eobp) (= (char-after) ?\n))
  668. (insert ?\n))))
  669. ;;; Font-Lock
  670. (defvar-local git-commit-need-summary-line t
  671. "Whether the text should have a heading that is separated from the body.
  672. For commit messages that is a convention that should not
  673. be violated. For notes it is up to the user. If you do
  674. not want to insist on an empty second line here, then use
  675. something like:
  676. (add-hook \\='git-commit-setup-hook
  677. (lambda ()
  678. (when (equal (file-name-nondirectory (buffer-file-name))
  679. \"NOTES_EDITMSG\")
  680. (setq git-commit-need-summary-line nil))))")
  681. (defun git-commit-summary-regexp ()
  682. (if git-commit-need-summary-line
  683. (concat
  684. ;; Leading empty lines and comments
  685. (format "\\`\\(?:^\\(?:\\s-*\\|%s.*\\)\n\\)*" comment-start)
  686. ;; Summary line
  687. (format "\\(.\\{0,%d\\}\\)\\(.*\\)" git-commit-summary-max-length)
  688. ;; Non-empty non-comment second line
  689. (format "\\(?:\n%s\\|\n\\(.+\\)\\)?" comment-start))
  690. "\\(EASTER\\) \\(EGG\\)"))
  691. (defun git-commit-extend-region-summary-line ()
  692. "Identify the multiline summary-regexp construct.
  693. Added to `font-lock-extend-region-functions'."
  694. (save-excursion
  695. (save-match-data
  696. (goto-char (point-min))
  697. (when (looking-at (git-commit-summary-regexp))
  698. (let ((summary-beg (match-beginning 0))
  699. (summary-end (match-end 0)))
  700. (when (or (< summary-beg font-lock-beg summary-end)
  701. (< summary-beg font-lock-end summary-end))
  702. (setq font-lock-beg (min font-lock-beg summary-beg))
  703. (setq font-lock-end (max font-lock-end summary-end))))))))
  704. (defvar-local git-commit--branch-name-regexp nil)
  705. (defconst git-commit-comment-headings
  706. '("Changes to be committed:"
  707. "Untracked files:"
  708. "Changed but not updated:"
  709. "Changes not staged for commit:"
  710. "Unmerged paths:"
  711. "Author:"
  712. "Date:"))
  713. (defconst git-commit-font-lock-keywords-1
  714. '(;; Pseudo headers
  715. (eval . `(,(format "^\\(%s:\\)\\( .*\\)"
  716. (regexp-opt git-commit-known-pseudo-headers))
  717. (1 'git-commit-known-pseudo-header)
  718. (2 'git-commit-pseudo-header)))
  719. ("^[-a-zA-Z]+: [^<]+? <[^>]+>"
  720. (0 'git-commit-pseudo-header))
  721. ;; Summary
  722. (eval . `(,(git-commit-summary-regexp)
  723. (1 'git-commit-summary)))
  724. ;; - Keyword [aka "text in brackets"] (overrides summary)
  725. ("\\[.+?\\]"
  726. (0 'git-commit-keyword t))
  727. ;; - Non-empty second line (overrides summary and note)
  728. (eval . `(,(git-commit-summary-regexp)
  729. (2 'git-commit-overlong-summary t t)
  730. (3 'git-commit-nonempty-second-line t t)))))
  731. (defconst git-commit-font-lock-keywords-2
  732. `(,@git-commit-font-lock-keywords-1
  733. ;; Comments
  734. (eval . `(,(format "^%s.*" comment-start)
  735. (0 'font-lock-comment-face)))
  736. (eval . `(,(format "^%s On branch \\(.*\\)" comment-start)
  737. (1 'git-commit-comment-branch-local t)))
  738. (eval . `(,(format "^%s \\(HEAD\\) detached at" comment-start)
  739. (1 'git-commit-comment-detached t)))
  740. (eval . `(,(format "^%s %s" comment-start
  741. (regexp-opt git-commit-comment-headings t))
  742. (1 'git-commit-comment-heading t)))
  743. (eval . `(,(format "^%s\t\\(?:\\([^:\n]+\\):\\s-+\\)?\\(.*\\)" comment-start)
  744. (1 'git-commit-comment-action t t)
  745. (2 'git-commit-comment-file t)))))
  746. (defconst git-commit-font-lock-keywords-3
  747. `(,@git-commit-font-lock-keywords-2
  748. ;; More comments
  749. (eval
  750. ;; Your branch is ahead of 'master' by 3 commits.
  751. ;; Your branch is behind 'master' by 2 commits, and can be fast-forwarded.
  752. . `(,(format
  753. "^%s Your branch is \\(?:ahead\\|behind\\) of '%s' by \\([0-9]*\\)"
  754. comment-start git-commit--branch-name-regexp)
  755. (1 'git-commit-comment-branch-local t)
  756. (2 'git-commit-comment-branch-remote t)
  757. (3 'bold t)))
  758. (eval
  759. ;; Your branch is up to date with 'master'.
  760. ;; Your branch and 'master' have diverged,
  761. . `(,(format
  762. "^%s Your branch \\(?:is up-to-date with\\|and\\) '%s'"
  763. comment-start git-commit--branch-name-regexp)
  764. (1 'git-commit-comment-branch-local t)
  765. (2 'git-commit-comment-branch-remote t)))
  766. (eval
  767. ;; and have 1 and 2 different commits each, respectively.
  768. . `(,(format
  769. "^%s and have \\([0-9]*\\) and \\([0-9]*\\) commits each"
  770. comment-start)
  771. (1 'bold t)
  772. (2 'bold t)))))
  773. (defvar git-commit-font-lock-keywords git-commit-font-lock-keywords-2
  774. "Font-Lock keywords for Git-Commit mode.")
  775. (defun git-commit-setup-font-lock ()
  776. (let ((table (make-syntax-table (syntax-table))))
  777. (when comment-start
  778. (modify-syntax-entry (string-to-char comment-start) "." table))
  779. (modify-syntax-entry ?# "." table)
  780. (modify-syntax-entry ?\" "." table)
  781. (modify-syntax-entry ?\' "." table)
  782. (modify-syntax-entry ?` "." table)
  783. (set-syntax-table table))
  784. (setq-local comment-start
  785. (or (ignore-errors
  786. (car (process-lines "git" "config" "core.commentchar")))
  787. "#"))
  788. (setq-local comment-start-skip (format "^%s+[\s\t]*" comment-start))
  789. (setq-local comment-end-skip "\n")
  790. (setq-local comment-use-syntax nil)
  791. (setq-local git-commit--branch-name-regexp
  792. (if (and (featurep 'magit-git)
  793. ;; When using cygwin git, we may end up in a
  794. ;; non-existing directory, which would cause
  795. ;; any git calls to signal an error.
  796. (file-accessible-directory-p default-directory))
  797. (progn
  798. ;; Make sure the below functions are available.
  799. (require 'magit)
  800. ;; Font-Lock wants every submatch to succeed,
  801. ;; so also match the empty string. Do not use
  802. ;; `regexp-quote' because that is slow if there
  803. ;; are thousands of branches outweighing the
  804. ;; benefit of an efficient regep.
  805. (format "\\(\\(?:%s\\)\\|\\)\\(\\(?:%s\\)\\|\\)"
  806. (mapconcat #'identity
  807. (magit-list-local-branch-names)
  808. "\\|")
  809. (mapconcat #'identity
  810. (magit-list-remote-branch-names)
  811. "\\|")))
  812. "\\([^']*\\)"))
  813. (setq-local font-lock-multiline t)
  814. (add-hook 'font-lock-extend-region-functions
  815. #'git-commit-extend-region-summary-line
  816. t t)
  817. (font-lock-add-keywords nil git-commit-font-lock-keywords))
  818. (defun git-commit-propertize-diff ()
  819. (require 'diff-mode)
  820. (save-excursion
  821. (goto-char (point-min))
  822. (when (re-search-forward "^diff --git" nil t)
  823. (beginning-of-line)
  824. (let ((buffer (current-buffer)))
  825. (insert
  826. (with-temp-buffer
  827. (insert
  828. (with-current-buffer buffer
  829. (prog1 (buffer-substring-no-properties (point) (point-max))
  830. (delete-region (point) (point-max)))))
  831. (let ((diff-default-read-only nil))
  832. (diff-mode))
  833. (let (font-lock-verbose font-lock-support-mode)
  834. (if (fboundp 'font-lock-ensure)
  835. (font-lock-ensure)
  836. (with-no-warnings
  837. (font-lock-fontify-buffer))))
  838. (let (next (pos (point-min)))
  839. (while (setq next (next-single-property-change pos 'face))
  840. (put-text-property pos next 'font-lock-face
  841. (get-text-property pos 'face))
  842. (setq pos next))
  843. (put-text-property pos (point-max) 'font-lock-face
  844. (get-text-property pos 'face)))
  845. (buffer-string)))))))
  846. ;;; Elisp Text Mode
  847. (define-derived-mode git-commit-elisp-text-mode text-mode "ElText"
  848. "Major mode for editing commit messages of elisp projects.
  849. This is intended for use as `git-commit-major-mode' for projects
  850. that expect `symbols' to look like this. I.e. like they look in
  851. Elisp doc-strings, including this one. Unlike in doc-strings,
  852. \"strings\" also look different than the other text."
  853. (setq font-lock-defaults '(git-commit-elisp-text-mode-keywords)))
  854. (defvar git-commit-elisp-text-mode-keywords
  855. `((,(concat "[`‘]\\(" lisp-mode-symbol-regexp "\\)['’]")
  856. (1 font-lock-constant-face prepend))
  857. ("\"[^\"]*\"" (0 font-lock-string-face prepend))))
  858. ;;; _
  859. (provide 'git-commit)
  860. ;;; git-commit.el ends here