diff options
Diffstat (limited to 'lisp/log-edit.el')
| -rw-r--r-- | lisp/log-edit.el | 261 |
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' |
| 369 | if MODE is nil. | ||
| 324 | If SETUP is non-nil, the buffer is then erased and `log-edit-hook' is run. | 370 | If SETUP is non-nil, the buffer is then erased and `log-edit-hook' is run. |
| 325 | Mark and point will be set around the entire contents of the buffer so | 371 | Mark and point will be set around the entire contents of the buffer so |
| 326 | that it is easy to kill the contents of the buffer with \\[kill-region]. | 372 | that 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. |
| 381 | If you want to abort the commit, simply delete the buffer." | 432 | If 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. | ||
| 577 | The value should be of the form (REGEXP . REPLACEMENT) | ||
| 578 | where REGEXP should match the expression referring to a bug number | ||
| 579 | in the text, and REPLACEMENT is an expression to pass to `replace-match' | ||
| 580 | to 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. |
| 513 | The idea is to write your ChangeLog entries first, and then use this | 586 | The 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]), | |||
| 525 | or if the command is repeated a second time in a row, use the first log entry | 598 | or if the command is repeated a second time in a row, use the first log entry |
| 526 | regardless of user name or time." | 599 | regardless 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. |
| 607 | Return non-nil if it is." | 703 | Return 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. |
| 625 | The return value looks like this: | 735 | The return value looks like this: |
| 626 | (LOGBUFFER (ENTRYSTART . ENTRYEND) ...) | 736 | (LOGBUFFER (ENTRYSTART ENTRYEND) ...) |
| 627 | where LOGBUFFER is the name of the ChangeLog buffer, and each | 737 | where 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. |
| 686 | Sort REGIONS front-to-back first." | 796 | Rename 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. | ||
| 844 | HEADERS should be an alist with elements of the form (HEADER . CMDARG) | ||
| 845 | associating header names to the corresponding cmdline option name and the | ||
| 846 | result is then a list of the form (MSG CMDARG1 HDRTEXT1 CMDARG2 HDRTEXT2...). | ||
| 847 | where MSG is the remaining text from STRING. | ||
| 848 | If \"Summary\" is not in HEADERS, then the \"Summary\" header is extracted | ||
| 849 | anyway 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 | ||