master 56154c939bf: Improve commit log entry creation from ChangeLog (bug#80928)

Stephen Berman via Mailing list for Emacs changes <[email protected]> Sat, 4 Jul 2026 09:57:20 -0400 (EDT)
Newsgroups gmane.emacs.diffs
Message-ID <[email protected]>
branch: master
commit 56154c939bf8f3cd7dbadeb91ef0a2fe4294ec65
Author: Stephen Berman <[email protected]>
Commit: Stephen Berman <[email protected]>

    Improve commit log entry creation from ChangeLog (bug#80928)
    
    * lisp/vc/log-edit.el (log-edit-insert-changelog-entry): New function.
    (log-edit-insert-changelog-entries): Use it when all log entries
    come from the same ChangeLog file or buffer.
    
    * test/lisp/vc/vc-tests/vc-test-misc.el
    (vc-test-log-message-from-changelog): New test.
    
    * doc/emacs/maintaining.texi (VC With A Merging VCS):
    (Log Buffer): Document improved creation of commit messages from
    ChangeLog.  Improve documentation of *vc-log* header lines.
    Document 'log-edit-hook'.
    (VC Directory Commands): Correct documentation of 'M' and 'U'.
    Document determination of current VC fileset without marking.
    (Format of ChangeLog): Improve documentation of multi-item entries.
    
    * etc/NEWS: Announce user-facing changes.
---
 doc/emacs/maintaining.texi            | 212 ++++++++++++++++++++++++----------
 etc/NEWS                              |   9 ++
 lisp/vc/log-edit.el                   |  68 ++++++++++-
 test/lisp/vc/vc-tests/vc-test-misc.el |  54 +++++++++
 4 files changed, 278 insertions(+), 65 deletions(-)

diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi
index e40ff8e3f26..7e893ab4fdf 100644
--- a/doc/emacs/maintaining.texi
+++ b/doc/emacs/maintaining.texi
@@ -562,10 +562,12 @@ v}.  Note that registering the files doesn't commit them; you must
 invoke @w{@kbd{C-x v v}} again to commit; see the next point.
 
 @item
-If every file in the VC fileset has been either newly-added or
-modified, commit the changed files.  To do this, Emacs pops up a
-@file{*vc-log*} buffer; type the desired log entry for the changes,
-followed by @kbd{C-c C-c} to commit.  @xref{Log Buffer}.
+If every file in the VC fileset has been either newly-added or modified,
+commit the changed files.  To do this, Emacs pops up a @file{*vc-log*}
+buffer, in which you can type the log entry for the changes (or, if the
+entry has been automatically inserted from a ChangeLog file or buffer,
+further edit it).  When you are satisfied with the log entry, type
+@kbd{C-c C-c} to commit the changes.  See @ref{Log Buffer} for details.
 
 If @kbd{C-x v v} is invoked from a buffer under Diff mode, the command
 treats the buffer as holding a set of patches for one or more files.  It
@@ -715,49 +717,83 @@ out individual files.
   When you tell VC to commit a change, it pops up a buffer named
 @file{*vc-log*}.  In this buffer, you should write a @dfn{log entry}
 describing the changes you have made (@pxref{Why Version Control?}).
-After you are done, type @kbd{C-c C-c} (@code{log-edit-done}) to exit
-the buffer and commit the change, together with your log entry.
+Alternatively, you can have the entry automatically inserted from a
+ChangeLog file or buffer (see below), which you can further edit in the
+@file{*vc-log*} buffer if necessary.  After you are done, type @kbd{C-c
+C-c} (@code{log-edit-done}) to exit the buffer and commit the change,
+together with your log entry.
+
+  In the @file{*vc-log*} buffer, you can also write one or more
+@dfn{header lines} specifying additional information to be supplied to
+the version control system.  For example, the following header line
+states that the present change was not written by you, but by another
+developer:
+
+@smallexample
+Author: J. R. Hacker <jrh@@example.com>
+@end smallexample
+
+@noindent
+Each header line must occupy a single line at the top of the buffer; the
+first line that is not a header line is treated as the start of the log
+entry.  In addition to the @samp{Author} header, most version control
+systems also support the headers @samp{Summary} (a one-line summary of
+the changeset) and @samp{Date} (a manually specified commit time).
+Emacs recognizes these as well as several VCS-specific headers.  If you
+specify a header for a VCS that does not support it, the header is
+treated as part of the log entry.
 
 @cindex Log Edit mode
 @cindex mode, Log Edit
 @vindex vc-log-mode-hook
 @c FIXME: Mention log-edit-mode-hook here?  --xfq
+@vindex log-edit-hook
   The major mode for the @file{*vc-log*} buffer is Log Edit mode, a
 variant of Text mode (@pxref{Text Mode}).  On entering Log Edit mode,
 Emacs runs the hooks @code{text-mode-hook} and @code{vc-log-mode-hook}
-(@pxref{Hooks}).
-
-  In the @file{*vc-log*} buffer, you can write one or more @dfn{header
-lines}, specifying additional information to be supplied to the
-version control system.  Each header line must occupy a single line at
-the top of the buffer; the first line that is not a header line is
-treated as the start of the log entry.  For example, the following
-header line states that the present change was not written by you, but
-by another developer:
-
-@smallexample
-Author: J. R. Hacker <jrh@@example.com>
-@end smallexample
-
-@noindent
-Apart from the @samp{Author} header, Emacs recognizes the headers
-@samp{Summary} (a one-line summary of the changeset), @samp{Date} (a
-manually-specified commit time), and @samp{Fixes} (a reference to a
-bug fixed by the change).  Not all version control systems recognize
-all headers.  If you specify a header for a system that does not
-support it, the header is treated as part of the log entry.
+(@pxref{Hooks}).  In addition, to set up the @file{*vc-log*} buffer,
+Emacs runs the hook @code{log-edit-hook}, which optionally makes several
+useful features available.  The following paragraphs describe several of
+these features, including three that are generally useful and enabled by
+default.  (To see a complete list of the features and enable or disable
+any of them, type @w{@kbd{M-x customize-option RET log-edit-hook RET}};
+@pxref{Specific Customization}.)
+
+@findex log-edit-insert-message-template
+  The function @code{log-edit-insert-message-template} produces the
+default @file{*vc-log*} buffer setup, containing @samp{Author} and
+@samp{Summary} headers, and displays a thick line separating the header
+part of the buffer from the log entry part.  If you remove
+@code{log-edit-insert-message-template} from @code{log-edit-hook}, then
+the initial @file{*vc-log*} buffer is simply empty.  The two header
+lines are initially empty.  If you are the author of the change and you
+have configured the VCS to use your name and email address in commit
+messages, you can leave the @samp{Author} header empty.  If you leave
+the @samp{Summary} header empty, most systems will use the first line of
+the log entry as the summary line (see below for a way to have the
+@samp{Summary} header automatically filled).
 
 @kindex C-c C-f @r{(Log Edit mode)}
 @findex log-edit-show-files
+  When VC pops up the @file{*vc-log*} buffer, a buffer named
+@file{*log-edit-files*} listing the current VC fileset, that is, the
+files that will be committed if you type @w{@kbd{C-c C-c}}, also pops up
+by default.  If you have removed the command @code{log-edit-show-files}
+from @code{log-edit-hook}, the @file{*log-edit-files*} buffer does not
+automatically pop up, but you can invoke the command by typing
+@w{@kbd{C-c C-f}}.  Likewise, if you kill or bury the
+@file{*log-edit-files*} buffer, typing @w{@kbd{C-c C-f}} will pop it up
+again.
+
 @kindex C-c C-d @r{(Log Edit mode)}
 @findex log-edit-show-diff
-  While in the @file{*vc-log*} buffer, the current VC fileset is
-considered to be the fileset that will be committed if you type
-@w{@kbd{C-c C-c}}.  To view a list of the files in the VC fileset,
-type @w{@kbd{C-c C-f}} (@code{log-edit-show-files}).  To view a diff
-of changes between the VC fileset and the version from which you
-started editing (@pxref{Old Revisions}), type @kbd{C-c C-d}
-(@code{log-edit-show-diff}).
+  To view a diff of changes between the VC fileset and the version from
+which you started editing (@pxref{Old Revisions}), type @kbd{C-c C-d}
+(@code{log-edit-show-diff}).  This command also selects the window
+displaying the diff.  To have VC automatically display the diff when it
+pops up the @file{*vc-log*} buffer, add @code{log-edit-maybe-show-diff}
+to @code{log-edit-hook}; unlike @kbd{C-c C-d}, this function does not
+select the window displaying the diff.
 
 @kindex C-c C-w @r{(Log Edit mode)}
 @findex log-edit-generate-changelog-from-diff
@@ -773,15 +809,37 @@ VC root, customize @code{diff-add-log-use-relative-names}.
 
 @kindex C-c C-a @r{(Log Edit mode)}
 @findex log-edit-insert-changelog
-  If the VC fileset includes one or more @file{ChangeLog} files
-(@pxref{Change Log}), type @kbd{C-c C-a}
-(@code{log-edit-insert-changelog}) to pull the relevant entries into
-the @file{*vc-log*} buffer.  If the topmost item in each
-@file{ChangeLog} was made under your user name on the current date,
-this command searches that item for entries matching the file(s) to be
-committed, and inserts them.
+  If you have added log entries for the current VC fileset to one or
+more @file{ChangeLog} files or to a temporary ChangeLog buffer
+(@pxref{Change Log}), by default VC automatically inserts these entries
+into the @file{*vc-log*} buffer on popping it up.  If you have removed
+the command @code{log-edit-insert-changelog} from @code{log-edit-hook},
+VC does not automatically insert the log entries, but you can invoke the
+command by typing @kbd{C-c C-a}.  Likewise, if you delete or otherwise
+change the content of this buffer and then decide you want to use the
+ChangeLog entries after all, type @kbd{C-c C-a} to repopulate the buffer
+with these entries.
+
+  If the relevant log entries are in one or more @file{ChangeLog} files
+and the topmost item in each file was made under your user name on the
+current date, this command searches that item (or those items) for
+entries matching the file(s) to be committed, and inserts them.  If you
+invoke the command with a prefix argument (@kbd{C-u C-c C-a}) or repeat
+it immediately (@kbd{C-c C-a C-c C-a}), it searches the topmost item
+regardless of the user name and date of the item.
+
+  If VC detects that all log entries come from the same ChangeLog file
+or buffer, then the command simply inserts the entire body of the entry
+into the @file{*vc-log*} buffer.  In addition, if the first line of the
+entry does not begin with @samp{* } (an asterisk followed by a space),
+VC considers it to be a summary line and moves it to the @samp{Summary}
+header.  Finally, if VC detects that the set of changed files listed in
+the ChangeLog entry differs from the current VC fileset, it displays a
+warning urging you to fix this discrepancy before committing the
+changes.
+
 @ifnottex
-If you are using CVS or RCS, see @ref{Change Logs and VC}, for the
+  If you are using CVS or RCS, see @ref{Change Logs and VC}, for the
 opposite way of working---generating ChangeLog entries from the Log
 Edit buffer.
 @end ifnottex
@@ -1641,9 +1699,10 @@ cannot be marked with this command if any file in its tree is marked.
 
 @item M
 If point is on a file entry, mark all files with the same status; if
-point is on a directory entry, mark all files in that directory tree
-(@code{vc-dir-mark-all-files}).  With a prefix argument, mark all
-listed files and directories.
+point is on a directory entry, mark all and only the files that are
+immediate descendents (i.e., children) of that directory
+(@code{vc-dir-mark-all-files}).  With a prefix argument, mark all listed
+files.
 
 @findex vc-dir-mark-by-regexp
 @item % m
@@ -1680,9 +1739,10 @@ active, unmark all the files in the region (@code{vc-dir-unmark}).
 
 @item U
 If point is on a file entry, unmark all files with the same status; if
-point is on a directory entry, unmark all files in that directory tree
+point is on a directory entry, unmark all and only the files that are
+immediate descendents (i.e., children) of that directory
 (@code{vc-dir-unmark-all-files}).  With a prefix argument, unmark all
-files and directories.
+listed files and directories.
 
 @item x
 Hide files with @samp{up-to-date} or @samp{ignored} status
@@ -1701,13 +1761,22 @@ Quit the VC Directory buffer, and bury it (@code{quit-window}).
   While in the VC Directory buffer, all the files that you mark with
 @kbd{m} (@code{vc-dir-mark}) or @kbd{M} (@code{vc-dir-mark-all-files})
 are in the current VC fileset.  If you mark a directory entry with
-@kbd{m}, all the listed files in that directory tree are in the
-current VC fileset.  The files and directories that belong to the
-current VC fileset are indicated with a @samp{*} character in the VC
-Directory buffer, next to their VC status.  In this way, you can set
-up a multi-file VC fileset to be acted on by VC commands like
-@w{@kbd{C-x v v}} (@pxref{Basic VC Editing}), @w{@kbd{C-x v =}}
-(@pxref{Old Revisions}), and @w{@kbd{C-x v u}} (@pxref{VC Undo}).
+@kbd{m}, all the listed files in that directory tree (i.e., not only the
+listed files of the directory but also the listed files of its
+subdirectories) are in the current VC fileset.  The files and
+directories that belong to the current VC fileset are indicated with a
+@samp{*} character in the VC Directory buffer, next to their VC status.
+In this way, you can set up a multi-file VC fileset to be acted on by VC
+commands like @w{@kbd{C-x v v}} (@pxref{Basic VC Editing}), @w{@kbd{C-x
+v =}} (@pxref{Old Revisions}), and @w{@kbd{C-x v u}} (@pxref{VC Undo}).
+
+  In a VC Directory buffer containing no marked file or directory
+entries, the current VC fileset is determined as follows.  If point is
+on a file entry, the fileset consists of just that file.  If point is on
+a directory entry, the fileset consists of all files in that directory
+tree.  Consequently, if point in on (or above) the entry @samp{./}
+(representing the working directory), the fileset consists of all listed
+files.
 
   The VC Directory buffer also defines some single-key shortcuts for
 VC commands with the @kbd{C-x v} prefix: @kbd{=}, @kbd{+}, @kbd{l},
@@ -2526,13 +2595,36 @@ dated in May 1993, with two items and one item respectively.
 @end smallexample
 
   One entry can describe several changes; each change should have its
-own item, or its own line in an item.  Normally there should be a
-blank line between items.  When items are related (parts of the same
-change, in different places), group them by leaving no blank line
-between them.
+own item, or its own line in an item.  Normally there should be a blank
+line between items.  When items are related (parts of the same change,
+in different places), group them by leaving no blank line between them.
+Such a multi-item entry often begins with a single line of text that
+succintly summarizes the whole changeset.  The entry may also contain
+additional explanatory text between the summary line and the first
+change item; the explanatory text should also be bounded by blank lines.
+Here is an example:
+
+@iftex
+@medbreak
+@end iftex
+@smallexample
+2026-02-27  Sean Whitton  <spwhitton@@spwhitton.name>
+
+        New 'log-outstanding' value for vc-log-view-type
+
+        This fixes vc-hg-print-log's choice of -r arguments to 'hg log'.
+
+        * lisp/vc/vc.el (vc-log-view-type): New 'log-outstanding' value.
+        (vc-log-outstanding): Pass it.
+        * lisp/vc/vc-git.el (vc-git-log-view-mode):
+        * lisp/vc/vc-hg.el (vc-hg-print-log, vc-hg-log-view-mode): Treat
+        'log-outstanding' the same as 'log-outgoing'.
+@end smallexample
 
-  You should put a copyright notice and permission notice at the
-end of the change log file.  Here is an example:
+  If you include one or more change log files in a software package that
+is distributed to others, you should put a copyright notice and
+permission notice at the end of each change log file.  Here is an
+example:
 
 @smallexample
 Copyright 1997--1998, 2025--2026 Free Software Foundation, Inc.
diff --git a/etc/NEWS b/etc/NEWS
index 8d89b65a3a4..d39ede2b346 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -119,6 +119,15 @@ are now buttonized, allowing using mouse or 'RET' to follow them.
 
 ** VC
 
++++
+*** Improved creation of commit log entry from ChangeLog.
+When VC detects that all log entries for the current changeset come from
+the same ChangeLog file or buffer, it now inserts the entire body of the
+ChangeLog entry into the *vc-log* buffer.  If the ChangeLog entry
+contains a summary line, VC inserts it into the *vc-log* Summary header.
+If VC detects that the set of changed files listed in the ChangeLog
+entry differs from the current VC fileset, it displays a warning.
+
 +++
 *** VC-Dir outgoing revisions count is now asynchronous.
 This means it won't get in your way even if it's slow for your
diff --git a/lisp/vc/log-edit.el b/lisp/vc/log-edit.el
index e3c92bd4d28..b7b9c32ad99 100644
--- a/lisp/vc/log-edit.el
+++ b/lisp/vc/log-edit.el
@@ -1367,16 +1367,74 @@ entry."
 	  (if (setq entry (assoc key log-entries))
 	      (setcdr entry (append (cdr entry) (list file)))
 	    (push (list key file) log-entries)))))
