Subject: bug#3462: Auto-fill VC log contents

From: Lluis Vilanova <[email-masked]>
Date: Sun, 14 Jun 2009 22:58:10 +0200

El Thu, Jun 04, 2009 at 12:14:47PM -0400, Stefan Monnier ens deleità amb les següents paraules:
> > (I think that modifying `add-change-log-entry' and related functions
> > to accept a destination buffer instead of a file would be valuable, so
> > changes can be directly pushed into the `VC-log' buffer)
> 
> Thank you.  Indeed, making add-log use the *VC-log* buffer (when it
> exists and when there's no ChangeLog file) would be a very welcome
> addition.  I believe it would provide the same functionality as your
> log-edit-fill, but more generic and better integrated.
> 
> If you can cook up a patch for it, we would definitely consider it for
> inclusion in Emacs-23.2

Yay! Attached is a patch to both add-log and log-edit. Note that a few TODO's
are marked along add-log code where I couldn't understand what the code was
doing (just started to learn elisp... well, in fact just switched into emacs :)).

The major show-stoppers right now are:

* Couldn't get change-log-minor-mode to highlight the text as in
  change-log-mode.
* Would be nice to allow adding entries to the bottom of current change log
  block instead of the top.
* diff-add-change-log-entries-other-window has a somewhat strange behaviour and
  ends up showing two windows with the same Changelog/VC-log buffer (I think the
  bug was already there).

See that some changes were made into log-edit to seamlessly integrate with this
new feature in add-log.

As always, comments are welcome.

apa!

-- 
 "And it's much the same thing with knowledge, for whenever you learn
 something new, the whole world becomes that much richer."
 -- The Princess of Pure Reason, as told by Norton Juster in The Phantom
 Tollbooth
Index: log-edit.el
===================================================================
RCS file: /sources/emacs/emacs/lisp/log-edit.el,v
retrieving revision 1.53
diff -u -c -r1.53 log-edit.el
cvs diff: conflicting specifications of output style
*** log-edit.el	5 Jan 2009 03:19:30 -0000	1.53
--- log-edit.el	14 Jun 2009 20:52:29 -0000
***************
*** 124,129 ****
--- 124,134 ----
    :group 'log-edit
    :type 'boolean)
  
+ (defcustom log-edit-done-clean-change-log t
+   "Non-nil means `log-edit-done' will clean the message if it was in change log style."
+   :group 'log-edit
+   :type 'boolean)
+ 
  (defcustom log-edit-hook '(log-edit-insert-cvs-template
  			   log-edit-insert-changelog)
    "Hook run at the end of `log-edit'."
***************
*** 369,378 ****
--- 374,412 ----
        (if win (ignore-errors (delete-window win))))
      (bury-buffer buf)))
  
