* bug#16301: 24.3.50; [Feature] Split add-change-log-entry to make it reusable
@ 2013-12-30 12:43 Daniel Dehennin
2014-01-02 4:12 ` Stefan Monnier
2019-07-05 1:57 ` Noam Postavsky
0 siblings, 2 replies; 6+ messages in thread
From: Daniel Dehennin @ 2013-12-30 12:43 UTC (permalink / raw)
To: 16301
[-- Attachment #1: Type: text/plain, Size: 3153 bytes --]
Hello,
I just start to use magit in complement[1] of vc-git and was wondering
why there is no feature like “add-change-log-entry(-other-window)” that
insert in the commit log instead of creating/adding to a plain
ChangeLog.
I finally found[2] the “magit-commit-add-log” which behave like what I
had in DVC[3].
All these functions do near the same thing but code is duplicated.
I propose to split the “add-change-log-entry” function to make parts
usable by third parties.
You can consider the following as pure product of my damaged brain:
A templating system could avoid much of third parties code by supporting
more than one changelog format:
1. Provide the changelog file name: .git/COMMIT_EDITMSG for magit
2. Set buffer parameter “new entry template”: this could be useful to
inform about best practice, for example, magit could use:
#+begin_src git-commit
Subject == Why this commit is necessary?
Long description of what was wrong before this commit.
* file (function): short description of the change.
#+end_src
If possible:
- each part should vanish when user modify the line by some kind of
“special property”:
+ user place point on first line
+ user hit “A”
+ “special property” delete the line, the “special property” is
removed for this line (not the others)
+ “A” is inserted a pos 0 of first line
- function calls should be usable in template, for example to fill
the list of modified files instead of a static vanish-on-write
example
3. Provide offset for items: 0 for magit as in the previous template
I don't know the feasibility of a such feature, but I think it could be
great.
Regards.
In GNU Emacs 24.3.50.1 (x86_64-pc-linux-gnu, GTK+ Version 3.8.6)
of 2013-12-26 on prometheus, modified by Debian
(emacs-snapshot package, version 2:20131226-1)
Windowing system distributor `The X.Org Foundation', version 11.0.11403000
Configured using:
`configure --build x86_64-linux-gnu --host x86_64-linux-gnu
--prefix=/usr --sharedstatedir=/var/lib --libexecdir=/usr/lib
--localstatedir=/var --infodir=/usr/share/info/emacs-snapshot
--mandir=/usr/share/man --with-pop=yes
--enable-locallisppath=/etc/emacs-snapshot:/etc/emacs:/usr/local/share/emacs/24.3.50/site-lisp:/usr/local/share/emacs/site-lisp:/usr/share/emacs/24.3.50/site-lisp:/usr/share/emacs/site-lisp
--with-crt-dir=/usr/lib/x86_64-linux-gnu/ --with-x=yes
--with-x-toolkit=gtk3 --with-imagemagick=yes 'CFLAGS=-DDEBIAN
-DSITELOAD_PURESIZE_EXTRA=5000 -g -O2' CPPFLAGS=-D_FORTIFY_SOURCE=2
'LDFLAGS=-g -Wl,--as-needed -znocombreloc''
Important settings:
value of $LANG: fr_FR.UTF-8
locale-coding-system: utf-8-unix
Major mode: Magit
Footnotes:
[1] http://git.baby-gnu.net/gitweb/gitweb.cgi?p=user/dad/config/emacs.git;a=blob;f=lisp/startup.d/VC.el
[2] https://github.com/magit/magit/issues/1130
[3] http://www.emacswiki.org/emacs/DistributedVersionControl
--
Daniel Dehennin
Récupérer ma clef GPG:
gpg --keyserver pgp.mit.edu --recv-keys 0x7A6FE2DF
[-- Attachment #2: Type: application/pgp-signature, Size: 229 bytes --]
^ permalink raw reply [flat|nested] 6+ messages in thread
* bug#16301: 24.3.50; [Feature] Split add-change-log-entry to make it reusable
2013-12-30 12:43 bug#16301: 24.3.50; [Feature] Split add-change-log-entry to make it reusable Daniel Dehennin
@ 2014-01-02 4:12 ` Stefan Monnier
2019-07-05 1:57 ` Noam Postavsky
1 sibling, 0 replies; 6+ messages in thread
From: Stefan Monnier @ 2014-01-02 4:12 UTC (permalink / raw)
To: Daniel Dehennin; +Cc: 16301
> I just start to use magit in complement[1] of vc-git and was wondering
> why there is no feature like “add-change-log-entry(-other-window)” that
> insert in the commit log instead of creating/adding to a plain
> ChangeLog.
Indeed, we need this feature. For VC as well.
Stefan
^ permalink raw reply [flat|nested] 6+ messages in thread
* bug#16301: 24.3.50; [Feature] Split add-change-log-entry to make it reusable
2013-12-30 12:43 bug#16301: 24.3.50; [Feature] Split add-change-log-entry to make it reusable Daniel Dehennin
2014-01-02 4:12 ` Stefan Monnier
@ 2019-07-05 1:57 ` Noam Postavsky
2019-07-16 23:47 ` Noam Postavsky
1 sibling, 1 reply; 6+ messages in thread
From: Noam Postavsky @ 2019-07-05 1:57 UTC (permalink / raw)
To: Daniel Dehennin; +Cc: 16301
[-- Attachment #1: Type: text/plain, Size: 713 bytes --]
Daniel Dehennin <daniel.dehennin@baby-gnu.org> writes:
> I just start to use magit in complement[1] of vc-git and was wondering
> why there is no feature like “add-change-log-entry(-other-window)” that
> insert in the commit log instead of creating/adding to a plain
> ChangeLog.
>
> I finally found[2] the “magit-commit-add-log” which behave like what I
> had in DVC[3].
>
> All these functions do near the same thing but code is duplicated.
>
> I propose to split the “add-change-log-entry” function to make parts
> usable by third parties.
I've started working on this, patch attached below. It's not entirely
baked, but I thought I'd post it now to let people know about it.
[-- Attachment #2: patch --]
[-- Type: text/plain, Size: 13884 bytes --]
From 00b2e60143b9ecfbe86e993d2c29b76514368fb4 Mon Sep 17 00:00:00 2001
From: Noam Postavsky <npostavs@gmail.com>
Date: Thu, 4 Jul 2019 20:32:39 -0400
Subject: [PATCH] Improved ChangeLog generation for vc log (Bug#16301)
* lisp/vc/add-log.el (change-log-unindented-file-names-re)
(change-log-read-entries, change-log-read-defuns)
(change-log-insert-entries): New functions.
* lisp/vc/diff-mode.el (diff-find-source-location): Fix docstring.
(diff-add-log-current-defuns): New function.
* lisp/vc/log-edit.el (log-edit-generate-changelog): New command.
(log-edit-mode-map): Bind it to C-c C-a.
(log-edit-fill-entry): New function.
(log-edit-mode): Set it as fill-paragraph-function.
(log-edit-insert-filled-defuns):
(change-log-no-margin-fill-forward-paragraph): New functions.
---
lisp/vc/add-log.el | 18 ++++++++
lisp/vc/diff-mode.el | 115 +++++++++++++++++++++++++++++++++++++++++++++++++--
lisp/vc/log-edit.el | 81 +++++++++++++++++++++++++++++++++++-
3 files changed, 209 insertions(+), 5 deletions(-)
diff --git a/lisp/vc/add-log.el b/lisp/vc/add-log.el
index f9efd44c5c..4810c0dbe9 100644
--- a/lisp/vc/add-log.el
+++ b/lisp/vc/add-log.el
@@ -309,6 +309,24 @@ 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)
+ (cl-loop while (re-search-forward change-log-unindented-file-names-re end t)
+ collect (cons (match-string-no-properties 1)
+ (change-log-read-defuns end))))
+
+(defun change-log-read-defuns (&optional end)
+ (cl-loop while (re-search-forward change-log-tag-re end t)
+ nconc (split-string (match-string-no-properties 1)
+ ",[[:blank:]]*" t)))
+
+(defun change-log-insert-entries (changelogs)
+ (cl-loop for (file . defuns) in changelogs do
+ (insert "* " file " ")
+ (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..229e901e0a 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,105 @@ 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)
+ (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)
+ (diff-find-source-location t))
+ (new-buf nil)
+ (goto-newbuf
+ ;; If APPLIED, we have NEW-TEXT in BUF, so we
+ ;; need to a buffer with OLD-TEXT to follow
+ ;; -lines.
+ (lambda ()
+ (if new-buf (set-buffer new-buf)
+ (set-buffer (generate-new-buffer " *diff-new-text*"))
+ (insert (if applied old-text new-text))
+ (funcall (buffer-local-value 'major-mode buf))
+ (setq new-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-newbuf gotobuf)))
+ (`(,+ck-eodefun ,+ck-defun)
+ (funcall make-defun-context-follower
+ (if applied gotobuf goto-newbuf))))
+ (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 new-buf)
+ (kill-buffer new-buf)))))
+ (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..736bcaf0fa 100644
--- a/lisp/vc/log-edit.el
+++ b/lisp/vc/log-edit.el
@@ -53,7 +53,8 @@ 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-a" . log-edit-insert-changelog)
+ ("\C-c\C-a" . log-edit-generate-changelog)
("\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,73 @@ 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 (defuns)
+ (cl-loop for def in defuns do
+ (when (> (+ (current-column) (string-width def)) fill-column)
+ (insert (if (memq (char-before) '(?\n ?\s))
+ "\n" ")\n")))
+ (insert (if (memq (char-before) '(?\n ?\s))
+ "(" ", ")
+ def))
+ (insert "): "))
+
+(defun log-edit-fill-entry (&optional _justify _region)
+ ;; TODO: Use arguments!
+ (pcase-let ((`(,beg ,entry-end) (log-edit-changelog-subparagraph)))
+ (if (= beg entry-end)
+ ;; Not a ChangeLog entry, fill as normal.
+ nil
+ (cl-callf copy-marker entry-end)
+ (cl-loop
+ do (goto-char beg)
+ (let ((end (if (re-search-forward "): ?\\(\\).*[^:[:blank:]\n].*$"
+ entry-end t)
+ (match-beginning 1)
+ entry-end)))
+ (goto-char beg)
+ (re-search-forward (concat "\\(?1:" change-log-unindented-file-names-re
+ " \\)\\|^\\(?1:\\)(")
+ entry-end)
+ (goto-char (setq beg (match-end 1)))
+ (log-edit-insert-filled-defuns
+ (prog1 (change-log-read-defuns end)
+ (delete-region beg end))))
+ while (setq beg (and (re-search-forward "^(" entry-end t)
+ (match-beginning 0))))
+ (set-marker entry-end nil)
+ t)))
+
+(defun change-log-no-margin-fill-forward-paragraph (n)
+ "Move N change log entries forward.
+Delete redundant parens along the way."
+ (let ((end-marker (make-marker))
+ (dir (cl-signum n)))
+ (cl-callf abs n)
+ (catch 'paragraphs-left
+ (dotimes (i n)
+ (pcase-let ((`(,beg ,end) (log-edit-changelog-subparagraph)))
+ (when (= end beg)
+ (throw 'paragraphs-left (- n i)))
+ (goto-char beg)
+ (set-marker end-marker end)
+ (cl-loop
+ do (progn (when (and (re-search-forward "):?$" (line-end-position) 'move)
+ (eq ?\( (char-after (1+ (match-end 0)))))
+ (replace-match "," t t))
+ (forward-line 1))
+ while (< (point) end-marker)
+ do (when (looking-at "^(")
+ (replace-match "" t t)))
+ (goto-char (if (< dir 0) beg (1- end-marker)))
+ (set-marker end-marker nil)
+ ))
+ 0)))
+
(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 +790,21 @@ log-edit-add-field
(replace-match (concat " " value) t t nil 1)
(insert field ": " value "\n" (if (looking-at "\n") "" "\n"))))
+(defun log-edit-generate-changelog ()
+ (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
--
2.11.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* bug#16301: 24.3.50; [Feature] Split add-change-log-entry to make it reusable
2019-07-05 1:57 ` Noam Postavsky
@ 2019-07-16 23:47 ` Noam Postavsky
2019-07-28 14:13 ` Noam Postavsky
0 siblings, 1 reply; 6+ messages in thread
From: Noam Postavsky @ 2019-07-16 23:47 UTC (permalink / raw)
To: Daniel Dehennin; +Cc: 16301
[-- Attachment #1: Type: text/plain, Size: 634 bytes --]
tags 16301 + patch
quit
>> I propose to split the “add-change-log-entry” function to make parts
>> usable by third parties.
>
> I've started working on this, patch attached below. It's not entirely
> baked, but I thought I'd post it now to let people know about it.
I've made enough progress that this should be usable by now. One
problem I noticed is that add-log-current-defun returns "foo" for lines
like
(require 'foo)
Which is usually not wanted. So we might try to make
add-log-current-defun a bit more discriminating, although missing
definitions might be more annoying than false positives.
[-- Attachment #2: patch --]
[-- Type: text/plain, Size: 20523 bytes --]
From 74f28ab974fd1690381f0a4f3e47f95c8ab83ce0 Mon Sep 17 00:00:00 2001
From: Noam Postavsky <npostavs@gmail.com>
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-generate-changelog-from-diff): New command.
(log-edit-mode-map): Bind it to C-c C-w.
(log-edit-mode): Set it as `fill-paragraph-function'.
* doc/emacs/maintaining.texi (Types of Log File, Log Buffer): Document
it.
* etc/NEWS: Announce it.
* test/lisp/vc/log-edit-tests.el (log-edit-fill-entry): New test.
---
doc/emacs/maintaining.texi | 13 ++++-
etc/NEWS | 4 ++
lisp/vc/add-log.el | 36 ++++++++++++
lisp/vc/diff-mode.el | 127 +++++++++++++++++++++++++++++++++++++++--
lisp/vc/log-edit.el | 67 ++++++++++++++++++++++
test/lisp/vc/log-edit-tests.el | 90 +++++++++++++++++++++++++++++
6 files changed, 331 insertions(+), 6 deletions(-)
create mode 100644 test/lisp/vc/log-edit-tests.el
diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi
index 4986c11103..da500338a9 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 06d5e93d02..f7b4f041f4 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -649,6 +649,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..df665887f1 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,40 @@ 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 " ")
+ (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..2397d43e16 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,117 @@ 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))
+ (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..f375e10d79 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,55 @@ 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."
+ (cl-loop for def in func-names do
+ (when (> (+ (current-column) (string-width def)) fill-column)
+ (insert (if (memq (char-before) '(?\n ?\s))
+ "\n" ")\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 normally, but don't pick up indentation.
+ do (let ((fill-indent-according-to-mode t))
+ (fill-region (progn (goto-char beg) (line-beginning-position))
+ (if defuns-beg (match-beginning 0) 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 +772,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..e48ad4f610
--- /dev/null
+++ b/test/lisp/vc/log-edit-tests.el
@@ -0,0 +1,90 @@
+;;; 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."))))
+
+;;; log-edit-tests.el ends here
--
2.11.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* bug#16301: 24.3.50; [Feature] Split add-change-log-entry to make it reusable
2019-07-16 23:47 ` Noam Postavsky
@ 2019-07-28 14:13 ` Noam Postavsky
2019-08-04 0:16 ` Noam Postavsky
0 siblings, 1 reply; 6+ messages in thread
From: Noam Postavsky @ 2019-07-28 14:13 UTC (permalink / raw)
To: Daniel Dehennin; +Cc: 16301
[-- Attachment #1: Type: text/plain, Size: 141 bytes --]
> 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.
[-- Attachment #2: patch --]
[-- Type: text/plain, Size: 24174 bytes --]
From a593ab35656c89e2850bbff283963c00a595a74b Mon Sep 17 00:00:00 2001
From: Noam Postavsky <npostavs@gmail.com>
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
^ permalink raw reply related [flat|nested] 6+ messages in thread
* bug#16301: 24.3.50; [Feature] Split add-change-log-entry to make it reusable
2019-07-28 14:13 ` Noam Postavsky
@ 2019-08-04 0:16 ` Noam Postavsky
0 siblings, 0 replies; 6+ messages in thread
From: Noam Postavsky @ 2019-08-04 0:16 UTC (permalink / raw)
To: Daniel Dehennin; +Cc: 16301
tags 16301 fixed
close 16301 27.1
quit
Noam Postavsky <npostavs@gmail.com> writes:
>> 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.
Done.
01661f33c1 2019-08-03T20:14:52-04:00 "Improved ChangeLog generation for vc log (Bug#16301)"
https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=01661f33c11654d1fe5fe1013332db2500b7f449
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2019-08-04 0:16 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-12-30 12:43 bug#16301: 24.3.50; [Feature] Split add-change-log-entry to make it reusable Daniel Dehennin
2014-01-02 4:12 ` Stefan Monnier
2019-07-05 1:57 ` Noam Postavsky
2019-07-16 23:47 ` Noam Postavsky
2019-07-28 14:13 ` Noam Postavsky
2019-08-04 0:16 ` Noam Postavsky
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).