-;;; log-edit.el --- Major mode for editing CVS commit messages
+;;; log-edit.el --- Major mode for editing CVS commit messages -*- lexical-binding: t -*-
-;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
-;; 2008, 2009, 2010 Free Software Foundation, Inc.
+;; Copyright (C) 1999-2012 Free Software Foundation, Inc.
;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
;; Keywords: pcl-cvs cvs commit log vc
;;; Code:
-(eval-when-compile (require 'cl))
(require 'add-log) ; for all the ChangeLog goodies
(require 'pcvs-util)
(require 'ring)
"21.1")
(defvar log-edit-changelog-full-paragraphs cvs-changelog-full-paragraphs
- "*If non-nil, include full ChangeLog paragraphs in the log.
+ "If non-nil, include full ChangeLog paragraphs in the log.
This may be set in the ``local variables'' section of a ChangeLog, to
indicate the policy for that ChangeLog.
(defvar log-edit-parent-buffer nil)
+(defvar log-edit-vc-backend nil
+ "VC fileset corresponding to the current log.")
+
;;; Originally taken from VC-Log mode
(defconst log-edit-maximum-comment-ring-size 32
"Maximum number of saved comments in the comment ring.")
+(define-obsolete-variable-alias 'vc-comment-ring 'log-edit-comment-ring "22.1")
(defvar log-edit-comment-ring (make-ring log-edit-maximum-comment-ring-size))
+(define-obsolete-variable-alias 'vc-comment-ring-index
+ 'log-edit-comment-ring-index "22.1")
(defvar log-edit-comment-ring-index nil)
(defvar log-edit-last-comment-match "")
(insert "\n"))))
;; Compatibility with old names.
-(define-obsolete-variable-alias 'vc-comment-ring 'log-edit-comment-ring "22.1")
-(define-obsolete-variable-alias 'vc-comment-ring-index 'log-edit-comment-ring-index "22.1")
(define-obsolete-function-alias 'vc-previous-comment 'log-edit-previous-comment "22.1")
(define-obsolete-function-alias 'vc-next-comment 'log-edit-next-comment "22.1")
(define-obsolete-function-alias 'vc-comment-search-reverse 'log-edit-comment-search-backward "22.1")
(defconst log-edit-header-contents-regexp
"[ \t]*\\(.*\\(\n[ \t].*\\)*\\)\n?")
-(defun log-edit-match-to-eoh (limit)
+(defun log-edit-match-to-eoh (_limit)
;; FIXME: copied from message-match-to-eoh.
(let ((start (point)))
(rfc822-goto-eoh)
(defvar log-edit-font-lock-keywords
;; Copied/inspired by message-font-lock-keywords.
`((log-edit-match-to-eoh
- (,(concat "^\\(\\([a-z]+\\):\\)" log-edit-header-contents-regexp
- "\\|\\(.*\\)")
+ (,(concat "^\\(\\([[:alpha:]]+\\):\\)" log-edit-header-contents-regexp)
(progn (goto-char (match-beginning 0)) (match-end 0)) nil
(1 (if (assoc (match-string 2) log-edit-headers-alist)
'log-edit-header
'log-edit-unknown-header)
nil lax)
+ ;; From `log-edit-header-contents-regexp':
(3 (or (cdr (assoc (match-string 2) log-edit-headers-alist))
'log-edit-header)
- nil lax)
- (4 font-lock-warning-face)))))
+ nil lax)))))
+
+(defvar log-edit-font-lock-gnu-style nil
+ "If non-nil, highlight common failures to follow the GNU coding standards.")
+(put 'log-edit-font-lock-gnu-style 'safe-local-variable 'booleanp)
+
+(defconst log-edit-font-lock-gnu-keywords
+ ;; Use
+ ;; * foo.el (bla, bli)
+ ;; (blo, blu): Toto.
+ ;; Rather than
+ ;; * foo.el (bla, bli,
+ ;; blo, blu): Toto.
+ '(("^[ \t]*\\(?:\\* .*\\)?\\(([^\n)]*,\\s-*\\)$"
+ (1 '(face font-lock-warning-face
+ help-echo "Continue function lists with \")\\n(\".") t))
+ ;; Don't leave a lone word on a single line.
+ ;;("^\\s-*\\(\\S-*[^\n:)]\\)\\s-*$" (1 font-lock-warning-face t))
+ ;; Don't cut a sentence right after the first word (better to move
+ ;; the sentence on the next line, then).
+ ;;("[.:]\\s-+\\(\\sw+\\)\\s-*$" (1 font-lock-warning-face t))
+ ;; Change Log entries should use present tense.
+ ("):[ \t\n]*[[:alpha:]]+\\(ed\\)\\>"
+ (1 '(face font-lock-warning-face help-echo "Use present tense.") t))
+ ;; Change log entries start with a capital letter.
+ ("): [a-z]" (0 '(face font-lock-warning-face help-echo "Capitalize.") t))
+ ("[^[:upper:]]\\(\\. [[:upper:]]\\)"
+ (1 '(face font-lock-warning-face
+ help-echo "Use two spaces to end a sentence") t))
+ ("^("
+ (0 (let ((beg (max (point-min) (- (match-beginning 0) 2))))
+ (put-text-property beg (match-end 0) 'font-lock-multiline t)
+ (if (eq (char-syntax (char-after beg)) ?w)
+ '(face font-lock-warning-face
+ help-echo "Punctuate previous line.")))
+ t))
+ ))
+
+(defun log-edit-font-lock-keywords ()
+ (if log-edit-font-lock-gnu-style
+ (append log-edit-font-lock-keywords
+ log-edit-font-lock-gnu-keywords)
+ log-edit-font-lock-keywords))
;;;###autoload
-(defun log-edit (callback &optional setup params buffer mode &rest ignore)
+(defun log-edit (callback &optional setup params buffer mode &rest _ignore)
"Setup a buffer to enter a log message.
-\\<log-edit-mode-map>The buffer will be put in mode MODE or `log-edit-mode'
-if MODE is nil.
-If SETUP is non-nil, the buffer is then erased and `log-edit-hook' is run.
-Mark and point will be set around the entire contents of the buffer so
-that it is easy to kill the contents of the buffer with \\[kill-region].
-Once you're done editing the message, pressing \\[log-edit-done] will call
-`log-edit-done' which will end up calling CALLBACK to do the actual commit.
-
-PARAMS if non-nil is an alist. Possible keys and associated values:
+The buffer is put in mode MODE or `log-edit-mode' if MODE is nil.
+\\<log-edit-mode-map>
+If SETUP is non-nil, erase the buffer and run `log-edit-hook'.
+Set mark and point around the entire contents of the buffer, so
+that it is easy to kill the contents of the buffer with
+\\[kill-region]. Once the user is done editing the message,
+invoking the command \\[log-edit-done] (`log-edit-done') will
+call CALLBACK to do the actual commit.
+
+PARAMS if non-nil is an alist of variables and buffer-local
+values to give them in the Log Edit buffer. Possible keys and
+associated values:
`log-edit-listfun' -- function taking no arguments that returns the list of
files that are concerned by the current operation (using relative names);
`log-edit-diff-function' -- function taking no arguments that
displays a diff of the files concerned by the current operation.
+ `vc-log-fileset' -- the VC fileset to be committed (if any).
-If BUFFER is non-nil `log-edit' will jump to that buffer, use it to edit the
-log message and go back to the current buffer when done. Otherwise, it
-uses the current buffer."
+If BUFFER is non-nil `log-edit' will jump to that buffer, use it
+to edit the log message and go back to the current buffer when
+done. Otherwise, it uses the current buffer."
(let ((parent (current-buffer)))
(if buffer (pop-to-buffer buffer))
(when (and log-edit-setup-invert (not (eq setup 'force)))
(setq setup (not setup)))
(when setup
(erase-buffer)
- (insert "Summary: ")
+ (insert "Summary: \nAuthor: ")
(save-excursion (insert "\n\n")))
(if mode
(funcall mode)
\\{log-edit-mode-map}"
(set (make-local-variable 'font-lock-defaults)
- '(log-edit-font-lock-keywords t t))
+ '(log-edit-font-lock-keywords t))
(make-local-variable 'log-edit-comment-ring-index)
(hack-dir-local-variables-non-file-buffer))
(shrink-window-if-larger-than-buffer)
(selected-window)))))
+(defun log-edit-empty-buffer-p ()
+ "Return non-nil if the buffer is \"empty\"."
+ (or (= (point-min) (point-max))
+ (save-excursion
+ (goto-char (point-min))
+ (while (and (looking-at "^\\([a-zA-Z]+: \\)?$")
+ (zerop (forward-line 1))))
+ (eobp))))
+
(defun log-edit-insert-cvs-template ()
"Insert the template specified by the CVS administrator, if any.
This simply uses the local CVS/Template file."
(interactive)
(when (or (called-interactively-p 'interactive)
- (= (point-min) (point-max)))
+ (log-edit-empty-buffer-p))
+ ;; Should the template take precedence over an empty Summary:,
+ ;; ie should we first erase the buffer?
(when (file-readable-p "CVS/Template")
+ (goto-char (point-max))
(insert-file-contents "CVS/Template"))))
(defun log-edit-insert-cvs-rcstemplate ()
can thus take some time."
(interactive)
(when (or (called-interactively-p 'interactive)
- (= (point-min) (point-max)))
+ (log-edit-empty-buffer-p))
(when (file-readable-p "CVS/Root")
+ (goto-char (point-max))
;; Ignore the stderr stuff, even if it's an error.
(call-process "cvs" nil '(t nil) nil
"checkout" "-p" "CVSROOT/rcstemplate"))))
(log-edit-comment-to-change-log)))))
(defvar log-edit-changelog-use-first nil)
+
+(defvar log-edit-rewrite-fixes nil
+ "Rule to rewrite bug numbers into Fixes: headers.
+The value should be of the form (REGEXP . REPLACEMENT)
+where REGEXP should match the expression referring to a bug number
+in the text, and REPLACEMENT is an expression to pass to `replace-match'
+to build the Fixes: header.")
+(put 'log-edit-rewrite-fixes 'safe-local-variable
+ (lambda (v) (and (stringp (car-safe v)) (stringp (cdr v)))))
+
+(defun log-edit-add-field (field value)
+ (rfc822-goto-eoh)
+ (if (save-excursion (re-search-backward (concat "^" field ":\\([ \t]*\\)$")
+ nil t))
+ (replace-match (concat " " value) t t nil 1)
+ (insert field ": " value "\n" (if (looking-at "\n") "" "\n"))))
+
(defun log-edit-insert-changelog (&optional use-first)
"Insert a log message by looking at the ChangeLog.
The idea is to write your ChangeLog entries first, and then use this
(when (<= (point) eoh)
(goto-char eoh)
(if (looking-at "\n") (forward-char 1))))
- (let ((log-edit-changelog-use-first
- (or use-first (eq last-command 'log-edit-insert-changelog))))
- (log-edit-insert-changelog-entries (log-edit-files)))
- (log-edit-set-common-indentation)
- (goto-char (point-min))
- (when (and log-edit-strip-single-file-name (looking-at "\\*\\s-+"))
- (forward-line 1)
- (when (not (re-search-forward "^\\*\\s-+" nil t))
- (goto-char (point-min))
- (skip-chars-forward "^():")
- (skip-chars-forward ": ")
- (delete-region (point-min) (point)))))
+ (let ((author
+ (let ((log-edit-changelog-use-first
+ (or use-first (eq last-command 'log-edit-insert-changelog))))
+ (log-edit-insert-changelog-entries (log-edit-files)))))
+ (log-edit-set-common-indentation)
+ ;; Add an Author: field if appropriate.
+ (when author (log-edit-add-field "Author" author))
+ ;; Add a Fixes: field if applicable.
+ (when (consp log-edit-rewrite-fixes)
+ (rfc822-goto-eoh)
+ (when (re-search-forward (car log-edit-rewrite-fixes) nil t)
+ (let ((start (match-beginning 0))
+ (end (match-end 0))
+ (fixes (match-substitute-replacement
+ (cdr log-edit-rewrite-fixes))))
+ (delete-region start end)
+ (log-edit-add-field "Fixes" fixes))))
+ (and log-edit-strip-single-file-name
+ (progn (rfc822-goto-eoh)
+ (if (looking-at "\n") (forward-char 1))
+ (looking-at "\\*\\s-+"))
+ (let ((start (point)))
+ (forward-line 1)
+ (when (not (re-search-forward "^\\*\\s-+" nil t))
+ (goto-char start)
+ (skip-chars-forward "^():")
+ (skip-chars-forward ": ")
+ (delete-region start (point)))))
+ (goto-char (point-min))))
;;;;
;;;; functions for getting commit message from ChangeLog a file...
(defvar user-full-name)
(defvar user-mail-address)
+
+(defvar log-edit-author) ;Dynamically scoped.
+
(defun log-edit-changelog-ours-p ()
"See if ChangeLog entry at point is for the current user, today.
Return non-nil if it is."
(functionp add-log-time-format)
(funcall add-log-time-format))
(format-time-string "%Y-%m-%d"))))
- (looking-at (if log-edit-changelog-use-first
- "[^ \t]"
- (regexp-quote (format "%s %s <%s>" time name mail))))))
+ (if (null log-edit-changelog-use-first)
+ (looking-at (regexp-quote (format "%s %s <%s>" time name mail)))
+ ;; Check the author, to potentially add it as a "Author: " header.
+ (when (looking-at "[^ \t]")
+ (when (and (boundp 'log-edit-author)
+ (not (looking-at (format ".+ .+ <%s>"
+ (regexp-quote mail))))
+ (looking-at ".+ \\(.+ <.+>\\)"))
+ (let ((author (replace-regexp-in-string " " " "
+ (match-string 1))))
+ (unless (and log-edit-author
+ (string-match (regexp-quote author) log-edit-author))
+ (setq log-edit-author
+ (if log-edit-author
+ (concat log-edit-author ", " author)
+ author)))))
+ t))))
(defun log-edit-changelog-entries (file)
"Return the ChangeLog entries for FILE, and the ChangeLog they came from.
(setq pattern (file-name-nondirectory file)))
(setq pattern (concat "\\(^\\|[^[:alnum:]]\\)"
- pattern
+ (regexp-quote pattern)
"\\($\\|[^[:alnum:]]\\)"))
(let (texts
(defun log-edit-insert-changelog-entries (files)
"Given a list of files FILES, insert the ChangeLog entries for them."
- (let ((log-entries nil))
+ (let ((log-entries nil)
+ (log-edit-author nil))
;; Note that any ChangeLog entry can apply to more than one file.
;; Here we construct a log-entries list with elements of the form
;; ((LOGBUFFER ENTRYSTART ENTRYEND) FILE1 FILE2...)
(dolist (log-entry (nreverse log-entries))
(apply 'log-edit-changelog-insert-entries
(append (car log-entry) (cdr log-entry)))
- (insert "\n"))))
+ (insert "\n"))
+ log-edit-author))
(defun log-edit-extract-headers (headers comment)
"Extract headers from COMMENT to form command line arguments.
(provide 'log-edit)
-;; arch-tag: 8089b39c-983b-4e83-93cd-ed0a64c7fdcc
;;; log-edit.el ends here