Subject: Re: bug#16301: 24.3.50; [Feature] Split add-change-log-entry to make it reusable

From: Noam Postavsky <[email-masked]>
Date: Sun, 28 Jul 2019 10:13:05 -0400

> I've made enough progress that this should be usable by now.

I found a few more corner cases while using it.  I'll push to master
soon.

>From a593ab35656c89e2850bbff283963c00a595a74b Mon Sep 17 00:00:00 2001
From: Noam Postavsky <[email-masked]>
Date: Thu, 4 Jul 2019 20:32:39 -0400
Subject: [PATCH] Improved ChangeLog generation for vc log (Bug#16301)

* lisp/vc/diff-mode.el (diff-find-source-location): Fix docstring.

* lisp/vc/add-log.el (change-log-unindented-file-names-re)
(change-log-read-entries, change-log-read-defuns)
(change-log-insert-entries):
* lisp/vc/diff-mode.el (diff-add-log-current-defuns):
* lisp/vc/log-edit.el (log-edit--insert-filled-defuns)
(log-edit-fill-entry): New functions.
(log-edit-mode): Set `log-edit-fill-entry' as
`fill-paragraph-function'.
(log-edit-generate-changelog-from-diff): New command.
(log-edit-mode-map): Bind it to C-c C-w.
* doc/emacs/maintaining.texi (Types of Log File, Log Buffer):
* CONTRIBUTE: Document it.
* etc/NEWS: Announce it.
* test/lisp/vc/log-edit-tests.el (log-edit-fill-entry)
(log-edit-fill-entry-joining): New tests.
---
 CONTRIBUTE                     |  20 ++++---
 doc/emacs/maintaining.texi     |  13 +++-
 etc/NEWS                       |   4 ++
 lisp/vc/add-log.el             |  39 ++++++++++++
 lisp/vc/diff-mode.el           | 131 +++++++++++++++++++++++++++++++++++++++--
 lisp/vc/log-edit.el            |  75 +++++++++++++++++++++++
 test/lisp/vc/log-edit-tests.el | 113 +++++++++++++++++++++++++++++++++++
 7 files changed, 381 insertions(+), 14 deletions(-)
 create mode 100644 test/lisp/vc/log-edit-tests.el

diff --git a/CONTRIBUTE b/CONTRIBUTE
index f257fc57f0..f480ffec9b 100644
--- a/CONTRIBUTE
+++ b/CONTRIBUTE
@@ -263,18 +263,22 @@ them right the first time, so here are guidelines for formatting them:
 
 ** Generating ChangeLog entries
 
-- You can use Emacs functions to write ChangeLog entries; see
+- If you use Emacs VC, you can use 'C-c C-w' to generate formatted
+  blank ChangeLog entries from the diff being committed, then use
+  'M-q' to combine and fill them.  See 'info "(emacs) Log Buffer"'.
+
+- Alternatively, you can use Emacs functions for ChangeLog files; see
   https://www.gnu.org/software/emacs/manual/html_node/emacs/Change-Log-Commands.html
   or run 'info "(emacs)Change Log Commands"'.
 
-- If you use Emacs VC, one way to format ChangeLog entries is to create
-  a top-level ChangeLog file manually, and update it with 'C-x 4 a' as
-  usual.  Do not register the ChangeLog file under git; instead, use
-  'C-c C-a' to insert its contents into your *vc-log* buffer.
-  Or if 'log-edit-hook' includes 'log-edit-insert-changelog' (which it
-  does by default), they will be filled in for you automatically.
+  To format ChangeLog entries with Emacs VC, create a top-level
+  ChangeLog file manually, and update it with 'C-x 4 a' as usual.  Do
+  not register the ChangeLog file under git; instead, use 'C-c C-a' to
+  insert its contents into your *vc-log* buffer.  Or if
+  'log-edit-hook' includes 'log-edit-insert-changelog' (which it does
+  by default), they will be filled in for you automatically.
 
-- Alternatively, you can use the vc-dwim command to maintain commit
+- Instead of Emacs VC, you can use the vc-dwim command to maintain commit
   messages.  When you create a source directory, run the shell command
   'git-changelog-symlink-init' to create a symbolic link from
   ChangeLog to .git/c/ChangeLog.  Edit this ChangeLog via its symlink
diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi
index c3895bffb5..c6fe29ed27 100644
--- a/doc/emacs/maintaining.texi
+++ b/doc/emacs/maintaining.texi
@@ -396,8 +396,9 @@ Types of Log File
 for each change just once, then put it into both logs.  You can write
 the entry in @file{ChangeLog}, then copy it to the log buffer with
 @kbd{C-c C-a} when committing the change (@pxref{Log Buffer}).  Or you
-can write the entry in the log buffer while committing the change, and
-later use the @kbd{C-x v a} command to copy it to @file{ChangeLog}
+can write the entry in the log buffer while committing the change
+(with the help of @kbd{C-c C-w}), and later use the @kbd{C-x v a}
+command to copy it to @file{ChangeLog}
 @iftex
 (@pxref{Change Logs and VC,,,emacs-xtra, Specialized Emacs Features}).
 @end iftex
@@ -677,6 +678,14 @@ Log Buffer
 started editing (@pxref{Old Revisions}), type @kbd{C-c C-d}
 (@code{log-edit-show-diff}).
 
+@kindex C-c C-w @r{(Log Edit mode)}
+@findex log-edit-generate-changelog
+  To help generate ChangeLog entries, type @kbd{C-c C-w}
+(@code{log-edit-generate-changelog}), to generate skeleton ChangeLog
+entries, listing all changed file and function names based on the diff
+of the VC fileset.  Consecutive entries left empty will be combined by
+@kbd{C-q} (@code{fill-paragraph}).
+
 @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
diff --git a/etc/NEWS b/etc/NEWS
index 13de6bb0f8..f5e13ea88c 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -653,6 +653,10 @@ The default value is 'find-dired-sort-by-filename'.
 
 ** Change Logs and VC
 
++++
+*** New command 'log-edit-generate-changelog', bound to C-c C-w.
+This generates ChangeLog entries from the VC fileset diff.
+
 *** Recording ChangeLog entries doesn't require an actual file.
 If a ChangeLog file doesn't exist, and if the new variable
 'add-log-dont-create-changelog-file' is non-nil (which is the
diff --git a/lisp/vc/add-log.el b/lisp/vc/add-log.el
index f9efd44c5c..47a68167fb 100644
--- a/lisp/vc/add-log.el
+++ b/lisp/vc/add-log.el
@@ -36,6 +36,8 @@
 
 ;;; Code:
 
+(eval-when-compile (require 'cl-lib))
+
 (defgroup change-log nil
   "Change log maintenance."
   :group 'tools
@@ -309,6 +311,43 @@ change-log-search-file-name
 	  (re-search-forward change-log-file-names-re nil t)
 	  (match-string-no-properties 2))))))
 