+ ;; TODO: should provide optional argument for forcing clean?
+ (defun log-edit-clean-change-log ()
+   "Check if buffer is in `change-log-minor-mode' and clean format if true.
+ If the contents of the buffer have been inserted through
+ `add-change-log-entry' and `log-edit-done-clean-change-log' is
+ non-nil, delete the change log block header and the change log
+ indentations before commiting the log."
+   (interactive)
+   (if (and log-edit-done-clean-change-log change-log-minor-mode)
+       (progn
+         (save-excursion
+           ;; Delete change log block header
+           (beginning-of-buffer)
+           (next-line 2)
+           (let ((end-header (point)))
+             (beginning-of-buffer)
+             (if (re-search-forward "^[0-9]*-[0-9]*-[0-9]*  [^<]*  <.*>\n\n" end-header t)
+                 (replace-match "" nil nil))
+             )
+           ;; Delete change log indentation
+           (while (re-search-forward "^[\t]" nil t)
+             (replace-match "" nil nil))
+           )
+         (change-log-minor-mode nil)
+         )
+     )
+   )
+ 
  (defun log-edit-done ()
    "Finish editing the log message and commit the files.
  If you want to abort the commit, simply delete the buffer."
    (interactive)
+   (log-edit-clean-change-log)
    ;; Get rid of trailing empty lines
    (goto-char (point-max))
    (skip-syntax-backward " ")
***************
*** 442,448 ****
    "Show the diff for the files to be committed."
    (interactive)
    (if (functionp log-edit-diff-function)
!       (funcall log-edit-diff-function)
      (error "Diff functionality has not been setup")))
  
  (defun log-edit-show-files ()
--- 476,486 ----
    "Show the diff for the files to be committed."
    (interactive)
    (if (functionp log-edit-diff-function)
!       (let ((current-buffer (buffer-name)))
!         (funcall log-edit-diff-function)
!         ;; Set destination log buffer for this diff (see `change-log-buffer-name')
!         (set (make-local-variable 'change-log-buffer-name) current-buffer)
!         )
      (error "Diff functionality has not been setup")))
  
  (defun log-edit-show-files ()
Index: add-log.el
===================================================================
RCS file: /sources/emacs/emacs/lisp/add-log.el,v
retrieving revision 1.227
diff -u -c -r1.227 add-log.el
cvs diff: conflicting specifications of output style
*** add-log.el	7 Jan 2009 16:59:38 -0000	1.227
--- add-log.el	14 Jun 2009 20:52:29 -0000
***************
*** 30,38 ****
  ;; - Find/use/create _MTN/log if there's a _MTN directory.
  ;; - Find/use/create ++log.* if there's an {arch} directory.
  ;; - Use an open *VC-Log* or *cvs-commit* buffer if it's related to the
! ;;   source file.
  ;; - Don't add TAB indents (and username?) if inserting entries in those
  ;;   special places.
  
  ;;; Code:
  
--- 30,46 ----
  ;; - Find/use/create _MTN/log if there's a _MTN directory.
  ;; - Find/use/create ++log.* if there's an {arch} directory.
  ;; - Use an open *VC-Log* or *cvs-commit* buffer if it's related to the
! ;;   source file. Resolved with `change-log-use-buffer'?
  ;; - Don't add TAB indents (and username?) if inserting entries in those
  ;;   special places.
+ ;; - Document new `change-log-use-buffer' functionality in manual.
+ ;; - `change-log-minor-mode' does not seem to have all
+ ;;   `change-log-mode' properties active (e.g. font faces).
+ ;; - Should provide an option for inserting new entries at the end of 
+ ;;   current chunk. This would be useful to insert entries for different 
+ ;;   files that have been changed in the same commit, thus keeping the 
+ ;;   same sorting of files shown by a commit diff. Note that changes 
+ ;;   inside a single file are kept in order (e.g. function names).
  
  ;;; Code:
  
***************
*** 52,57 ****
--- 60,91 ----
    :type '(choice (const :tag "default" nil)
  		 string)
    :group 'change-log)
+ 
+ (defcustom change-log-use-buffer nil
+   "If non-nil, use a buffer instead of a change log file.
+ See `change-log-buffer-name' for more information."
+   :type 'boolean
+   :group 'change-log)
+ 
+ (defcustom change-log-buffer-name nil
+   "If non-nil, the buffer name to use instead of a change log file.
+ When calling `add-change-log-entry', if `change-log-use-buffer'
+ is non-nil, a buffer named `change-log-buffer-name' will be used
+ to insert the entries instead of searching for a suitable change log
+ file.
+ 
+ If the buffer name is nil, the change log file is still used.
+ 
+ This can be used by version control systems to specify a destination
+ buffer for the commit logs, in case the user does not want to write
+ updates into a change log file.
+ 
+ Specifically, this has been intregrated with `log-edit-show-diff'
+ which sets `change-log-buffer-name'. You can insert changes into
+ the log buffer if you activate `change-log-use-buffer'."
+   :type 'string
+   :group 'change-log)
+ 
  ;;;###autoload
  (put 'change-log-default-name 'safe-local-variable 'string-or-null-p)
  
***************
*** 625,647 ****
    (interactive "*p")
    (add-log-edit-prev-comment (- arg)))
  
  ;;;###autoload
  (defun prompt-for-change-log-name ()
    "Prompt for a change log name."
!   (let* ((default (change-log-name))
! 	 (name (expand-file-name
! 		(read-file-name (format "Log file (default %s): " default)
! 				nil default))))
!     ;; Handle something that is syntactically a directory name.
!     ;; Look for ChangeLog or whatever in that directory.
!     (if (string= (file-name-nondirectory name) "")
! 	(expand-file-name (file-name-nondirectory default)
! 			  name)
!       ;; Handle specifying a file that is a directory.
!       (if (file-directory-p name)
! 	  (expand-file-name (file-name-nondirectory default)
! 			    (file-name-as-directory name))
! 	name))))
  
  (defun change-log-version-number-search ()
    "Return version number of current buffer's file.
--- 659,690 ----
    (interactive "*p")
    (add-log-edit-prev-comment (- arg)))
  
+ (defun change-log-check-use-buffer (buffer-name)
+   "Return if a buffer must be used instead of a change log file."
+   (and change-log-use-buffer buffer-name))
+ 
  ;;;###autoload
  (defun prompt-for-change-log-name ()
    "Prompt for a change log name."
!   (if (change-log-check-use-buffer change-log-buffer-name)
!       (let* ((default change-log-buffer-name)
!              (name (read-buffer "Log buffer" default nil)))
!         name)
!     (let* ((default (change-log-name))
!            (name (expand-file-name
!                   (read-file-name (format "Log file (default %s): " default)
!                                   nil default))
!                  ))
!       ;; Handle something that is syntactically a directory name.
!       ;; Look for ChangeLog or whatever in that directory.
!       (if (string= (file-name-nondirectory name) "")
!           (expand-file-name (file-name-nondirectory default)
!                             name)
!         ;; Handle specifying a file that is a directory.
!         (if (file-directory-p name)
!             (expand-file-name (file-name-nondirectory default)
!                               (file-name-as-directory name))
!           name)))))
  
  (defun change-log-version-number-search ()
    "Return version number of current buffer's file.
***************
*** 753,766 ****
  	buffer-file))))
  
  ;;;###autoload
! (defun add-change-log-entry (&optional whoami file-name other-window new-entry
  				       put-new-entry-on-new-line)
    "Find change log file, and add an entry for today and an item for this file.
  Optional arg WHOAMI (interactive prefix) non-nil means prompt for user
  name and email (stored in `add-log-full-name' and `add-log-mailing-address').
  
! Second arg FILE-NAME is file name of the change log.
! If nil, use the value of `change-log-default-name'.
  
  Third arg OTHER-WINDOW non-nil means visit in other window.
  
--- 796,815 ----
  	buffer-file))))
  
  ;;;###autoload
! (defun add-change-log-entry (&optional whoami name other-window new-entry
  				       put-new-entry-on-new-line)
    "Find change log file, and add an entry for today and an item for this file.
  Optional arg WHOAMI (interactive prefix) non-nil means prompt for user
  name and email (stored in `add-log-full-name' and `add-log-mailing-address').
  
! Second arg NAME is evaluated depending on the value of
! `change-log-use-buffer'.
! 
! When using a buffer, is the name of the buffer to use for inserting
! the changes. If nil, use the value of `change-log-buffer-name'.
! 
! When using a file, is the file name of the change log file. If
! nil, use the value of `change-log-default-name'.
  
  Third arg OTHER-WINDOW non-nil means visit in other window.
  
***************
*** 787,806 ****
    (let* ((defun (add-log-current-defun))
  	 (version (and change-log-version-info-enabled
  		       (change-log-version-number-search)))
  	 (buf-file-name (if add-log-buffer-file-name-function
  			    (funcall add-log-buffer-file-name-function)
  			  buffer-file-name))
! 	 (buffer-file (if buf-file-name (expand-file-name buf-file-name)))
! 	 (file-name (expand-file-name (find-change-log file-name buffer-file)))
  	 ;; Set ITEM to the file name to use in the new item.
! 	 (item (add-log-file-name buffer-file file-name)))
  
!     (unless (equal file-name buffer-file-name)
!       (if (or other-window (window-dedicated-p (selected-window)))
! 	  (find-file-other-window file-name)
! 	(find-file file-name)))
!     (or (derived-mode-p 'change-log-mode)
! 	(change-log-mode))
      (undo-boundary)
      (goto-char (point-min))
  
--- 836,874 ----
    (let* ((defun (add-log-current-defun))
  	 (version (and change-log-version-info-enabled
  		       (change-log-version-number-search)))
+      ;; TODO: ?
  	 (buf-file-name (if add-log-buffer-file-name-function
  			    (funcall add-log-buffer-file-name-function)
  			  buffer-file-name))
!          ;; Use a buffer instead of a file
!          (use-buffer (change-log-check-use-buffer (or name
!                                                       change-log-buffer-name)))
!      ;; TODO: ?
! 	 (buffer-file (if buf-file-name (expand-file-name
!                                          buf-file-name)))
!      ;; Name of file/buffer to insert entries into
! 	 (name (if use-buffer
!                    (or name change-log-buffer-name)
!                  (expand-file-name (find-change-log name
!                                                     buffer-file))))
!      ;; TODO: ?
  	 ;; Set ITEM to the file name to use in the new item.
! 	 (item (add-log-file-name buffer-file name)))
  
!     (unless (equal name buffer-file-name)
!       ;; TODO: [use-buffer] don't know what 'unless' checks
!       (if use-buffer
!           (if (or other-window (window-dedicated-p (selected-window)))
!               (switch-to-buffer-other-window name)
!             (switch-to-buffer name))
!         (if (or other-window (window-dedicated-p (selected-window)))
!             (find-file-other-window name)
!           (find-file name))))
!     (if use-buffer
!         (change-log-minor-mode t)
!       (or (derived-mode-p 'change-log-mode)
!           (change-log-mode))
!       )
      (undo-boundary)
      (goto-char (point-min))
  
***************
*** 937,950 ****
        (if version (insert version ?\s)))))
  
  ;;;###autoload
! (defun add-change-log-entry-other-window (&optional whoami file-name)
    "Find change log file in other window and add entry and item.
  This is just like `add-change-log-entry' except that it displays
! the change log file in another window."
    (interactive (if current-prefix-arg
  		   (list current-prefix-arg
  			 (prompt-for-change-log-name))))
!   (add-change-log-entry whoami file-name t))

  (defvar change-log-indent-text 0)
--- 1005,1018 ----
        (if version (insert version ?\s)))))
  
  ;;;###autoload
