summaryrefslogtreecommitdiff
path: root/lisp/vc/diff-mode.el
diff options
context:
space:
mode:
authorNoam Postavsky <npostavs@gmail.com>2019-07-04 20:32:39 -0400
committerNoam Postavsky <npostavs@gmail.com>2019-08-03 20:14:52 -0400
commit01661f33c11654d1fe5fe1013332db2500b7f449 (patch)
treed46bd831f134d495373c8eb389c11f95d7c6028a /lisp/vc/diff-mode.el
parent2d47483e815979d0ae0edd6f878b7fbb85fd72fa (diff)
downloademacs-01661f33c11654d1fe5fe1013332db2500b7f449.tar.gz
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.
Diffstat (limited to 'lisp/vc/diff-mode.el')
-rw-r--r--lisp/vc/diff-mode.el131
1 files changed, 127 insertions, 4 deletions
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 0d5dc0e1c0c..81662cafeda 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 @@ Whitespace differences are ignored."
(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 @@ Call FUN with two args (BEG and END) for each hunk."
(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."