+(defconst change-log-unindented-file-names-re "^[*] \\([^ ,:([\n]+\\)")
+
+(defun change-log-read-entries (&optional end)
+  "Read ChangeLog entries at point until END.
+Move point to the end of entries that were read.  Return a list
+in the same form as `diff-add-log-current-defuns'."
+  (cl-loop while (and (or (not end) (< (point) end))
+                      (looking-at change-log-unindented-file-names-re))
+           do (goto-char (match-end 0))
+           collect (cons (match-string-no-properties 1)
+                         (change-log-read-defuns end))))
+
+(defvar change-log-tag-re) ; add-log.el
+(defun change-log-read-defuns (&optional end)
+  "Read ChangeLog formatted function names at point until END.
+Move point to the end of names read and return the function names
+as a list of strings."
+  (cl-loop while (and (skip-chars-forward ":\n[:blank:]" end)
+                      (or (not end) (< (point) end))
+                      (looking-at change-log-tag-re))
+           do (goto-char (match-end 0))
+           nconc (split-string (match-string-no-properties 1)
+                               ",[[:blank:]]*" t)
+           finally do (skip-chars-backward "\n[:blank:]")))
+
+(defun change-log-insert-entries (changelogs)
+  "Format and insert CHANGELOGS into current buffer.
+CHANGELOGS is a list in the form returned by
+`diff-add-log-current-defuns'."
+  (cl-loop for (file . defuns) in changelogs do
+           (insert "* " file)
+           (if (not defuns)
+               (insert ":\n")
+             (insert " ")
+             (cl-loop for def in defuns
+                      do (insert "(" def "):\n")))))
+
 (defun change-log-find-file ()
   "Visit the file for the change under point."
   (interactive)
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 0d5dc0e1c0..81662cafed 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -54,6 +54,7 @@
 
 ;;; Code:
 (eval-when-compile (require 'cl-lib))
+(eval-when-compile (require 'subr-x))
 
 (autoload 'vc-find-revision "vc")
 (autoload 'vc-find-revision-no-save "vc")
@@ -1773,15 +1774,22 @@ diff-find-approx-text
 (defsubst diff-xor (a b) (if a (if (not b) a) b))
 
 (defun diff-find-source-location (&optional other-file reverse noprompt)
-  "Find out (BUF LINE-OFFSET POS SRC DST SWITCHED).
+  "Find current diff location within the source file.
+OTHER-FILE, if non-nil, means to look at the diff's name and line
+  numbers for the old file.  Furthermore, use `diff-vc-revisions'
+  if it's available.  If `diff-jump-to-old-file' is non-nil, the
+  sense of this parameter is reversed.  If the prefix argument is
+  8 or more, `diff-jump-to-old-file' is set to OTHER-FILE.
+REVERSE, if non-nil, switches the sense of SRC and DST (see below).
+NOPROMPT, if non-nil, means not to prompt the user.
+Return a list (BUF LINE-OFFSET (BEG . END) SRC DST SWITCHED).
 BUF is the buffer corresponding to the source file.
 LINE-OFFSET is the offset between the expected and actual positions
   of the text of the hunk or nil if the text was not found.
-POS is a pair (BEG . END) indicating the position of the text in the buffer.
+\(BEG . END) is a pair indicating the position of the text in the buffer.
 SRC and DST are the two variants of text as returned by `diff-hunk-text'.
   SRC is the variant that was found in the buffer.
-SWITCHED is non-nil if the patch is already applied.
-NOPROMPT, if non-nil, means not to prompt the user."
+SWITCHED is non-nil if the patch is already applied."
   (save-excursion
     (let* ((other (diff-xor other-file diff-jump-to-old-file))
 	   (char-offset (- (point) (diff-beginning-of-hunk t)))
@@ -2210,6 +2218,121 @@ diff-undo
   (let ((inhibit-read-only t))
     (undo arg)))
 
+(defun diff-add-log-current-defuns ()
+  "Return an alist of defun names for the current diff.
+The elements of the alist are of the form (FILE . (DEFUN...)),
+where DEFUN... is a list of function names found in FILE."
+  (save-excursion
+    (goto-char (point-min))
+    (let ((defuns nil)
+          (hunk-end nil)
+          (hunk-mismatch-files nil)
+          (make-defun-context-follower
+           (lambda (goline)
+             (let ((eodefun nil)
+                   (defname nil))
+               (list
+                (lambda () ;; Check for end of current defun.
+                  (when (and eodefun
+                             (funcall goline)
+                             (>= (point) eodefun))
+                    (setq defname nil)
+                    (setq eodefun nil)))
+                (lambda (&optional get-current) ;; Check for new defun.
+                  (if get-current
+                      defname
+                    (when-let* ((def (and (not eodefun)
+                                          (funcall goline)
+                                          (add-log-current-defun)))
+                                (eof (save-excursion (end-of-defun) (point))))
+                      (setq eodefun eof)
+                      (setq defname def)))))))))
+      (while
+          ;; Might need to skip over file headers between diff
+          ;; hunks (e.g., "diff --git ..." etc).
+          (re-search-forward diff-hunk-header-re nil t)
+        (setq hunk-end (save-excursion (diff-end-of-hunk)))
+        (pcase-let* ((filename (substring-no-properties (diff-find-file-name)))
+                     (=lines 0)
+                     (+lines 0)
+                     (-lines 0)
+                     (`(,buf ,line-offset (,beg . ,_end)
+                             (,old-text . ,_old-offset)
+                             (,new-text . ,_new-offset)
+                             ,applied)
+                      ;; Try to use the vc integration of
+                      ;; `diff-find-source-location', unless it
+                      ;; would look for non-existent files like
+                      ;; /dev/null.
+                      (diff-find-source-location
+                       (not (equal "/dev/null"
+                                   (car (diff-hunk-file-names t))))))
+                     (other-buf nil)
+                     (goto-otherbuf
+                      ;; If APPLIED, we have NEW-TEXT in BUF, so we
+                      ;; need to a buffer with OLD-TEXT to follow
+                      ;; -lines.
+                      (lambda ()
+                        (if other-buf (set-buffer other-buf)
+                          (set-buffer (generate-new-buffer " *diff-other-text*"))
+                          (insert (if applied old-text new-text))
+                          (funcall (buffer-local-value 'major-mode buf))
+                          (setq other-buf (current-buffer)))
+                        (goto-char (point-min))
+                        (forward-line (+ =lines -1
+                                         (if applied -lines +lines)))))
+                     (gotobuf (lambda ()
+                                (set-buffer buf)
+                                (goto-char beg)
+                                (forward-line (+ =lines -1
+                                                 (if applied +lines -lines)))))
+                     (`(,=ck-eodefun ,=ck-defun)
+                      (funcall make-defun-context-follower gotobuf))
+                     (`(,-ck-eodefun ,-ck-defun)
+                      (funcall make-defun-context-follower
+                               (if applied goto-otherbuf gotobuf)))
+                     (`(,+ck-eodefun ,+ck-defun)
+                      (funcall make-defun-context-follower
+                               (if applied gotobuf goto-otherbuf))))
+          (unless (eql line-offset 0)
+            (cl-pushnew filename hunk-mismatch-files :test #'equal))
+          ;; Some modes always return nil for `add-log-current-defun',
+          ;; make sure at least the filename is included.
+          (unless (assoc filename defuns)
+            (push (cons filename nil) defuns))
+          (unwind-protect
+              (while (progn (forward-line)
+                            (< (point) hunk-end))
+                (let ((patch-char (char-after)))
+                  (pcase patch-char
+                    (?+ (cl-incf +lines))
+                    (?- (cl-incf -lines))
+                    (?\s (cl-incf =lines)))
+                  (save-current-buffer
+                    (funcall =ck-eodefun)
+                    (funcall +ck-eodefun)
+                    (funcall -ck-eodefun)
+                    (when-let* ((def (cond
+                                      ((eq patch-char ?\s)
+                                       ;; Just updating context defun.
+                                       (ignore (funcall =ck-defun)))
+                                      ;; + or - in existing defun.
+                                      ((funcall =ck-defun t))
+                                      ;; Check added or removed defun.
+                                      (t (funcall (if (eq ?+ patch-char)
+                                                      +ck-defun -ck-defun))))))
+                      (cl-pushnew def (alist-get filename defuns
+                                                 nil nil #'equal)
+                                  :test #'equal)))))
+            (when (buffer-live-p other-buf)
+              (kill-buffer other-buf)))))
+      (when hunk-mismatch-files
+        (message "Diff didn't match for %s."
+                 (mapconcat #'identity hunk-mismatch-files ", ")))
+      (dolist (file-defuns defuns)
+        (cl-callf nreverse (cdr file-defuns)))
+      (nreverse defuns))))
+
 (defun diff-add-change-log-entries-other-window ()
   "Iterate through the current diff and create ChangeLog entries.
 I.e. like `add-change-log-entry-other-window' but applied to all hunks."
diff --git a/lisp/vc/log-edit.el b/lisp/vc/log-edit.el
index 91e18c1ec5..8d47d66ac3 100644
--- a/lisp/vc/log-edit.el
+++ b/lisp/vc/log-edit.el
@@ -54,6 +54,7 @@ cvs-buffer
 (easy-mmode-defmap log-edit-mode-map
   '(("\C-c\C-c" . log-edit-done)
     ("\C-c\C-a" . log-edit-insert-changelog)
+    ("\C-c\C-w" . log-edit-generate-changelog-from-diff)
     ("\C-c\C-d" . log-edit-show-diff)
     ("\C-c\C-f" . log-edit-show-files)
     ("\C-c\C-k" . log-edit-kill-buffer)
@@ -488,10 +489,63 @@ log-edit-mode
   (set (make-local-variable 'font-lock-defaults)
        '(log-edit-font-lock-keywords t))
   (setq-local jit-lock-contextually t)  ;For the "first line is summary".
+  (setq-local fill-paragraph-function #'log-edit-fill-entry)
   (make-local-variable 'log-edit-comment-ring-index)
   (add-hook 'kill-buffer-hook 'log-edit-remember-comment nil t)
   (hack-dir-local-variables-non-file-buffer))
 
+(defun log-edit--insert-filled-defuns (func-names)
+  "Insert FUNC-NAMES, following ChangeLog formatting."
+  (if (not func-names)
+      (insert ":")
+    (unless (or (memq (char-before) '(?\n ?\s))
+                (> (current-column) fill-column))
+      (insert " "))
+    (cl-loop for first-fun = t then nil
+             for def in func-names do
+             (when (> (+ (current-column) (string-width def)) fill-column)
+               (unless first-fun
+                 (insert ")"))
+               (insert "\n"))
+             (insert (if (memq (char-before) '(?\n ?\s))
+                         "(" ", ")
+                     def))
+    (insert "):")))
+
+(defun log-edit-fill-entry (&optional justify)
+  "Like \\[fill-paragraph], but handle ChangeLog entries.
+Consecutive function entries without prose (i.e., lines of the
+form \"(FUNCTION):\") will be combined into \"(FUNC1, FUNC2):\"
+according to `fill-column'."
+  (save-excursion
+    (pcase-let ((`(,beg ,end) (log-edit-changelog-paragraph)))
+      (if (= beg end)
+          ;; Not a ChangeLog entry, fill as normal.
+          nil
+        (cl-callf copy-marker end)
+        (goto-char beg)
+        (cl-loop
+         for defuns-beg =
+         (and (< beg end)
+              (re-search-forward
+               (concat "\\(?1:" change-log-unindented-file-names-re
+                       "\\)\\|^\\(?1:\\)(")
+               end t)
+              (copy-marker (match-end 1)))
+         ;; Fill prose between log entries.
+         do (let ((fill-indent-according-to-mode t)
+                  (end (if defuns-beg (match-beginning 0) end))
+                  (beg (progn (goto-char beg) (line-beginning-position))))
+              (when (<= (line-end-position) end)
+                (fill-region beg end justify)))
+         while defuns-beg
+         for defuns = (progn (goto-char defuns-beg)
+                             (change-log-read-defuns end))
+         do (progn (delete-region defuns-beg (point))
+                   (log-edit--insert-filled-defuns defuns)
+                   (setq beg (point))))
+        t))))
+
 (defun log-edit-hide-buf (&optional buf where)
   (when (setq buf (get-buffer (or buf log-edit-files-buf)))
     ;; FIXME: Should use something like `quit-windows-on' here, but
@@ -726,6 +780,27 @@ log-edit-add-field
       (replace-match (concat " " value) t t nil 1)
     (insert field ": " value "\n" (if (looking-at "\n") "" "\n"))))
 
+(declare-function diff-add-log-current-defuns "diff-mode" ())
+
+(defun log-edit-generate-changelog-from-diff ()
+  "Insert a log message by looking at the current diff.
+This command will generate a ChangeLog entries listing the
+functions.  You can then add a description where needed, and use
+\\[fill-paragraph] to join consecutive function names."
+  (interactive)
+  (let* ((diff-buf nil)
+         ;; Unfortunately, `log-edit-show-diff' doesn't have a NO-SHOW
+         ;; option, so we try to work around it via display-buffer
+         ;; machinery.
+         (display-buffer-overriding-action
+          `(,(lambda (buf alist)
+               (setq diff-buf buf)
+               (display-buffer-no-window buf alist))
+            . ((allow-no-window . t)))))
+    (change-log-insert-entries
+     (with-current-buffer (progn (log-edit-show-diff) diff-buf)
+       (diff-add-log-current-defuns)))))
+
 (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
diff --git a/test/lisp/vc/log-edit-tests.el b/test/lisp/vc/log-edit-tests.el
new file mode 100644
index 0000000000..7d77eca87d
--- /dev/null
+++ b/test/lisp/vc/log-edit-tests.el
@@ -0,0 +1,113 @@
+;;; log-edit-tests.el --- Unit tests for log-edit.el  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Unit tests for lisp/vc/log-edit.el.
+
+;;; Code:
+
+(require 'log-edit)
+(require 'ert)
+
+(ert-deftest log-edit-fill-entry ()
+  (with-temp-buffer
+    (insert "\
+* dir/file.ext (fun1):
+\(fun2):
+\(fun3):
+* file2.txt (fun4):
+\(fun5):
+\(fun6):
+\(fun7): Some prose.
+\(fun8): A longer description of a complicated change.\
+  Spread over a couple of sentencences.\
+  Long enough to be filled for several lines.
+\(fun9): Etc.")
+    (goto-char (point-min))
+    (let ((fill-column 72)) (log-edit-fill-entry))
+    (should (equal (buffer-string) "\
+* dir/file.ext (fun1, fun2, fun3):
+* file2.txt (fun4, fun5, fun6, fun7): Some prose.
+\(fun8): A longer description of a complicated change.  Spread over a
+couple of sentencences.  Long enough to be filled for several lines.
+\(fun9): Etc."))
+    (let ((fill-column 20)) (log-edit-fill-entry))
+    (should (equal (buffer-string) "\
+* dir/file.ext (fun1)
+\(fun2, fun3):
+* file2.txt (fun4)
+\(fun5, fun6, fun7):
+Some prose.
+\(fun8): A longer
+description of a
+complicated change.
+Spread over a couple
+of sentencences.
+Long enough to be
+filled for several
+lines.
+\(fun9): Etc."))
+    (let ((fill-column 40)) (log-edit-fill-entry))
+    (should (equal (buffer-string) "\
+* dir/file.ext (fun1, fun2, fun3):
+* file2.txt (fun4, fun5, fun6, fun7):
+Some prose.
+\(fun8): A longer description of a
+complicated change.  Spread over a
+couple of sentencences.  Long enough to
+be filled for several lines.
+\(fun9): Etc."))))
+
+(ert-deftest log-edit-fill-entry-trailing-prose ()
+  (with-temp-buffer
+    (insert "\
+* dir/file.ext (fun1): A longer description of a complicated change.\
+  Spread over a couple of sentencences.\
+  Long enough to be filled for several lines.")
+    (let ((fill-column 72)) (log-edit-fill-entry))
+    (should (equal (buffer-string) "\
+* dir/file.ext (fun1): A longer description of a complicated change.
+Spread over a couple of sentencences.  Long enough to be filled for
+several lines."))))
+
+(ert-deftest log-edit-fill-entry-joining ()
+  ;; Join short enough function names on the same line.
+  (with-temp-buffer
+    (insert "* dir/file.ext (fun1):\n(fun2):")
+    (let ((fill-column 72)) (log-edit-fill-entry))
+    (should (equal (buffer-string) "* dir/file.ext (fun1, fun2):")))
+  ;; Don't combine them if they're too long.
+  (with-temp-buffer
+    (insert "* dir/long-file-name.ext (a-really-long-function-name):
+\(another-very-long-function-name):")
+    (let ((fill-column 72)) (log-edit-fill-entry))
+    (should (equal (buffer-string) "* dir/long-file-name.ext (a-really-long-function-name)
+\(another-very-long-function-name):")))
+  ;; Put function name on next line, if the file name is too long.
+  (with-temp-buffer
+    (insert "\
+* a-very-long-directory-name/another-long-directory-name/and-a-long-file-name.ext\
+ (a-really-long-function-name):")
+    (let ((fill-column 72)) (log-edit-fill-entry))
+    (should (equal (buffer-string) "\
+* a-very-long-directory-name/another-long-directory-name/and-a-long-file-name.ext
+\(a-really-long-function-name):"))))
+
+;;; log-edit-tests.el ends here
-- 
2.11.0