aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/log-edit.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/log-edit.el')
-rw-r--r--lisp/log-edit.el261
1 files changed, 212 insertions, 49 deletions
diff --git a/lisp/log-edit.el b/lisp/log-edit.el
index 10c6d480d23..0d3061ad2df 100644
--- a/lisp/log-edit.el
+++ b/lisp/log-edit.el
@@ -125,6 +125,7 @@ If SETUP is 'force, this variable has no effect."
125 :type 'boolean) 125 :type 'boolean)
126 126
127(defcustom log-edit-hook '(log-edit-insert-cvs-template 127(defcustom log-edit-hook '(log-edit-insert-cvs-template
128 log-edit-show-files
128 log-edit-insert-changelog) 129 log-edit-insert-changelog)
129 "Hook run at the end of `log-edit'." 130 "Hook run at the end of `log-edit'."
130 :group 'log-edit 131 :group 'log-edit
@@ -188,6 +189,7 @@ when this variable is set to nil.")
188(defvar log-edit-callback nil) 189(defvar log-edit-callback nil)
189(defvar log-edit-diff-function nil) 190(defvar log-edit-diff-function nil)
190(defvar log-edit-listfun nil) 191(defvar log-edit-listfun nil)
192
191(defvar log-edit-parent-buffer nil) 193(defvar log-edit-parent-buffer nil)
192 194
193;;; Originally taken from VC-Log mode 195;;; Originally taken from VC-Log mode
@@ -312,15 +314,59 @@ automatically."
312;;; Actual code 314;;; Actual code
313;;; 315;;;
314 316
317(defface log-edit-summary '((t :inherit font-lock-function-name-face))
318 "Face for the summary in `log-edit-mode' buffers.")
319
320(defface log-edit-header '((t :inherit font-lock-keyword-face))
321 "Face for the headers in `log-edit-mode' buffers.")
322
323(defface log-edit-unknown-header '((t :inherit font-lock-comment-face))
324 "Face for unknown headers in `log-edit-mode' buffers.")
325
326(defvar log-edit-headers-alist '(("Summary" . log-edit-summary)
327 ("Fixes") ("Author"))
328 "AList of known headers and the face to use to highlight them.")
329
330(defconst log-edit-header-contents-regexp
331 "[ \t]*\\(.*\\(\n[ \t].*\\)*\\)\n?")
332
333(defun log-edit-match-to-eoh (limit)
334 ;; FIXME: copied from message-match-to-eoh.
335 (let ((start (point)))
336 (rfc822-goto-eoh)
337 ;; Typical situation: some temporary change causes the header to be
338 ;; incorrect, so EOH comes earlier than intended: the last lines of the
339 ;; intended headers are now not considered part of the header any more,
340 ;; so they don't have the multiline property set. When the change is
341 ;; completed and the header has its correct shape again, the lack of the
342 ;; multiline property means we won't rehighlight the last lines of
343 ;; the header.
344 (if (< (point) start)
345 nil ;No header within start..limit.
346 ;; Here we disregard LIMIT so that we may extend the area again.
347 (set-match-data (list start (point)))
348 (point))))
349
315(defvar log-edit-font-lock-keywords 350(defvar log-edit-font-lock-keywords
316 '(("\\`\\(Summary:\\)\\(.*\\)" 351 ;; Copied/inspired by message-font-lock-keywords.
317 (1 font-lock-keyword-face) 352 `((log-edit-match-to-eoh
318 (2 font-lock-function-name-face)))) 353 (,(concat "^\\(\\([a-z]+\\):\\)" log-edit-header-contents-regexp
354 "\\|\\(.*\\)")
355 (progn (goto-char (match-beginning 0)) (match-end 0)) nil
356 (1 (if (assoc (match-string 2) log-edit-headers-alist)
357 'log-edit-header
358 'log-edit-unknown-header)
359 nil lax)
360 (3 (or (cdr (assoc (match-string 2) log-edit-headers-alist))
361 'log-edit-header)
362 nil lax)
363 (4 font-lock-warning-face)))))
319 364
320;;;###autoload 365;;;###autoload
321(defun log-edit (callback &optional setup params buffer &rest ignore) 366(defun log-edit (callback &optional setup params buffer mode &rest ignore)
322 "Setup a buffer to enter a log message. 367 "Setup a buffer to enter a log message.
323\\<log-edit-mode-map>The buffer will be put in `log-edit-mode'. 368\\<log-edit-mode-map>The buffer will be put in mode MODE or `log-edit-mode'
369if MODE is nil.
324If SETUP is non-nil, the buffer is then erased and `log-edit-hook' is run. 370If SETUP is non-nil, the buffer is then erased and `log-edit-hook' is run.
325Mark and point will be set around the entire contents of the buffer so 371Mark and point will be set around the entire contents of the buffer so
326that it is easy to kill the contents of the buffer with \\[kill-region]. 372that it is easy to kill the contents of the buffer with \\[kill-region].
@@ -340,8 +386,13 @@ uses the current buffer."
340 (if buffer (pop-to-buffer buffer)) 386 (if buffer (pop-to-buffer buffer))
341 (when (and log-edit-setup-invert (not (eq setup 'force))) 387 (when (and log-edit-setup-invert (not (eq setup 'force)))
342 (setq setup (not setup))) 388 (setq setup (not setup)))
343 (when setup (erase-buffer)) 389 (when setup
344 (log-edit-mode) 390 (erase-buffer)
391 (insert "Summary: ")
392 (save-excursion (insert "\n\n")))
393 (if mode
394 (funcall mode)
395 (log-edit-mode))
345 (set (make-local-variable 'log-edit-callback) callback) 396 (set (make-local-variable 'log-edit-callback) callback)
346 (if (listp params) 397 (if (listp params)
347 (dolist (crt params) 398 (dolist (crt params)
@@ -367,7 +418,7 @@ commands (under C-x v for VC, for example).
367 418
368\\{log-edit-mode-map}" 419\\{log-edit-mode-map}"
369 (set (make-local-variable 'font-lock-defaults) 420 (set (make-local-variable 'font-lock-defaults)
370 '(log-edit-font-lock-keywords t)) 421 '(log-edit-font-lock-keywords t t))
371 (make-local-variable 'log-edit-comment-ring-index)) 422 (make-local-variable 'log-edit-comment-ring-index))
372 423
373(defun log-edit-hide-buf (&optional buf where) 424(defun log-edit-hide-buf (&optional buf where)
@@ -380,6 +431,17 @@ commands (under C-x v for VC, for example).
380 "Finish editing the log message and commit the files. 431 "Finish editing the log message and commit the files.
381If you want to abort the commit, simply delete the buffer." 432If you want to abort the commit, simply delete the buffer."
382 (interactive) 433 (interactive)
434 ;; Clean up empty headers.
435 (goto-char (point-min))
436 (while (looking-at (concat "^[a-z]*:" log-edit-header-contents-regexp))
437 (let ((beg (match-beginning 0)))
438 (goto-char (match-end 0))
439 (if (string-match "\\`[ \n\t]*\\'" (match-string 1))
440 (delete-region beg (point)))))
441 ;; Get rid of leading empty lines.
442 (goto-char (point-min))
443 (when (looking-at "\\([ \t]*\n\\)+")
444 (delete-region (match-beginning 0) (match-end 0)))
383 ;; Get rid of trailing empty lines 445 ;; Get rid of trailing empty lines
384 (goto-char (point-max)) 446 (goto-char (point-max))
385 (skip-syntax-backward " ") 447 (skip-syntax-backward " ")
@@ -437,12 +499,13 @@ If you want to abort the commit, simply delete the buffer."
437 "(Un)Indent the current buffer rigidly to `log-edit-common-indent'." 499 "(Un)Indent the current buffer rigidly to `log-edit-common-indent'."
438 (save-excursion 500 (save-excursion
439 (let ((common (point-max))) 501 (let ((common (point-max)))
440 (goto-char (point-min)) 502 (rfc822-goto-eoh)
441 (while (< (point) (point-max)) 503 (while (< (point) (point-max))
442 (if (not (looking-at "^[ \t]*$")) 504 (if (not (looking-at "^[ \t]*$"))
443 (setq common (min common (current-indentation)))) 505 (setq common (min common (current-indentation))))
444 (forward-line 1)) 506 (forward-line 1))
445 (indent-rigidly (point-min) (point-max) 507 (rfc822-goto-eoh)
508 (indent-rigidly (point) (point-max)
446 (- log-edit-common-indent common))))) 509 (- log-edit-common-indent common)))))
447 510
448(defun log-edit-show-diff () 511(defun log-edit-show-diff ()
@@ -508,6 +571,16 @@ can thus take some time."
508 (log-edit-comment-to-change-log))))) 571 (log-edit-comment-to-change-log)))))
509 572
510(defvar log-edit-changelog-use-first nil) 573(defvar log-edit-changelog-use-first nil)
574
575(defvar log-edit-rewrite-fixes nil
576 "Rule to rewrite bug numbers into Fixes: headers.
577The value should be of the form (REGEXP . REPLACEMENT)
578where REGEXP should match the expression referring to a bug number
579in the text, and REPLACEMENT is an expression to pass to `replace-match'
580to build the Fixes: header.")
581(put 'log-edit-rewrite-fixes 'safe-local-variable
582 (lambda (v) (and (stringp (car-safe v)) (stringp (cdr v)))))
583
511(defun log-edit-insert-changelog (&optional use-first) 584(defun log-edit-insert-changelog (&optional use-first)
512 "Insert a log message by looking at the ChangeLog. 585 "Insert a log message by looking at the ChangeLog.
513The idea is to write your ChangeLog entries first, and then use this 586The idea is to write your ChangeLog entries first, and then use this
@@ -525,18 +598,38 @@ If the optional prefix arg USE-FIRST is given (via \\[universal-argument]),
525or if the command is repeated a second time in a row, use the first log entry 598or if the command is repeated a second time in a row, use the first log entry
526regardless of user name or time." 599regardless of user name or time."
527 (interactive "P") 600 (interactive "P")
528 (let ((log-edit-changelog-use-first 601 (let ((eoh (save-excursion (rfc822-goto-eoh) (point))))
529 (or use-first (eq last-command 'log-edit-insert-changelog)))) 602 (when (<= (point) eoh)
530 (log-edit-insert-changelog-entries (log-edit-files))) 603 (goto-char eoh)
531 (log-edit-set-common-indentation) 604 (if (looking-at "\n") (forward-char 1))))
532 (goto-char (point-min)) 605 (let ((author
533 (when (and log-edit-strip-single-file-name (looking-at "\\*\\s-+")) 606 (let ((log-edit-changelog-use-first
534 (forward-line 1) 607 (or use-first (eq last-command 'log-edit-insert-changelog))))
535 (when (not (re-search-forward "^\\*\\s-+" nil t)) 608 (log-edit-insert-changelog-entries (log-edit-files)))))
536 (goto-char (point-min)) 609 (log-edit-set-common-indentation)
537 (skip-chars-forward "^():") 610 ;; Add an Author: field if appropriate.
538 (skip-chars-forward ": ") 611 (when author
539 (delete-region (point-min) (point))))) 612 (rfc822-goto-eoh)
613 (insert "Author: " author "\n" (if (looking-at "\n") "" "\n")))
614 ;; Add a Fixes: field if applicable.
615 (when (consp log-edit-rewrite-fixes)
616 (rfc822-goto-eoh)
617 (when (re-search-forward (car log-edit-rewrite-fixes) nil t)
618 (let ((start (match-beginning 0))
619 (end (match-end 0))
620 (fixes (match-substitute-replacement
621 (cdr log-edit-rewrite-fixes))))
622 (delete-region start end)
623 (rfc822-goto-eoh)
624 (insert "Fixes: " fixes "\n" (if (looking-at "\n") "" "\n")))))
625 (goto-char (point-min))
626 (when (and log-edit-strip-single-file-name (looking-at "\\*\\s-+"))
627 (forward-line 1)
628 (when (not (re-search-forward "^\\*\\s-+" nil t))
629 (goto-char (point-min))
630 (skip-chars-forward "^():")
631 (skip-chars-forward ": ")
632 (delete-region (point-min) (point))))))
540 633
541;;;; 634;;;;
542;;;; functions for getting commit message from ChangeLog a file... 635;;;; functions for getting commit message from ChangeLog a file...
@@ -602,6 +695,9 @@ for more details."
602 695
603(defvar user-full-name) 696(defvar user-full-name)
604(defvar user-mail-address) 697(defvar user-mail-address)
698
699(defvar log-edit-author) ;Dynamically scoped.
700
605(defun log-edit-changelog-ours-p () 701(defun log-edit-changelog-ours-p ()
606 "See if ChangeLog entry at point is for the current user, today. 702 "See if ChangeLog entry at point is for the current user, today.
607Return non-nil if it is." 703Return non-nil if it is."
@@ -616,14 +712,28 @@ Return non-nil if it is."
616 (functionp add-log-time-format) 712 (functionp add-log-time-format)
617 (funcall add-log-time-format)) 713 (funcall add-log-time-format))
618 (format-time-string "%Y-%m-%d")))) 714 (format-time-string "%Y-%m-%d"))))
619 (looking-at (if log-edit-changelog-use-first 715 (if (null log-edit-changelog-use-first)
620 "[^ \t]" 716 (looking-at (regexp-quote (format "%s %s <%s>" time name mail)))
621 (regexp-quote (format "%s %s <%s>" time name mail)))))) 717 ;; Check the author, to potentially add it as a "Author: " header.
718 (when (looking-at "[^ \t]")
719 (when (and (boundp 'log-edit-author)
720 (not (looking-at (format ".+ .+ <%s>"
721 (regexp-quote mail))))
722 (looking-at ".+ \\(.+ <.+>\\)"))
723 (let ((author (replace-regexp-in-string " " " "
724 (match-string 1))))
725 (unless (and log-edit-author
726 (string-match (regexp-quote author) log-edit-author))
727 (setq log-edit-author
728 (if log-edit-author
729 (concat log-edit-author ", " author)
730 author)))))
731 t))))
622 732
623(defun log-edit-changelog-entries (file) 733(defun log-edit-changelog-entries (file)
624 "Return the ChangeLog entries for FILE, and the ChangeLog they came from. 734 "Return the ChangeLog entries for FILE, and the ChangeLog they came from.
625The return value looks like this: 735The return value looks like this:
626 (LOGBUFFER (ENTRYSTART . ENTRYEND) ...) 736 (LOGBUFFER (ENTRYSTART ENTRYEND) ...)
627where LOGBUFFER is the name of the ChangeLog buffer, and each 737where LOGBUFFER is the name of the ChangeLog buffer, and each
628\(ENTRYSTART . ENTRYEND\) pair is a buffer region." 738\(ENTRYSTART . ENTRYEND\) pair is a buffer region."
629 (let ((changelog-file-name 739 (let ((changelog-file-name
@@ -681,34 +791,87 @@ where LOGBUFFER is the name of the ChangeLog buffer, and each
681 791
682 (cons (current-buffer) texts)))))))) 792 (cons (current-buffer) texts))))))))
683 793
684(defun log-edit-changelog-insert-entries (buffer regions) 794(defun log-edit-changelog-insert-entries (buffer beg end &rest files)
685 "Insert those regions in BUFFER specified in REGIONS. 795 "Insert the text from BUFFER between BEG and END.
686Sort REGIONS front-to-back first." 796Rename relative filenames in the ChangeLog entry as FILES."
687 (let ((regions (sort regions 'car-less-than-car)) 797 (let ((opoint (point))
688 (last)) 798 (log-name (buffer-file-name buffer))
689 (dolist (region regions) 799 (case-fold-search nil)
690 (when (and last (< last (car region))) (newline)) 800 bound)
691 (setq last (elt region 1)) 801 (insert-buffer-substring buffer beg end)
692 (apply 'insert-buffer-substring buffer region)))) 802 (setq bound (point-marker))
803 (when log-name
804 (dolist (f files)
805 (save-excursion
806 (goto-char opoint)
807 (when (re-search-forward
808 (concat "\\(^\\|[ \t]\\)\\("
809 (file-relative-name f (file-name-directory log-name))
810 "\\)[, :\n]")
811 bound t)
812 (replace-match f t t nil 2)))))
813 ;; Eliminate tabs at the beginning of the line.
814 (save-excursion
815 (goto-char opoint)
816 (while (re-search-forward "^\\(\t+\\)" bound t)
817 (replace-match "")))))
693 818
694(defun log-edit-insert-changelog-entries (files) 819(defun log-edit-insert-changelog-entries (files)
695 "Given a list of files FILES, insert the ChangeLog entries for them." 820 "Given a list of files FILES, insert the ChangeLog entries for them."
696 (let ((buffer-entries nil)) 821 (let ((log-entries nil)
697 822 (log-edit-author nil))
698 ;; Add each buffer to buffer-entries, and associate it with the list 823 ;; Note that any ChangeLog entry can apply to more than one file.
699 ;; of entries we want from that file. 824 ;; Here we construct a log-entries list with elements of the form
825 ;; ((LOGBUFFER ENTRYSTART ENTRYEND) FILE1 FILE2...)
700 (dolist (file files) 826 (dolist (file files)
701 (let* ((entries (log-edit-changelog-entries file)) 827 (let* ((entries (log-edit-changelog-entries file))
702 (pair (assq (car entries) buffer-entries))) 828 (buf (car entries))
703 (if pair 829 key entry)
704 (setcdr pair (cvs-union (cdr pair) (cdr entries))) 830 (dolist (region (cdr entries))
705 (push entries buffer-entries)))) 831 (setq key (cons buf region))
706 832 (if (setq entry (assoc key log-entries))
707 ;; Now map over each buffer in buffer-entries, sort the entries for 833 (setcdr entry (append (cdr entry) (list file)))
708 ;; each buffer, and extract them as strings. 834 (push (list key file) log-entries)))))
709 (dolist (buffer-entry buffer-entries) 835 ;; Now map over log-entries, and extract the strings.
710 (log-edit-changelog-insert-entries (car buffer-entry) (cdr buffer-entry)) 836 (dolist (log-entry (nreverse log-entries))
711 (when (cdr buffer-entry) (newline))))) 837 (apply 'log-edit-changelog-insert-entries
838 (append (car log-entry) (cdr log-entry)))
839 (insert "\n"))
840 log-edit-author))
841
842(defun log-edit-extract-headers (headers comment)
843 "Extract headers from COMMENT to form command line arguments.
844HEADERS should be an alist with elements of the form (HEADER . CMDARG)
845associating header names to the corresponding cmdline option name and the
846result is then a list of the form (MSG CMDARG1 HDRTEXT1 CMDARG2 HDRTEXT2...).
847where MSG is the remaining text from STRING.
848If \"Summary\" is not in HEADERS, then the \"Summary\" header is extracted
849anyway and put back as the first line of MSG."
850 (with-temp-buffer
851 (insert comment)
852 (rfc822-goto-eoh)
853 (narrow-to-region (point-min) (point))
854 (let ((case-fold-search t)
855 (summary ())
856 (res ()))
857 (dolist (header (if (assoc "Summary" headers) headers
858 (cons '("Summary" . t) headers)))
859 (goto-char (point-min))
860 (while (re-search-forward (concat "^" (car header)
861 ":" log-edit-header-contents-regexp)
862 nil t)
863 (if (eq t (cdr header))
864 (setq summary (match-string 1))
865 (push (match-string 1) res)
866 (push (or (cdr header) (car header)) res))
867 (replace-match "" t t)))
868 ;; Remove header separator if the header is empty.
869 (widen)
870 (goto-char (point-min))
871 (when (looking-at "\\([ \t]*\n\\)+")
872 (delete-region (match-beginning 0) (match-end 0)))
873 (if summary (insert summary "\n"))
874 (cons (buffer-string) res))))
712 875
713(provide 'log-edit) 876(provide 'log-edit)
714 877