! (defun add-change-log-entry-other-window (&optional whoami name)
    "Find change log file in other window and add entry and item.
  This is just like `add-change-log-entry' except that it displays
! the change log in another window."
    (interactive (if current-prefix-arg
  		   (list current-prefix-arg
  			 (prompt-for-change-log-name))))
!   (add-change-log-entry whoami name t))

  (defvar change-log-indent-text 0)
***************
*** 1005,1018 ****
  (defvar smerge-resolve-function)
  (defvar copyright-at-end-flag)
  
! ;;;###autoload
! (define-derived-mode change-log-mode text-mode "Change Log"
!   "Major mode for editing change logs; like Indented Text mode.
! Prevents numeric backups and sets `left-margin' to 8 and `fill-column' to 74.
! New log entries are usually made with \\[add-change-log-entry] or \\[add-change-log-entry-other-window].
! Each entry behaves as a paragraph, and the entries for one day as a page.
! Runs `change-log-mode-hook'.
! \n\\{change-log-mode-map}"
    (setq left-margin 8
  	fill-column 74
  	indent-tabs-mode t
--- 1073,1079 ----
  (defvar smerge-resolve-function)
  (defvar copyright-at-end-flag)
  
! (defun change-log-mode-settings ()
    (setq left-margin 8
  	fill-column 74
  	indent-tabs-mode t
***************
*** 1053,1058 ****
--- 1114,1145 ----
    (setq next-error-function 'change-log-next-error)
    (setq next-error-last-buffer (current-buffer)))
  
+ ;;;###autoload
+ (define-minor-mode change-log-minor-mode
+   "Minor mode for edition change logs.
+ This minor mode can be used when using a buffer from
+ `change-log-use-buffer' that is in another mode."
+   ;; The initial value.
+   nil
+   ;; The indicator for the mode.
+   " Change Log"
+   ;; The minor mode bindings
+   ;; TODO: current change-log-mode-map redefines some bindings in
+   ;; log-edit-mode-map, so no keymap is loaded for this minor mode
+   nil ;; 'change-log-mode-map
+   ;; The body of the mode
+   (change-log-mode-settings))
+ 
+ ;;;###autoload
+ (define-derived-mode change-log-mode text-mode "Change Log"
+   "Major mode for editing change logs; like Indented Text mode.
+ Prevents numeric backups and sets `left-margin' to 8 and `fill-column' to 74.
+ New log entries are usually made with \\[add-change-log-entry] or \\[add-change-log-entry-other-window].
+ Each entry behaves as a paragraph, and the entries for one day as a page.
+ Runs `change-log-mode-hook'.
+ \n\\{change-log-mode-map}"
+   (change-log-mode-settings))
+ 
  (defun change-log-next-buffer (&optional buffer wrap)
    "Return the next buffer in the series of ChangeLog file buffers.
  This function is used for multiple buffers isearch.