-    ;; Now map over log-entries, and extract the strings.
-    (dolist (log-entry (nreverse log-entries))
-      (apply 'log-edit-changelog-insert-entries
-	     (append (car log-entry) (cdr log-entry)))
-      (insert "\n"))
+    ;; Check whether all log entries come from the same ChangeLog file.
+    (if (length= (seq-uniq (mapcar #'caar log-entries)) 1)
+        ;; If so, extract the commit message in toto from that file.
+        (log-edit-insert-changelog-entry (caaar log-entries))
+      ;; Otherwise, map over the entries constructed from each ChangeLog
+      ;; file used, and extract the strings.
+      (dolist (log-entry (nreverse log-entries))
+        (apply 'log-edit-changelog-insert-entries
+               (append (car log-entry) (cdr log-entry)))
+        (insert "\n")))
     ;; No newline after the last entry.
     (when log-entries
       (delete-char -1))
     log-edit-author))
 
+(declare-function vc-deduce-fileset "vc")
+
+(defun log-edit-insert-changelog-entry (buffer)
+  "Use body of ChangeLog entry in BUFFER as commit message.
+Insert the body of the latest entry in the ChangeLog file that BUFFER is
+visiting into the \"*vc-log*\" buffer.  If the first line of the body
+does not begin with \"* \", move it to the Summary header in the
+\"*vc-log*\" buffer, thus making is the summary line of the commit
+message.
+
+If the set of files listed in the ChangeLog entry differs from the set
+of files with changes to commit according to VC, display a warning
+urging the user to correct this discrepancy before committing the
+changes."
+  (let (summary beg end files-in-changelog)
+    (with-current-buffer buffer
+      (save-restriction
+        (log-edit-narrow-changelog)
+        (setq summary (let ((s (buffer-substring-no-properties
+                               (pos-bol) (pos-eol))))
+                        (and (string-match "^\t\\([^*].+\\)$" s)
+                             (match-string 1 s)))
+              beg (or (and summary (forward-line)
+                           (skip-syntax-forward "\s-")
+                           (goto-char (pos-bol)))
+                      (point))
+              end (point-max))
+        ;; List of changed files according to the ChangeLog entry.
+        (save-excursion
+          (let* ((bfn (buffer-file-name buffer))
+                 (bn (buffer-name))
+                 (fnd (or (and bfn (file-name-directory bfn))
+                          ;; If ChangeLog buffer is not visiting a file,
+                          ;; extract the directory from the buffer name.
+                          (and
+                           (string-match "\\`\\*changes to \\(.+\\)\\*\\'" bn)
+                           (match-string 1 bn)))))
+            (while (re-search-forward "\t\\* \\([^ :\n]+\\)[ :\n]" nil t)
+              (let ((fn (concat fnd (match-string-no-properties 1))))
+                (when (file-exists-p fn)
+                  (push fn files-in-changelog))))))))
+    (log-edit-changelog-insert-entries buffer beg end)
+    (when summary (log-edit-set-header "Summary" summary))
+    (unless (seq-set-equal-p
+             (save-current-buffer
+               (nth 2 (vc-deduce-fileset nil nil 'state-model-only-files)))
+             files-in-changelog)
+      (display-warning
+       'log-edit
+       "Files in ChangeLog entry differ from files with changes to commit!
+Remove this discrepancy before committing the changes by adjusting as
+appropriate either the ChangeLog entry or the selection of files to commit."))))
+
 (defun log-edit-toggle-header (header value)
   "Toggle a boolean-type header in the current buffer.
 See `log-edit-set-header' for details."
diff --git a/test/lisp/vc/vc-tests/vc-test-misc.el b/test/lisp/vc/vc-tests/vc-test-misc.el
index 4e3f883c1cd..30c5df5b63a 100644
--- a/test/lisp/vc/vc-tests/vc-test-misc.el
+++ b/test/lisp/vc/vc-tests/vc-test-misc.el
@@ -522,5 +522,59 @@ See bug#80803 and bug#80967."
           (should-not (seq-intersection directories (vc-dir-marked-files)))
           (kill-buffer vc-dir-buf))))))
 
+(ert-deftest vc-test-log-message-from-changelog () ; bug#80928
+  "Test automatic insertion of log message from ChangeLog."
+  (skip-unless (executable-find vc-git-program))
+  (vc-test--with-author-identity 'Git
+    (let ((vc-handled-backends '(Git))
+          file-buf vc-dir-buf vc-diff-buf changelog-buf log-edit-buf
+          changelog-entry log-edit-entry)
+      (unwind-protect
+          (ert-with-temp-directory tempdir
+            (let* ((default-directory tempdir)
+                   (file (expand-file-name "README" default-directory))
+                   vc-async-checkin)
+              (vc-test--create-repo-function 'Git)
+              (write-region "hello\n" nil file)
+              (with-current-buffer (setq file-buf (find-file-noselect file))
+                (vc-register `(Git (,file)))
+                (vc-checkin (list file) 'Git)
+                (insert "Initial commit")
+                (let (vc-async-checkin)
+                  (log-edit-done))
+                (write-region "Hello\n" nil "README" nil t))
+              (vc-dir default-directory 'Git)
+              (while (vc-dir-busy) (sit-for 0.05))
+              (setq vc-dir-buf (current-buffer))
+              (save-window-excursion
+                (vc-diff)
+                (setq vc-diff-buf (current-buffer))
+                (diff-add-change-log-entries-other-window)
+                (with-current-buffer (window-buffer (frame-first-window))
+                  (setq changelog-buf (current-buffer))
+                  (insert "Change text.")
+                  (forward-line -1)
+                  (newline-and-indent)
+                  (insert "Summary line")
+                  (newline)
+                  (save-restriction
+                    (log-edit-narrow-changelog)
+                    ;; ChangeLog entry ends with "\n\n" so omit last "\n" to
+                    ;; ensure equivalence with to commit log entry in the test.
+                    (let ((s (buffer-substring-no-properties
+                              (point-min) (1- (point-max)))))
+                      (setq changelog-entry
+                            (mapconcat #'concat (string-split s "\t")))))))
+              (vc-next-action nil)
+              (setq log-edit-buf (current-buffer))
+              (goto-char (point-min))
+              (re-search-forward "^Summary: " nil t)
+              (setq log-edit-entry
+                    (buffer-substring-no-properties (point) (point-max)))
+              (should (equal changelog-entry log-edit-entry))))
+        (dolist (buf (list file-buf vc-dir-buf vc-diff-buf changelog-buf
+                           log-edit-buf "*log-edit-files*" "*vc*"))
+          (kill-buffer buf))))))
+
 (provide 'vc-test-misc)
 ;;; vc-test-misc.el ends here