| 1 | ;;; log-edit.el --- Major mode for editing CVS commit messages |
| 2 | |
| 3 | ;; Copyright (C) 1999, 2000 Free Software Foundation, Inc. |
| 4 | |
| 5 | ;; Author: Stefan Monnier <monnier@cs.yale.edu> |
| 6 | ;; Keywords: pcl-cvs cvs commit log |
| 7 | ;; Revision: $Id: log-edit.el,v 1.15 2001/03/07 00:26:25 monnier Exp $ |
| 8 | |
| 9 | ;; This file is part of GNU Emacs. |
| 10 | |
| 11 | ;; GNU Emacs is free software; you can redistribute it and/or modify |
| 12 | ;; it under the terms of the GNU General Public License as published by |
| 13 | ;; the Free Software Foundation; either version 2, or (at your option) |
| 14 | ;; any later version. |
| 15 | |
| 16 | ;; GNU Emacs is distributed in the hope that it will be useful, |
| 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 19 | ;; GNU General Public License for more details. |
| 20 | |
| 21 | ;; You should have received a copy of the GNU General Public License |
| 22 | ;; along with GNU Emacs; see the file COPYING. If not, write to the |
| 23 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| 24 | ;; Boston, MA 02111-1307, USA. |
| 25 | |
| 26 | ;;; Commentary: |
| 27 | |
| 28 | ;; Todo: |
| 29 | |
| 30 | ;; - Move in VC's code |
| 31 | ;; - Add compatibility for VC's hook variables |
| 32 | |
| 33 | ;;; Code: |
| 34 | |
| 35 | (eval-when-compile (require 'cl)) |
| 36 | (require 'add-log) ; for all the ChangeLog goodies |
| 37 | (require 'pcvs-util) |
| 38 | (require 'ring) |
| 39 | (require 'vc) |
| 40 | |
| 41 | ;;;; |
| 42 | ;;;; Global Variables |
| 43 | ;;;; |
| 44 | |
| 45 | (defgroup log-edit nil |
| 46 | "Major mode for editing RCS and CVS commit messages." |
| 47 | :group 'pcl-cvs |
| 48 | :group 'vc ; It's used by VC. |
| 49 | :version "21.1" |
| 50 | :prefix "log-edit-") |
| 51 | |
| 52 | ;; compiler pacifiers |
| 53 | (defvar cvs-buffer) |
| 54 | |
| 55 | (easy-mmode-defmap log-edit-mode-map |
| 56 | `(("\C-c\C-c" . log-edit-done) |
| 57 | ("\C-c\C-a" . log-edit-insert-changelog) |
| 58 | ("\C-c\C-f" . log-edit-show-files) |
| 59 | ("\C-c?" . log-edit-mode-help)) |
| 60 | "Keymap for the `log-edit-mode' (to edit version control log messages)." |
| 61 | :group 'log-edit |
| 62 | :inherit (if (boundp 'vc-log-entry-mode) vc-log-entry-mode |
| 63 | (if (boundp 'vc-log-mode-map) vc-log-mode-map))) |
| 64 | |
| 65 | (easy-menu-define log-edit-menu log-edit-mode-map |
| 66 | "Menu used for `log-edit-mode'." |
| 67 | '("Log-Edit" |
| 68 | ["Done" log-edit-done |
| 69 | :help "Exit log-edit and proceed with the actual action."] |
| 70 | "--" |
| 71 | ["Insert ChangeLog" log-edit-insert-changelog] |
| 72 | ["Add to ChangeLog" log-edit-add-to-changelog] |
| 73 | "--" |
| 74 | ["List files" log-edit-show-files |
| 75 | :help "Show the list of relevant files."] |
| 76 | "--" |
| 77 | ["Previous comment" vc-previous-comment] |
| 78 | ["Next comment" vc-next-comment] |
| 79 | ["Search comment forward" vc-comment-search-forward] |
| 80 | ["Search comment backward" vc-comment-search-reverse])) |
| 81 | |
| 82 | (defcustom log-edit-confirm 'changed |
| 83 | "*If non-nil, `log-edit-done' will request confirmation. |
| 84 | If 'changed, only request confirmation if the list of files has |
| 85 | changed since the beginning of the log-edit session." |
| 86 | :group 'log-edit |
| 87 | :type '(choice (const changed) (const t) (const nil))) |
| 88 | |
| 89 | (defcustom log-edit-keep-buffer nil |
| 90 | "*If non-nil, don't hide the buffer after `log-edit-done'." |
| 91 | :group 'log-edit |
| 92 | :type 'boolean) |
| 93 | |
| 94 | (defvar cvs-commit-buffer-require-final-newline t |
| 95 | "Obsolete, use `log-edit-require-final-newline'.") |
| 96 | |
| 97 | (defcustom log-edit-require-final-newline |
| 98 | cvs-commit-buffer-require-final-newline |
| 99 | "*Enforce a newline at the end of commit log messages. |
| 100 | Enforce it silently if t, query if non-nil and don't do anything if nil." |
| 101 | :group 'log-edit |
| 102 | :type '(choice (const ask) (const t) (const nil))) |
| 103 | |
| 104 | (defcustom log-edit-setup-invert nil |
| 105 | "*Non-nil means `log-edit' should invert the meaning of its SETUP arg. |
| 106 | If SETUP is 'force, this variable has no effect." |
| 107 | :group 'log-edit |
| 108 | :type 'boolean) |
| 109 | |
| 110 | (defcustom log-edit-hook '(log-edit-insert-cvs-template |
| 111 | log-edit-insert-changelog) |
| 112 | "*Hook run at the end of `log-edit'." |
| 113 | :group 'log-edit |
| 114 | :type '(hook :options (log-edit-insert-cvs-template |
| 115 | log-edit-insert-changelog))) |
| 116 | |
| 117 | (defcustom log-edit-mode-hook (if (boundp 'vc-log-mode-hook) vc-log-mode-hook) |
| 118 | "*Hook run when entering `log-edit-mode'." |
| 119 | :group 'log-edit |
| 120 | :type 'hook) |
| 121 | |
| 122 | (defcustom log-edit-done-hook nil |
| 123 | "*Hook run before doing the actual commit. |
| 124 | This hook can be used to cleanup the message, enforce various |
| 125 | conventions, or to allow recording the message in some other database, |
| 126 | such as a bug-tracking system. The list of files about to be committed |
| 127 | can be obtained from `log-edit-files'." |
| 128 | :group 'log-edit |
| 129 | :type '(hook :options (log-edit-set-common-indentation |
| 130 | log-edit-add-to-changelog))) |
| 131 | |
| 132 | (defvar cvs-changelog-full-paragraphs t |
| 133 | "Obsolete, use `log-edit-changelog-full-paragraphs'.") |
| 134 | |
| 135 | (defvar log-edit-changelog-full-paragraphs cvs-changelog-full-paragraphs |
| 136 | "*If non-nil, include full ChangeLog paragraphs in the log. |
| 137 | This may be set in the ``local variables'' section of a ChangeLog, to |
| 138 | indicate the policy for that ChangeLog. |
| 139 | |
| 140 | A ChangeLog paragraph is a bunch of log text containing no blank lines; |
| 141 | a paragraph usually describes a set of changes with a single purpose, |
| 142 | but perhaps spanning several functions in several files. Changes in |
| 143 | different paragraphs are unrelated. |
| 144 | |
| 145 | You could argue that the log entry for a file should contain the |
| 146 | full ChangeLog paragraph mentioning the change to the file, even though |
| 147 | it may mention other files, because that gives you the full context you |
| 148 | need to understand the change. This is the behaviour you get when this |
| 149 | variable is set to t. |
| 150 | |
| 151 | On the other hand, you could argue that the log entry for a change |
| 152 | should contain only the text for the changes which occurred in that |
| 153 | file, because the log is per-file. This is the behaviour you get |
| 154 | when this variable is set to nil.") |
| 155 | |
| 156 | ;;;; Internal global or buffer-local vars |
| 157 | |
| 158 | (defconst log-edit-files-buf "*log-edit-files*") |
| 159 | (defvar log-edit-initial-files nil) |
| 160 | (defvar log-edit-callback nil) |
| 161 | (defvar log-edit-listfun nil) |
| 162 | (defvar log-edit-parent-buffer nil) |
| 163 | |
| 164 | ;;; |
| 165 | ;;; Actual code |
| 166 | ;;; |
| 167 | |
| 168 | ;;;###autoload |
| 169 | (defun log-edit (callback &optional setup listfun buffer &rest ignore) |
| 170 | "Setup a buffer to enter a log message. |
| 171 | \\<log-edit-mode-map>The buffer will be put in `log-edit-mode'. |
| 172 | If SETUP is non-nil, the buffer is then erased and `log-edit-hook' is run. |
| 173 | Mark and point will be set around the entire contents of the |
| 174 | buffer so that it is easy to kill the contents of the buffer with \\[kill-region]. |
| 175 | Once you're done editing the message, pressing \\[log-edit-done] will call |
| 176 | `log-edit-done' which will end up calling CALLBACK to do the actual commit. |
| 177 | LISTFUN if non-nil is a function of no arguments returning the list of files |
| 178 | that are concerned by the current operation (using relative names). |
| 179 | If BUFFER is non-nil `log-edit' will jump to that buffer, use it to edit the |
| 180 | log message and go back to the current buffer when done. Otherwise, it |
| 181 | uses the current buffer." |
| 182 | (let ((parent (current-buffer))) |
| 183 | (if buffer (pop-to-buffer buffer)) |
| 184 | (when (and log-edit-setup-invert (not (eq setup 'force))) |
| 185 | (setq setup (not setup))) |
| 186 | (when setup (erase-buffer)) |
| 187 | (log-edit-mode) |
| 188 | (set (make-local-variable 'log-edit-callback) callback) |
| 189 | (set (make-local-variable 'log-edit-listfun) listfun) |
| 190 | (if buffer (set (make-local-variable 'log-edit-parent-buffer) parent)) |
| 191 | (when setup (run-hooks 'log-edit-hook)) |
| 192 | (goto-char (point-min)) (push-mark (point-max)) |
| 193 | (set (make-local-variable 'log-edit-initial-files) (log-edit-files)) |
| 194 | (message (substitute-command-keys |
| 195 | "Press \\[log-edit-done] when you are done editing.")))) |
| 196 | |
| 197 | (define-derived-mode log-edit-mode text-mode "Log-Edit" |
| 198 | "Major mode for editing version-control log messages. |
| 199 | When done editing the log entry, just type \\[log-edit-done] which |
| 200 | will trigger the actual commit of the file(s). |
| 201 | Several other handy support commands are provided of course and |
| 202 | the package from which this is used might also provide additional |
| 203 | commands (under C-x v for VC, for example). |
| 204 | |
| 205 | \\{log-edit-mode-map}" |
| 206 | (make-local-variable 'vc-comment-ring-index)) |
| 207 | |
| 208 | (defun log-edit-hide-buf (&optional buf where) |
| 209 | (when (setq buf (get-buffer (or buf log-edit-files-buf))) |
| 210 | (let ((win (get-buffer-window buf where))) |
| 211 | (if win (ignore-errors (delete-window win)))) |
| 212 | (bury-buffer buf))) |
| 213 | |
| 214 | (defun log-edit-done () |
| 215 | "Finish editing the log message and commit the files. |
| 216 | If you want to abort the commit, simply delete the buffer." |
| 217 | (interactive) |
| 218 | ;; Get rid of trailing empty lines |
| 219 | (goto-char (point-max)) |
| 220 | (skip-syntax-backward " ") |
| 221 | (when (equal (char-after) ?\n) (forward-char 1)) |
| 222 | (delete-region (point) (point-max)) |
| 223 | ;; Check for final newline |
| 224 | (if (and (> (point-max) (point-min)) |
| 225 | (/= (char-before (point-max)) ?\n) |
| 226 | (or (eq log-edit-require-final-newline t) |
| 227 | (and log-edit-require-final-newline |
| 228 | (y-or-n-p |
| 229 | (format "Buffer %s does not end in newline. Add one? " |
| 230 | (buffer-name)))))) |
| 231 | (save-excursion |
| 232 | (goto-char (point-max)) |
| 233 | (insert ?\n))) |
| 234 | (let ((comment (buffer-string))) |
| 235 | (when (or (ring-empty-p vc-comment-ring) |
| 236 | (not (equal comment (ring-ref vc-comment-ring 0)))) |
| 237 | (ring-insert vc-comment-ring comment))) |
| 238 | (let ((win (get-buffer-window log-edit-files-buf))) |
| 239 | (if (and log-edit-confirm |
| 240 | (not (and (eq log-edit-confirm 'changed) |
| 241 | (equal (log-edit-files) log-edit-initial-files))) |
| 242 | (progn |
| 243 | (log-edit-show-files) |
| 244 | (not (y-or-n-p "Really commit ? ")))) |
| 245 | (progn (when (not win) (log-edit-hide-buf)) |
| 246 | (message "Oh, well! Later maybe?")) |
| 247 | (run-hooks 'log-edit-done-hook) |
| 248 | (log-edit-hide-buf) |
| 249 | (unless (or log-edit-keep-buffer (not log-edit-parent-buffer)) |
| 250 | (cvs-bury-buffer (current-buffer) log-edit-parent-buffer)) |
| 251 | (call-interactively log-edit-callback)))) |
| 252 | |
| 253 | (defun log-edit-files () |
| 254 | "Return the list of files that are about to be committed." |
| 255 | (ignore-errors (funcall log-edit-listfun))) |
| 256 | |
| 257 | |
| 258 | (defun log-edit-insert-changelog () |
| 259 | "Insert a log message by looking at the ChangeLog. |
| 260 | The idea is to write your ChangeLog entries first, and then use this |
| 261 | command to commit your changes. |
| 262 | |
| 263 | To select default log text, we: |
| 264 | - find the ChangeLog entries for the files to be checked in, |
| 265 | - verify that the top entry in the ChangeLog is on the current date |
| 266 | and by the current user; if not, we don't provide any default text, |
| 267 | - search the ChangeLog entry for paragraphs containing the names of |
| 268 | the files we're checking in, and finally |
| 269 | - use those paragraphs as the log text." |
| 270 | (interactive) |
| 271 | (log-edit-insert-changelog-entries (log-edit-files)) |
| 272 | (log-edit-set-common-indentation) |
| 273 | (goto-char (point-min)) |
| 274 | (when (looking-at "\\*\\s-+") |
| 275 | (forward-line 1) |
| 276 | (when (not (re-search-forward "^\\*\\s-+" nil t)) |
| 277 | (goto-char (point-min)) |
| 278 | (skip-chars-forward "^():") |
| 279 | (skip-chars-forward ": ") |
| 280 | (delete-region (point-min) (point))))) |
| 281 | |
| 282 | (defun log-edit-mode-help () |
| 283 | "Provide help for the `log-edit-mode-map'." |
| 284 | (interactive) |
| 285 | (if (eq last-command 'log-edit-mode-help) |
| 286 | (describe-function major-mode) |
| 287 | (message |
| 288 | (substitute-command-keys |
| 289 | "Type `\\[log-edit-done]' to finish commit. Try `\\[describe-function] log-edit-done' for more help.")))) |
| 290 | |
| 291 | (defcustom log-edit-common-indent 0 |
| 292 | "Minimum indentation to use in `log-edit-set-common-indentation'." |
| 293 | :group 'log-edit |
| 294 | :type 'integer) |
| 295 | |
| 296 | (defun log-edit-set-common-indentation () |
| 297 | "(Un)Indent the current buffer rigidly to `log-edit-common-indent'." |
| 298 | (save-excursion |
| 299 | (let ((common (point-max))) |
| 300 | (goto-char (point-min)) |
| 301 | (while (< (point) (point-max)) |
| 302 | (if (not (looking-at "^[ \t]*$")) |
| 303 | (setq common (min common (current-indentation)))) |
| 304 | (forward-line 1)) |
| 305 | (indent-rigidly (point-min) (point-max) |
| 306 | (- log-edit-common-indent common))))) |
| 307 | |
| 308 | (defun log-edit-show-files () |
| 309 | "Show the list of files to be committed." |
| 310 | (interactive) |
| 311 | (let* ((files (log-edit-files)) |
| 312 | (editbuf (current-buffer)) |
| 313 | (buf (get-buffer-create log-edit-files-buf))) |
| 314 | (with-current-buffer buf |
| 315 | (log-edit-hide-buf buf 'all) |
| 316 | (setq buffer-read-only nil) |
| 317 | (erase-buffer) |
| 318 | (cvs-insert-strings files) |
| 319 | (setq buffer-read-only t) |
| 320 | (goto-char (point-min)) |
| 321 | (save-selected-window |
| 322 | (cvs-pop-to-buffer-same-frame buf) |
| 323 | (shrink-window-if-larger-than-buffer) |
| 324 | (selected-window))))) |
| 325 | |
| 326 | (defun log-edit-insert-cvs-template () |
| 327 | "Insert the template specified by the CVS administrator, if any." |
| 328 | (interactive) |
| 329 | (when (file-readable-p "CVS/Template") |
| 330 | (insert-file-contents "CVS/Template"))) |
| 331 | |
| 332 | |
| 333 | (defun log-edit-add-to-changelog () |
| 334 | "Insert this log message into the appropriate ChangeLog file." |
| 335 | (interactive) |
| 336 | ;; Yuck! |
| 337 | (unless (string= (buffer-string) (ring-ref vc-comment-ring 0)) |
| 338 | (ring-insert vc-comment-ring (buffer-string))) |
| 339 | (dolist (f (log-edit-files)) |
| 340 | (let ((buffer-file-name (expand-file-name f))) |
| 341 | (save-excursion |
| 342 | (vc-comment-to-change-log))))) |
| 343 | |
| 344 | ;;;; |
| 345 | ;;;; functions for getting commit message from ChangeLog a file... |
| 346 | ;;;; Courtesy Jim Blandy |
| 347 | ;;;; |
| 348 | |
| 349 | (defun log-edit-narrow-changelog () |
| 350 | "Narrow to the top page of the current buffer, a ChangeLog file. |
| 351 | Actually, the narrowed region doesn't include the date line. |
| 352 | A \"page\" in a ChangeLog file is the area between two dates." |
| 353 | (or (eq major-mode 'change-log-mode) |
| 354 | (error "log-edit-narrow-changelog: current buffer isn't a ChangeLog")) |
| 355 | |
| 356 | (goto-char (point-min)) |
| 357 | |
| 358 | ;; Skip date line and subsequent blank lines. |
| 359 | (forward-line 1) |
| 360 | (if (looking-at "[ \t\n]*\n") |
| 361 | (goto-char (match-end 0))) |
| 362 | |
| 363 | (let ((start (point))) |
| 364 | (forward-page 1) |
| 365 | (narrow-to-region start (point)) |
| 366 | (goto-char (point-min)))) |
| 367 | |
| 368 | (defun log-edit-changelog-paragraph () |
| 369 | "Return the bounds of the ChangeLog paragraph containing point. |
| 370 | If we are between paragraphs, return the previous paragraph." |
| 371 | (save-excursion |
| 372 | (beginning-of-line) |
| 373 | (if (looking-at "^[ \t]*$") |
| 374 | (skip-chars-backward " \t\n" (point-min))) |
| 375 | (list (progn |
| 376 | (if (re-search-backward "^[ \t]*\n" nil 'or-to-limit) |
| 377 | (goto-char (match-end 0))) |
| 378 | (point)) |
| 379 | (if (re-search-forward "^[ \t\n]*$" nil t) |
| 380 | (match-beginning 0) |
| 381 | (point))))) |
| 382 | |
| 383 | (defun log-edit-changelog-subparagraph () |
| 384 | "Return the bounds of the ChangeLog subparagraph containing point. |
| 385 | A subparagraph is a block of non-blank lines beginning with an asterisk. |
| 386 | If we are between sub-paragraphs, return the previous subparagraph." |
| 387 | (save-excursion |
| 388 | (end-of-line) |
| 389 | (if (search-backward "*" nil t) |
| 390 | (list (progn (beginning-of-line) (point)) |
| 391 | (progn |
| 392 | (forward-line 1) |
| 393 | (if (re-search-forward "^[ \t]*[\n*]" nil t) |
| 394 | (match-beginning 0) |
| 395 | (point-max)))) |
| 396 | (list (point) (point))))) |
| 397 | |
| 398 | (defun log-edit-changelog-entry () |
| 399 | "Return the bounds of the ChangeLog entry containing point. |
| 400 | The variable `log-edit-changelog-full-paragraphs' decides whether an |
| 401 | \"entry\" is a paragraph or a subparagraph; see its documentation string |
| 402 | for more details." |
| 403 | (if log-edit-changelog-full-paragraphs |
| 404 | (log-edit-changelog-paragraph) |
| 405 | (log-edit-changelog-subparagraph))) |
| 406 | |
| 407 | (defvar user-full-name) |
| 408 | (defvar user-mail-address) |
| 409 | (defun log-edit-changelog-ours-p () |
| 410 | "See if ChangeLog entry at point is for the current user, today. |
| 411 | Return non-nil iff it is." |
| 412 | ;; Code adapted from add-change-log-entry. |
| 413 | (let ((name (or (and (boundp 'add-log-full-name) add-log-full-name) |
| 414 | (and (fboundp 'user-full-name) (user-full-name)) |
| 415 | (and (boundp 'user-full-name) user-full-name))) |
| 416 | (mail (or (and (boundp 'add-log-mailing-address) add-log-mailing-address) |
| 417 | ;;(and (fboundp 'user-mail-address) (user-mail-address)) |
| 418 | (and (boundp 'user-mail-address) user-mail-address))) |
| 419 | (time (or (and (boundp 'add-log-time-format) |
| 420 | (functionp add-log-time-format) |
| 421 | (funcall add-log-time-format)) |
| 422 | (format-time-string "%Y-%m-%d")))) |
| 423 | (looking-at (regexp-quote (format "%s %s <%s>" time name mail))))) |
| 424 | |
| 425 | (defun log-edit-changelog-entries (file) |
| 426 | "Return the ChangeLog entries for FILE, and the ChangeLog they came from. |
| 427 | The return value looks like this: |
| 428 | (LOGBUFFER (ENTRYSTART . ENTRYEND) ...) |
| 429 | where LOGBUFFER is the name of the ChangeLog buffer, and each |
| 430 | \(ENTRYSTART . ENTRYEND\) pair is a buffer region." |
| 431 | (save-excursion |
| 432 | (let ((changelog-file-name |
| 433 | (let ((default-directory |
| 434 | (file-name-directory (expand-file-name file)))) |
| 435 | ;; `find-change-log' uses `change-log-default-name' if set |
| 436 | ;; and sets it before exiting, so we need to work around |
| 437 | ;; that memoizing which is undesired here |
| 438 | (setq change-log-default-name nil) |
| 439 | (find-change-log)))) |
| 440 | (set-buffer (find-file-noselect changelog-file-name)) |
| 441 | (unless (eq major-mode 'change-log-mode) (change-log-mode)) |
| 442 | (goto-char (point-min)) |
| 443 | (if (looking-at "\\s-*\n") (goto-char (match-end 0))) |
| 444 | (if (not (log-edit-changelog-ours-p)) |
| 445 | (list (current-buffer)) |
| 446 | (save-restriction |
| 447 | (log-edit-narrow-changelog) |
| 448 | (goto-char (point-min)) |
| 449 | |
| 450 | ;; Search for the name of FILE relative to the ChangeLog. If that |
| 451 | ;; doesn't occur anywhere, they're not using full relative |
| 452 | ;; filenames in the ChangeLog, so just look for FILE; we'll accept |
| 453 | ;; some false positives. |
| 454 | (let ((pattern (file-relative-name |
| 455 | file (file-name-directory changelog-file-name)))) |
| 456 | (if (or (string= pattern "") |
| 457 | (not (save-excursion |
| 458 | (search-forward pattern nil t)))) |
| 459 | (setq pattern (file-name-nondirectory file))) |
| 460 | |
| 461 | (let (texts) |
| 462 | (while (search-forward pattern nil t) |
| 463 | (let ((entry (log-edit-changelog-entry))) |
| 464 | (push entry texts) |
| 465 | (goto-char (elt entry 1)))) |
| 466 | |
| 467 | (cons (current-buffer) texts)))))))) |
| 468 | |
| 469 | (defun log-edit-changelog-insert-entries (buffer regions) |
| 470 | "Insert those regions in BUFFER specified in REGIONS. |
| 471 | Sort REGIONS front-to-back first." |
| 472 | (let ((regions (sort regions 'car-less-than-car)) |
| 473 | (last)) |
| 474 | (dolist (region regions) |
| 475 | (when (and last (< last (car region))) (newline)) |
| 476 | (setq last (elt region 1)) |
| 477 | (apply 'insert-buffer-substring buffer region)))) |
| 478 | |
| 479 | (defun log-edit-insert-changelog-entries (files) |
| 480 | "Given a list of files FILES, insert the ChangeLog entries for them." |
| 481 | (let ((buffer-entries nil)) |
| 482 | |
| 483 | ;; Add each buffer to buffer-entries, and associate it with the list |
| 484 | ;; of entries we want from that file. |
| 485 | (dolist (file files) |
| 486 | (let* ((entries (log-edit-changelog-entries file)) |
| 487 | (pair (assq (car entries) buffer-entries))) |
| 488 | (if pair |
| 489 | (setcdr pair (cvs-union (cdr pair) (cdr entries))) |
| 490 | (push entries buffer-entries)))) |
| 491 | |
| 492 | ;; Now map over each buffer in buffer-entries, sort the entries for |
| 493 | ;; each buffer, and extract them as strings. |
| 494 | (dolist (buffer-entry buffer-entries) |
| 495 | (log-edit-changelog-insert-entries (car buffer-entry) (cdr buffer-entry)) |
| 496 | (when (cdr buffer-entry) (newline))))) |
| 497 | |
| 498 | (provide 'log-edit) |
| 499 | |
| 500 | ;;; log-edit.el ends here |