From debbugs-submit-bounces@debbugs.gnu.org Mon Dec 30 07:43:38 2013 Received: (at submit) by debbugs.gnu.org; 30 Dec 2013 12:43:38 +0000 Received: from localhost ([127.0.0.1]:51921 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.80) (envelope-from ) id 1VxcC1-0004TM-Ro for submit@debbugs.gnu.org; Mon, 30 Dec 2013 07:43:38 -0500 Received: from eggs.gnu.org ([208.118.235.92]:39621) by debbugs.gnu.org with esmtp (Exim 4.80) (envelope-from ) id 1VxcBz-0004TA-QJ for submit@debbugs.gnu.org; Mon, 30 Dec 2013 07:43:36 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1VxcBv-0005Oh-67 for submit@debbugs.gnu.org; Mon, 30 Dec 2013 07:43:35 -0500 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on eggs.gnu.org X-Spam-Level: X-Spam-Status: No, score=-0.5 required=5.0 tests=BAYES_05 autolearn=disabled version=3.3.2 Received: from lists.gnu.org ([2001:4830:134:3::11]:49416) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1VxcBv-0005OY-2p for submit@debbugs.gnu.org; Mon, 30 Dec 2013 07:43:31 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:41280) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1VxcBq-0007dp-MQ for bug-gnu-emacs@gnu.org; Mon, 30 Dec 2013 07:43:30 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1VxcBm-0005MH-6J for bug-gnu-emacs@gnu.org; Mon, 30 Dec 2013 07:43:26 -0500 Received: from zion.baby-gnu.net ([82.225.168.180]:50426 helo=zion.baby-gnu.org) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1VxcBl-0005L8-TY for bug-gnu-emacs@gnu.org; Mon, 30 Dec 2013 07:43:22 -0500 Received: from hati.asgardr.info ([192.168.1.2] helo=hati.baby-gnu.org) by zion.baby-gnu.org with esmtp (Exim 4.82) (envelope-from ) id 1VxcBY-0001Cs-3V for bug-gnu-emacs@gnu.org; Mon, 30 Dec 2013 13:43:08 +0100 From: Daniel Dehennin To: bug-gnu-emacs@gnu.org Subject: 24.3.50; [Feature] Split add-change-log-entry to make it reusable Organisation: Dark Church of Emacs Date: Mon, 30 Dec 2013 13:43:00 +0100 Message-ID: <87bnzyv7aj.fsf@hati.baby-gnu.org> User-Agent: Gnus/5.130006 (Ma Gnus v0.6) Emacs/24.3.50 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/signed; boundary="=-=-="; micalg=pgp-sha512; protocol="application/pgp-signature" X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-detected-operating-system: by eggs.gnu.org: Error: Malformed IPv6 address (bad octet value). X-Received-From: 2001:4830:134:3::11 X-Spam-Score: -5.0 (-----) X-Debbugs-Envelope-To: submit X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -5.0 (-----) --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Hello, I just start to use magit in complement[1] of vc-git and was wondering why there is no feature like =E2=80=9Cadd-change-log-entry(-other-window)= =E2=80=9D that insert in the commit log instead of creating/adding to a plain ChangeLog. I finally found[2] the =E2=80=9Cmagit-commit-add-log=E2=80=9D 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 =E2=80=9Cadd-change-log-entry=E2=80=9D function to m= ake 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 =E2=80=9Cnew entry template=E2=80=9D: this could be= useful to inform about best practice, for example, magit could use: #+begin_src git-commit Subject =3D=3D 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 =E2=80=9Cspecial property=E2=80=9D: + user place point on first line + user hit =E2=80=9CA=E2=80=9D + =E2=80=9Cspecial property=E2=80=9D delete the line, the =E2=80=9Cspe= cial property=E2=80=9D is removed for this line (not the others) + =E2=80=9CA=E2=80=9D 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=3D/usr --sharedstatedir=3D/var/lib --libexecdir=3D/usr/lib --localstatedir=3D/var --infodir=3D/usr/share/info/emacs-snapshot --mandir=3D/usr/share/man --with-pop=3Dyes --enable-locallisppath=3D/etc/emacs-snapshot:/etc/emacs:/usr/local/share/e= macs/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=3D/usr/lib/x86_64-linux-gnu/ --with-x=3Dyes --with-x-toolkit=3Dgtk3 --with-imagemagick=3Dyes 'CFLAGS=3D-DDEBIAN -DSITELOAD_PURESIZE_EXTRA=3D5000 -g -O2' CPPFLAGS=3D-D_FORTIFY_SOURCE=3D2 'LDFLAGS=3D-g -Wl,--as-needed -znocombreloc'' Important settings: value of $LANG: fr_FR.UTF-8 locale-coding-system: utf-8-unix Major mode: Magit Footnotes:=20 [1] http://git.baby-gnu.net/gitweb/gitweb.cgi?p=3Duser/dad/config/emacs.gi= t;a=3Dblob;f=3Dlisp/startup.d/VC.el [2] https://github.com/magit/magit/issues/1130 [3] http://www.emacswiki.org/emacs/DistributedVersionControl =2D-=20 Daniel Dehennin R=C3=A9cup=C3=A9rer ma clef GPG: gpg --keyserver pgp.mit.edu --recv-keys 0x7A6FE2DF --=-=-= Content-Type: application/pgp-signature -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iF4EAREKAAYFAlLBalsACgkQFrLRMcygGktOGgD+P5YRSANqJoiPEZTF8y1S0IVs o+5YpAxkTPcnLUtruQUA/0pjTitCR9FrVICjd697gCROjSYsepy0fYB5rBGXyVJx =SidY -----END PGP SIGNATURE----- --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Wed Jan 01 23:12:05 2014 Received: (at 16301) by debbugs.gnu.org; 2 Jan 2014 04:12:05 +0000 Received: from localhost ([127.0.0.1]:57018 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.80) (envelope-from ) id 1VyZdd-0002od-9T for submit@debbugs.gnu.org; Wed, 01 Jan 2014 23:12:05 -0500 Received: from ironport2-out.teksavvy.com ([206.248.154.181]:64728) by debbugs.gnu.org with esmtp (Exim 4.80) (envelope-from ) id 1VyZdb-0002oS-1m for 16301@debbugs.gnu.org; Wed, 01 Jan 2014 23:12:03 -0500 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: AgEFABK/CFHO+J7K/2dsb2JhbABEhke0boNZF3OCHgEBBAEjMyMFCwsaAhgOAgIUGA0kiB4Grl+SToEjjlSBEwOIYZwZgV6DFQ X-IPAS-Result: AgEFABK/CFHO+J7K/2dsb2JhbABEhke0boNZF3OCHgEBBAEjMyMFCwsaAhgOAgIUGA0kiB4Grl+SToEjjlSBEwOIYZwZgV6DFQ X-IronPort-AV: E=Sophos;i="4.84,565,1355115600"; d="scan'208";a="43740981" Received: from 206-248-158-202.dsl.teksavvy.com (HELO pastel.home) ([206.248.158.202]) by ironport2-out.teksavvy.com with ESMTP/TLS/ADH-AES256-SHA; 01 Jan 2014 23:12:02 -0500 Received: by pastel.home (Postfix, from userid 20848) id 0EED960051; Wed, 1 Jan 2014 23:12:02 -0500 (EST) From: Stefan Monnier To: Daniel Dehennin Subject: Re: bug#16301: 24.3.50; [Feature] Split add-change-log-entry to make it reusable Message-ID: References: <87bnzyv7aj.fsf@hati.baby-gnu.org> Date: Wed, 01 Jan 2014 23:12:01 -0500 In-Reply-To: <87bnzyv7aj.fsf@hati.baby-gnu.org> (Daniel Dehennin's message of "Mon, 30 Dec 2013 13:43:00 +0100") User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.3.50 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: 0.3 (/) X-Debbugs-Envelope-To: 16301 Cc: 16301@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: 0.3 (/) > I just start to use magit in complement[1] of vc-git and was wondering > why there is no feature like =E2=80=9Cadd-change-log-entry(-other-window)= =E2=80=9D that > insert in the commit log instead of creating/adding to a plain > ChangeLog. Indeed, we need this feature. For VC as well. Stefan From debbugs-submit-bounces@debbugs.gnu.org Thu Jul 04 21:57:55 2019 Received: (at 16301) by debbugs.gnu.org; 5 Jul 2019 01:57:55 +0000 Received: from localhost ([127.0.0.1]:52175 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hjDU3-0001M7-31 for submit@debbugs.gnu.org; Thu, 04 Jul 2019 21:57:55 -0400 Received: from mail-io1-f67.google.com ([209.85.166.67]:43253) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hjDU0-0001Lt-FN for 16301@debbugs.gnu.org; Thu, 04 Jul 2019 21:57:53 -0400 Received: by mail-io1-f67.google.com with SMTP id k20so15954939ios.10 for <16301@debbugs.gnu.org>; Thu, 04 Jul 2019 18:57:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=5WHhRZr0ELhArrYdiG4fyQ+kEZ43OEVqceubn5cKrpQ=; b=RaFyW7qD4VO2mSV9Vu2Wh/sHj1OhQeBUhk6BGQjfaBB4LvAiTdWkEMNOio3gkjXPpP qJsjb12N4GSHyxT7h8ZNW2pqo2KJ9kk/xF7oY3lpU+Y9m2rw3PpohwNdHHvfARMf1NiC 6b3THsG8dMWvBPFB+qdyVRSD3/I8wIB3PPx/ypO/ZGvAL4J0iyYqn7P9DgdKz+jEvV3r R+Gqc/uH0deAOhgZY/uSUVYMeUjCR844eyiMjt7c15CDPz2xV8g/9hlkvS9d5tUQ0b8I O/wkJRkKuLPAwllo9edWL03cP69vU4b+lEXCM1SJn/ErlGPXnS40lOr8ZEfE9c1wf3XC 9L2w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to :message-id:user-agent:mime-version; bh=5WHhRZr0ELhArrYdiG4fyQ+kEZ43OEVqceubn5cKrpQ=; b=RXz/5ZQPfL8Sxbrt4Sv7Q1aZ1yv0b+1sh6NwIyIXEdH4ZvhlN2CGM5k53gAeZOTuMe V7nIJ5SIJAsTB1SODWtivdyTPouyC2I2NbC+qWeCgs12PaPh0AMprv/fd0HexqA2QmTX vjsUWsojYlhmA7zgR1ZYenkSLZNkFo8nl7FItPgs+f0ATTPNVO5o68AU7ulXvmKJJWaf 3zQ9Wkwd/wmEtDKIPUh6TCqHELAEzFZT9ZQVSz/XZCgXOlvFa802ooEdbaOOu5nOhbwa wZ3Xf/80FyIw6jqHtH1E9XaleX0XeMB5RjIsRHVfg772u51hJgAy14A4yxklCEhF0w+H hDPw== X-Gm-Message-State: APjAAAXcGPNB6/rXnvTm7OTnIzRTOBCnh53xeQ8/4VAeHvLgphUJD1yR w9R8ebg1UJcU3tjA0dVxjo+anE4f X-Google-Smtp-Source: APXvYqx/e+hJ623xE3ufJTu+d657JD9CwnNBfNIjADDUbS6BLXvNVx0bpdx6ZKbzf50vVOMwccXxZQ== X-Received: by 2002:a6b:901:: with SMTP id t1mr1446021ioi.42.1562291866655; Thu, 04 Jul 2019 18:57:46 -0700 (PDT) Received: from minid (cbl-45-2-119-34.yyz.frontiernetworks.ca. [45.2.119.34]) by smtp.gmail.com with ESMTPSA id p3sm6612003iog.70.2019.07.04.18.57.45 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Thu, 04 Jul 2019 18:57:45 -0700 (PDT) From: Noam Postavsky To: Daniel Dehennin Subject: Re: bug#16301: 24.3.50; [Feature] Split add-change-log-entry to make it reusable References: <87bnzyv7aj.fsf@hati.baby-gnu.org> Date: Thu, 04 Jul 2019 21:57:44 -0400 In-Reply-To: <87bnzyv7aj.fsf@hati.baby-gnu.org> (Daniel Dehennin's message of "Mon, 30 Dec 2013 13:43:00 +0100") Message-ID: <874l415jl3.fsf@gmail.com> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.2.90 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 16301 Cc: 16301@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Daniel Dehennin writes: > I just start to use magit in complement[1] of vc-git and was wondering > why there is no feature like =E2=80=9Cadd-change-log-entry(-other-window)= =E2=80=9D that > insert in the commit log instead of creating/adding to a plain > ChangeLog. > > I finally found[2] the =E2=80=9Cmagit-commit-add-log=E2=80=9D which behav= e like what I > had in DVC[3]. > > All these functions do near the same thing but code is duplicated. > > I propose to split the =E2=80=9Cadd-change-log-entry=E2=80=9D 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. --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=0001-Improved-ChangeLog-generation-for-vc-log-Bug-16301.patch Content-Description: patch >From 00b2e60143b9ecfbe86e993d2c29b76514368fb4 Mon Sep 17 00:00:00 2001 From: Noam Postavsky 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 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Tue Jul 16 19:47:52 2019 Received: (at 16301) by debbugs.gnu.org; 16 Jul 2019 23:47:52 +0000 Received: from localhost ([127.0.0.1]:51341 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hnXAl-00042u-B1 for submit@debbugs.gnu.org; Tue, 16 Jul 2019 19:47:52 -0400 Received: from mail-io1-f48.google.com ([209.85.166.48]:42256) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hnXAd-00042X-DY; Tue, 16 Jul 2019 19:47:45 -0400 Received: by mail-io1-f48.google.com with SMTP id e20so12534365iob.9; Tue, 16 Jul 2019 16:47:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=/XTpW+DUMc89+uS4AaNM0RjWN/Yupy+UOHMOBnb516M=; b=pnaGlk5r/OYa0d64JOcPlB6pGq7IPUcvVDRlv5BLWF2xCNyUfjuJKBKtypxpDESSab AuHi3FPlLs98/JxYI3EglA9vA7HMb/56mMT/0BdheJyhroLFc3ifZLPiYkI3wOLrUU40 +XnQ5ki8sqzWe4O9bsZ+WUbV2CkvxvfbUEsdoBrVqM/NsKK0DM9FjlrTUluEe6Wm4T7S NrvxZd++uTdWKnWVnCj8pulpfG+oiE9IxeRrjl+V+FybV9Pp2eMlkyxgkaVUlUG0ewY4 xOq4+tvse34WIazYyD+R1JIueNPC4eMUZfbExNM61N1uF6Ky2QHK5BEAk88e/qLkhU9i Q0tg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to :message-id:user-agent:mime-version; bh=/XTpW+DUMc89+uS4AaNM0RjWN/Yupy+UOHMOBnb516M=; b=Xa3JMoO23jT2D4LUjTXwqY68ImFRVHnQmJ9MXhbe6jTUNg27DNtOfc/epuWU+fFKJc Yjl/ts182b9VbjSr+N4447A0Xog6kUIQEWHFn169b5kCgh+O4p/l6a9fmeRaODSnXZlU HL5s3rg92xmeBsQM2Tky4jTB878j7cJ80nMviypYXJlAMBZU4LndEYYoj6WPxWjE9WUZ VogroW+qzNNOCuYouq50cjD0y8H0tGTSlb1R+TgPUxId/V+TFljXhwdmWabnQ0ifWHm6 ICsXSlF0FFkynBYzIJRa445p/hpYmKpT98aw16kC2b1KGxO9YPOkUwDyT9bvp2zkxv3L PWew== X-Gm-Message-State: APjAAAU9xNK7N7saeHck3TNxQAauIlwz8JWNxvtq9cJmPodukeuyMTB3 a6Az1Hlg79f5nVbvBWPB0P4e2dpn X-Google-Smtp-Source: APXvYqwxfpXKj1FV14Kq0/TFj/ycXEP1Y8dK/T1kIqLNa7rU7uJ1+mNAX0LQ/5E+88hDig7rDhfG5Q== X-Received: by 2002:a05:6602:114:: with SMTP id s20mr32019013iot.122.1563320857554; Tue, 16 Jul 2019 16:47:37 -0700 (PDT) Received: from minid (cbl-45-2-119-34.yyz.frontiernetworks.ca. [45.2.119.34]) by smtp.gmail.com with ESMTPSA id c14sm17495961ioa.22.2019.07.16.16.47.36 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Tue, 16 Jul 2019 16:47:36 -0700 (PDT) From: Noam Postavsky To: Daniel Dehennin Subject: Re: bug#16301: 24.3.50; [Feature] Split add-change-log-entry to make it reusable References: <87bnzyv7aj.fsf@hati.baby-gnu.org> <874l415jl3.fsf@gmail.com> Date: Tue, 16 Jul 2019 19:47:35 -0400 In-Reply-To: <874l415jl3.fsf@gmail.com> (Noam Postavsky's message of "Thu, 04 Jul 2019 21:57:44 -0400") Message-ID: <87ims1zgmg.fsf@gmail.com> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.2.90 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 16301 Cc: 16301@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable tags 16301 + patch quit >> I propose to split the =E2=80=9Cadd-change-log-entry=E2=80=9D function t= o 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. --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=0001-Improved-ChangeLog-generation-for-vc-log-Bug-16301.patch Content-Description: patch >From 74f28ab974fd1690381f0a4f3e47f95c8ab83ce0 Mon Sep 17 00:00:00 2001 From: Noam Postavsky 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 . + +;;; 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 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Sun Jul 28 10:13:17 2019 Received: (at 16301) by debbugs.gnu.org; 28 Jul 2019 14:13:17 +0000 Received: from localhost ([127.0.0.1]:46931 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hrjvI-0007j3-J7 for submit@debbugs.gnu.org; Sun, 28 Jul 2019 10:13:17 -0400 Received: from mail-io1-f51.google.com ([209.85.166.51]:37944) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hrjvF-0007ip-Vq for 16301@debbugs.gnu.org; Sun, 28 Jul 2019 10:13:15 -0400 Received: by mail-io1-f51.google.com with SMTP id j6so39345339ioa.5 for <16301@debbugs.gnu.org>; Sun, 28 Jul 2019 07:13:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=8aaLYCH8QWtDhSSIzDfsXXTjXxhnnpLALpG2f+2MUHA=; b=JLDSbY2ZniweqXqm1LApoZaUiroNiHjGZLyTUx6viMOD/Pbft88+i5B/q/Psr4U8sR eOHXziqIqLvJkQ9gTDsX6AwSHvWIVXybQi70SwnmVyGbJ2fCQTMpJqWhoQjpHSLbQtMY 3itCrJUZwbZXwAeuN5JkeFhmbk7EMOPpfIG3r6DqLuyIvU9SgcMtEgSEVN1M7JLxKq2j Nc5pHE2YlqTHtGBFuzDRa/3gWmtBZBwEdJNXNKKrClFW1FzLhdT5gVtsMO/yANrLzuoP edcPrBsC90A5phYdXb7pKVhunEaMLK5BOmfbqRHyHPYvb9z7h6b2UDq8vcqLP7L+8zZv avsw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to :message-id:user-agent:mime-version; bh=8aaLYCH8QWtDhSSIzDfsXXTjXxhnnpLALpG2f+2MUHA=; b=oFH0Y9qIz5EN6i9CD7oYlHkBmnHZ6SldYfaYeNOw8p0Z4FthXUJZ9JAnQrdmqxJNj6 FW00uM/FYC1dP36bVf61FTto9q8mPPxCXIO5OQRHq9OkQJP3UHhK122gs3FhJdG9LrbG /4xGQb2zCEvRK/1Gvl/i56iq7QX7z1B5ztpimUEMDBQjGiEfVjurRbnW0+kZimoA+osp U4L536cbJR4RYWbIjErMwMmKKShaCQpYTVm19AR3oM9PTkFl01StlTrDvXsTERtQlhOY MNnqoIbBXgRNoBLMyfAm1vmUr5CK0EMV1YtHIVn0T3cBttcKRxRyXvFAx/InVHg0e/fW 9dvQ== X-Gm-Message-State: APjAAAUGC1pjhrdPl73KhrWC9Xh6ADg/vXMvdChHoXH6Xy57f7+QU84O +fx9s4GJCcslSKVhBRMTpA/php3C X-Google-Smtp-Source: APXvYqzU4RR5LeopFUM52TBKRhyOQHOJvCDFgWyd+7DwUBl4bKpl/Q56fk8OFDDY9U0L7cockT/QIA== X-Received: by 2002:a02:c65a:: with SMTP id k26mr35537779jan.18.1564323186628; Sun, 28 Jul 2019 07:13:06 -0700 (PDT) Received: from minid (cbl-45-2-119-34.yyz.frontiernetworks.ca. [45.2.119.34]) by smtp.gmail.com with ESMTPSA id l14sm54863480iob.1.2019.07.28.07.13.05 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Sun, 28 Jul 2019 07:13:05 -0700 (PDT) From: Noam Postavsky To: Daniel Dehennin Subject: Re: bug#16301: 24.3.50; [Feature] Split add-change-log-entry to make it reusable References: <87bnzyv7aj.fsf@hati.baby-gnu.org> <874l415jl3.fsf@gmail.com> <87ims1zgmg.fsf@gmail.com> Date: Sun, 28 Jul 2019 10:13:05 -0400 In-Reply-To: <87ims1zgmg.fsf@gmail.com> (Noam Postavsky's message of "Tue, 16 Jul 2019 19:47:35 -0400") Message-ID: <87zhkytfgu.fsf@gmail.com> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.2.90 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 16301 Cc: 16301@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: text/plain > 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. --=-=-= Content-Type: text/plain Content-Disposition: attachment; filename=0001-Improved-ChangeLog-generation-for-vc-log-Bug-16301.patch Content-Description: patch >From a593ab35656c89e2850bbff283963c00a595a74b Mon Sep 17 00:00:00 2001 From: Noam Postavsky 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 . + +;;; 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 --=-=-=-- From debbugs-submit-bounces@debbugs.gnu.org Sat Aug 03 20:16:48 2019 Received: (at 16301) by debbugs.gnu.org; 4 Aug 2019 00:16:49 +0000 Received: from localhost ([127.0.0.1]:60207 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hu4Ce-00060N-OM for submit@debbugs.gnu.org; Sat, 03 Aug 2019 20:16:48 -0400 Received: from mail-io1-f49.google.com ([209.85.166.49]:34030) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1hu4CZ-000601-Nt; Sat, 03 Aug 2019 20:16:46 -0400 Received: by mail-io1-f49.google.com with SMTP id k8so160234468iot.1; Sat, 03 Aug 2019 17:16:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=h1+pO44/eNpoVIQYQyfxAnYKBZuvqz8Ae0dOPM6GAaw=; b=cVjnrDm2LCB0yPOkqPSPaWk6gVaxSU6HlamiGD2gbaQo6cbFPB3BKcD2FlEtaqAvHv 2BCaf9g1m2eLvcPzNvwbxz1Wsdn4xBbaSKfRapxJ9knnKFYGuiEeMhUSfxjC442M5P/Z 4/l0NzQ7Tqi9n5a9pUw4xWiGyzpHB6kNCrqLVFbeAKTu50LYKWhAhi7VAE8X5TYLiTDL TffrSukL3aF4IVThyU1ziS4SknXWDkEL9zMMtnkJuxsB89mrqFCa3yqqcXGGX4Pfklyh zLktzMND18qO0PSxrbZ/+QnepZt0O8PX4s2kD9LD366y86TAmf+PAXv9VOx4Lbq3bNuh 6D8A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to :message-id:user-agent:mime-version; bh=h1+pO44/eNpoVIQYQyfxAnYKBZuvqz8Ae0dOPM6GAaw=; b=r3bhQEFeLbXBrKPZ36dVz4n1cbsVlM6d1VYcxLpMEmETj10rYzqKI76lvy6XlxD41O 1d5ruPIGic/+BdMQQT5DHUSnQE395NTPIvKCliuLCIqYmmY51YTGqrrxGy5n2oHK16Ew jJ1eE/Yxea1bG3OmkvYEY6YdPW1rhDHN97NMYCACo2dznSsb6dmlvsmudHUm9IZLoVrk MIT9ZA0SieSNxFYdKeOzZcKMnNfSVN6VBIxw01EdBXKcZBhYui6f9yWuC9+ZiDWoLK0x zSIRuUbD6dIjr5/oMi87ntctdsVi6puvmn2wY3iLP1TVujKnEnzPzUr10uMyp6tu882e SxdQ== X-Gm-Message-State: APjAAAUygsuZ00an7ILl/yPdFhPteB6lNbcqKird0wSzQDz6sPselc9j PJqVyI3oBBfzMuNjWOhNun0J2LQa X-Google-Smtp-Source: APXvYqyG5l6xJXWyE6xQWssQpjJunvci4/gOEzSGqL1T6nha+Y72aXG5inHCqWG8HUr8qMDH9cbrGQ== X-Received: by 2002:a5d:8b52:: with SMTP id c18mr2610676iot.89.1564877798086; Sat, 03 Aug 2019 17:16:38 -0700 (PDT) Received: from minid (cbl-45-2-119-34.yyz.frontiernetworks.ca. [45.2.119.34]) by smtp.gmail.com with ESMTPSA id u4sm74558464iol.59.2019.08.03.17.16.37 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Sat, 03 Aug 2019 17:16:37 -0700 (PDT) From: Noam Postavsky To: Daniel Dehennin Subject: Re: bug#16301: 24.3.50; [Feature] Split add-change-log-entry to make it reusable References: <87bnzyv7aj.fsf@hati.baby-gnu.org> <874l415jl3.fsf@gmail.com> <87ims1zgmg.fsf@gmail.com> <87zhkytfgu.fsf@gmail.com> Date: Sat, 03 Aug 2019 20:16:36 -0400 In-Reply-To: <87zhkytfgu.fsf@gmail.com> (Noam Postavsky's message of "Sun, 28 Jul 2019 10:13:05 -0400") Message-ID: <87mugpss2j.fsf@gmail.com> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.2.90 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 16301 Cc: 16301@debbugs.gnu.org X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) tags 16301 fixed close 16301 27.1 quit Noam Postavsky 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 From unknown Sat Aug 16 16:07:18 2025 Received: (at fakecontrol) by fakecontrolmessage; To: internal_control@debbugs.gnu.org From: Debbugs Internal Request Subject: Internal Control Message-Id: bug archived. Date: Sun, 01 Sep 2019 11:24:04 +0000 User-Agent: Fakemail v42.6.9 # This is a fake control message. # # The action: # bug archived. thanks # This fakemail brought to you by your local debbugs # administrator