Switch to recommended form of GPLv3 permissions notice.
[bpt/emacs.git] / lisp / log-edit.el
CommitLineData
5b467bf4
SM
1;;; log-edit.el --- Major mode for editing CVS commit messages
2
c90f2757 3;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004,
409cc4a3 4;; 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
5b467bf4 5
cc1eecfd 6;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
5b467bf4 7;; Keywords: pcl-cvs cvs commit log
5b467bf4
SM
8
9;; This file is part of GNU Emacs.
10
eb3fa2cf 11;; GNU Emacs is free software: you can redistribute it and/or modify
5b467bf4 12;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
5b467bf4
SM
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
eb3fa2cf 22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
5b467bf4
SM
23
24;;; Commentary:
25
26;; Todo:
27
5b467bf4
SM
28;; - Move in VC's code
29;; - Add compatibility for VC's hook variables
5b467bf4
SM
30
31;;; Code:
32
33(eval-when-compile (require 'cl))
34(require 'add-log) ; for all the ChangeLog goodies
35(require 'pcvs-util)
36(require 'ring)
5b467bf4 37
f1180544 38;;;;
5b467bf4 39;;;; Global Variables
f1180544 40;;;;
5b467bf4
SM
41
42(defgroup log-edit nil
bc35d341 43 "Major mode for editing RCS and CVS commit messages."
5b467bf4 44 :group 'pcl-cvs
bc35d341
DL
45 :group 'vc ; It's used by VC.
46 :version "21.1"
5b467bf4
SM
47 :prefix "log-edit-")
48
49;; compiler pacifiers
50(defvar cvs-buffer)
51
12dd83de
SM
52\f
53;; The main keymap
54
5b467bf4
SM
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)
93a142e1 58 ("\C-c\C-d" . log-edit-show-diff)
5b467bf4 59 ("\C-c\C-f" . log-edit-show-files)
9fe89a26
SM
60 ("\M-n" . log-edit-next-comment)
61 ("\M-p" . log-edit-previous-comment)
62 ("\M-r" . log-edit-comment-search-backward)
63 ("\M-s" . log-edit-comment-search-forward)
0916e956 64 ("\C-c?" . log-edit-mode-help))
cdf54749 65 "Keymap for the `log-edit-mode' (to edit version control log messages)."
0916e956
SM
66 :group 'log-edit)
67
68;; Compatibility with old names. Should we bother ?
69(defvar vc-log-mode-map log-edit-mode-map)
70(defvar vc-log-entry-mode vc-log-mode-map)
5b467bf4 71
d247e32d
SM
72(easy-menu-define log-edit-menu log-edit-mode-map
73 "Menu used for `log-edit-mode'."
74 '("Log-Edit"
75 ["Done" log-edit-done
76 :help "Exit log-edit and proceed with the actual action."]
77 "--"
799224fe
DN
78 ["Insert ChangeLog" log-edit-insert-changelog
79 :help "Insert a log message by looking at the ChangeLog"]
80 ["Add to ChangeLog" log-edit-add-to-changelog
81 :help "Insert this log message into the appropriate ChangeLog file"]
d247e32d 82 "--"
93a142e1
DN
83 ["Show diff" log-edit-show-diff
84 :help "Show the diff for the files to be committed."]
d247e32d
SM
85 ["List files" log-edit-show-files
86 :help "Show the list of relevant files."]
87 "--"
799224fe
DN
88 ["Previous comment" log-edit-previous-comment
89 :help "Cycle backwards through comment history"]
90 ["Next comment" log-edit-next-comment
91 :help "Cycle forwards through comment history."]
92 ["Search comment forward" log-edit-comment-search-forward
93 :help "Search forwards through comment history for a substring match of str"]
94 ["Search comment backward" log-edit-comment-search-backward
95 :help "Search backwards through comment history for substring match of str"]))
d247e32d 96
f077c462 97(defcustom log-edit-confirm 'changed
5b467bf4
SM
98 "*If non-nil, `log-edit-done' will request confirmation.
99If 'changed, only request confirmation if the list of files has
100 changed since the beginning of the log-edit session."
101 :group 'log-edit
102 :type '(choice (const changed) (const t) (const nil)))
103
104(defcustom log-edit-keep-buffer nil
105 "*If non-nil, don't hide the buffer after `log-edit-done'."
106 :group 'log-edit
107 :type 'boolean)
108
a664185b
JB
109(defvar cvs-commit-buffer-require-final-newline t)
110(make-obsolete-variable 'cvs-commit-buffer-require-final-newline
b8ce3df9
JB
111 'log-edit-require-final-newline
112 "21.1")
5b467bf4
SM
113
114(defcustom log-edit-require-final-newline
115 cvs-commit-buffer-require-final-newline
116 "*Enforce a newline at the end of commit log messages.
117Enforce it silently if t, query if non-nil and don't do anything if nil."
118 :group 'log-edit
119 :type '(choice (const ask) (const t) (const nil)))
120
121(defcustom log-edit-setup-invert nil
122 "*Non-nil means `log-edit' should invert the meaning of its SETUP arg.
123If SETUP is 'force, this variable has no effect."
124 :group 'log-edit
125 :type 'boolean)
126
127(defcustom log-edit-hook '(log-edit-insert-cvs-template
128 log-edit-insert-changelog)
129 "*Hook run at the end of `log-edit'."
130 :group 'log-edit
f7eeab0d
SM
131 :type '(hook :options (log-edit-insert-changelog
132 log-edit-insert-cvs-rcstemplate
133 log-edit-insert-cvs-template
134 log-edit-insert-filenames)))
5b467bf4 135
3831af62 136(defcustom log-edit-mode-hook (if (boundp 'vc-log-mode-hook) vc-log-mode-hook)
5b467bf4
SM
137 "*Hook run when entering `log-edit-mode'."
138 :group 'log-edit
139 :type 'hook)
140
141(defcustom log-edit-done-hook nil
142 "*Hook run before doing the actual commit.
143This hook can be used to cleanup the message, enforce various
144conventions, or to allow recording the message in some other database,
145such as a bug-tracking system. The list of files about to be committed
146can be obtained from `log-edit-files'."
147 :group 'log-edit
43a9a0c4 148 :type '(hook :options (log-edit-set-common-indentation
5b467bf4
SM
149 log-edit-add-to-changelog)))
150
a664185b
JB
151(defvar cvs-changelog-full-paragraphs t)
152(make-obsolete-variable 'cvs-changelog-full-paragraphs
b8ce3df9
JB
153 'log-edit-changelog-full-paragraphs
154 "21.1")
14188021
SM
155
156(defvar log-edit-changelog-full-paragraphs cvs-changelog-full-paragraphs
157 "*If non-nil, include full ChangeLog paragraphs in the log.
5b467bf4
SM
158This may be set in the ``local variables'' section of a ChangeLog, to
159indicate the policy for that ChangeLog.
160
161A ChangeLog paragraph is a bunch of log text containing no blank lines;
162a paragraph usually describes a set of changes with a single purpose,
163but perhaps spanning several functions in several files. Changes in
164different paragraphs are unrelated.
165
14188021 166You could argue that the log entry for a file should contain the
5b467bf4
SM
167full ChangeLog paragraph mentioning the change to the file, even though
168it may mention other files, because that gives you the full context you
5ab405e4 169need to understand the change. This is the behavior you get when this
5b467bf4
SM
170variable is set to t.
171
14188021 172On the other hand, you could argue that the log entry for a change
5b467bf4 173should contain only the text for the changes which occurred in that
5ab405e4 174file, because the log is per-file. This is the behavior you get
5b467bf4
SM
175when this variable is set to nil.")
176
177;;;; Internal global or buffer-local vars
178
179(defconst log-edit-files-buf "*log-edit-files*")
180(defvar log-edit-initial-files nil)
181(defvar log-edit-callback nil)
93a142e1 182(defvar log-edit-diff-function nil)
5b467bf4 183(defvar log-edit-listfun nil)
cdf54749 184(defvar log-edit-parent-buffer nil)
5b467bf4 185
9fe89a26 186;;; Originally taken from VC-Log mode
12dd83de 187
9fe89a26 188(defconst log-edit-maximum-comment-ring-size 32
12dd83de 189 "Maximum number of saved comments in the comment ring.")
9fe89a26
SM
190(defvar log-edit-comment-ring (make-ring log-edit-maximum-comment-ring-size))
191(defvar log-edit-comment-ring-index nil)
192(defvar log-edit-last-comment-match "")
12dd83de 193
9fe89a26 194(defun log-edit-new-comment-index (stride len)
12dd83de 195 "Return the comment index STRIDE elements from the current one.
9fe89a26 196LEN is the length of `log-edit-comment-ring'."
12dd83de 197 (mod (cond
9fe89a26 198 (log-edit-comment-ring-index (+ log-edit-comment-ring-index stride))
12dd83de
SM
199 ;; Initialize the index on the first use of this command
200 ;; so that the first M-p gets index 0, and the first M-n gets
201 ;; index -1.
202 ((> stride 0) (1- stride))
203 (t stride))
204 len))
205
9fe89a26 206(defun log-edit-previous-comment (arg)
12dd83de
SM
207 "Cycle backwards through comment history.
208With a numeric prefix ARG, go back ARG comments."
209 (interactive "*p")
9fe89a26 210 (let ((len (ring-length log-edit-comment-ring)))
12dd83de
SM
211 (if (<= len 0)
212 (progn (message "Empty comment ring") (ding))
0916e956
SM
213 ;; Don't use `erase-buffer' because we don't want to `widen'.
214 (delete-region (point-min) (point-max))
9fe89a26
SM
215 (setq log-edit-comment-ring-index (log-edit-new-comment-index arg len))
216 (message "Comment %d" (1+ log-edit-comment-ring-index))
217 (insert (ring-ref log-edit-comment-ring log-edit-comment-ring-index)))))
12dd83de 218
9fe89a26 219(defun log-edit-next-comment (arg)
12dd83de
SM
220 "Cycle forwards through comment history.
221With a numeric prefix ARG, go forward ARG comments."
222 (interactive "*p")
9fe89a26 223 (log-edit-previous-comment (- arg)))
12dd83de 224
9fe89a26 225(defun log-edit-comment-search-backward (str &optional stride)
12dd83de
SM
226 "Search backwards through comment history for substring match of STR.
227If the optional argument STRIDE is present, that is a step-width to use
228when going through the comment ring."
229 ;; Why substring rather than regexp ? -sm
230 (interactive
9fe89a26 231 (list (read-string "Comment substring: " nil nil log-edit-last-comment-match)))
12dd83de
SM
232 (unless stride (setq stride 1))
233 (if (string= str "")
9fe89a26
SM
234 (setq str log-edit-last-comment-match)
235 (setq log-edit-last-comment-match str))
12dd83de 236 (let* ((str (regexp-quote str))
9fe89a26
SM
237 (len (ring-length log-edit-comment-ring))
238 (n (log-edit-new-comment-index stride len)))
12dd83de 239 (while (progn (when (or (>= n len) (< n 0)) (error "Not found"))
9fe89a26 240 (not (string-match str (ring-ref log-edit-comment-ring n))))
12dd83de 241 (setq n (+ n stride)))
9fe89a26
SM
242 (setq log-edit-comment-ring-index n)
243 (log-edit-previous-comment 0)))
12dd83de 244
9fe89a26 245(defun log-edit-comment-search-forward (str)
12dd83de
SM
246 "Search forwards through comment history for a substring match of STR."
247 (interactive
9fe89a26
SM
248 (list (read-string "Comment substring: " nil nil log-edit-last-comment-match)))
249 (log-edit-comment-search-backward str -1))
12dd83de 250
9fe89a26 251(defun log-edit-comment-to-change-log (&optional whoami file-name)
12dd83de
SM
252 "Enter last VC comment into the change log for the current file.
253WHOAMI (interactive prefix) non-nil means prompt for user name
254and site. FILE-NAME is the name of the change log; if nil, use
255`change-log-default-name'.
256
9fe89a26 257This may be useful as a `log-edit-checkin-hook' to update change logs
12dd83de
SM
258automatically."
259 (interactive (if current-prefix-arg
260 (list current-prefix-arg
261 (prompt-for-change-log-name))))
12dd83de 262 (let (;; Extract the comment first so we get any error before doing anything.
9fe89a26 263 (comment (ring-ref log-edit-comment-ring 0))
12dd83de
SM
264 ;; Don't let add-change-log-entry insert a defun name.
265 (add-log-current-defun-function 'ignore)
266 end)
267 ;; Call add-log to do half the work.
268 (add-change-log-entry whoami file-name t t)
269 ;; Insert the VC comment, leaving point before it.
270 (setq end (save-excursion (insert comment) (point-marker)))
271 (if (looking-at "\\s *\\s(")
272 ;; It starts with an open-paren, as in "(foo): Frobbed."
273 ;; So remove the ": " add-log inserted.
274 (delete-char -2))
275 ;; Canonicalize the white space between the file name and comment.
276 (just-one-space)
277 ;; Indent rest of the text the same way add-log indented the first line.
278 (let ((indentation (current-indentation)))
279 (save-excursion
280 (while (< (point) end)
281 (forward-line 1)
282 (indent-to indentation))
283 (setq end (point))))
284 ;; Fill the inserted text, preserving open-parens at bol.
0916e956 285 (let ((paragraph-start (concat paragraph-start "\\|\\s *\\s(")))
12dd83de
SM
286 (beginning-of-line)
287 (fill-region (point) end))
288 ;; Canonicalize the white space at the end of the entry so it is
289 ;; separated from the next entry by a single blank line.
290 (skip-syntax-forward " " end)
291 (delete-char (- (skip-syntax-backward " ")))
292 (or (eobp) (looking-at "\n\n")
293 (insert "\n"))))
294
9fe89a26 295;; Compatibility with old names.
f7eeab0d
SM
296(define-obsolete-variable-alias 'vc-comment-ring 'log-edit-comment-ring "22.1")
297(define-obsolete-variable-alias 'vc-comment-ring-index 'log-edit-comment-ring-index "22.1")
298(define-obsolete-function-alias 'vc-previous-comment 'log-edit-previous-comment "22.1")
299(define-obsolete-function-alias 'vc-next-comment 'log-edit-next-comment "22.1")
300(define-obsolete-function-alias 'vc-comment-search-reverse 'log-edit-comment-search-backward "22.1")
301(define-obsolete-function-alias 'vc-comment-search-forward 'log-edit-comment-search-forward "22.1")
302(define-obsolete-function-alias 'vc-comment-to-change-log 'log-edit-comment-to-change-log "22.1")
9fe89a26 303
cdf54749
SM
304;;;
305;;; Actual code
306;;;
5b467bf4 307
63fbe552 308(defvar log-edit-font-lock-keywords
165a7fbe
SM
309 '(("\\`\\(Summary:\\)\\(.*\\)"
310 (1 font-lock-keyword-face)
311 (2 font-lock-function-name-face))))
312
5b467bf4 313;;;###autoload
93a142e1 314(defun log-edit (callback &optional setup params buffer &rest ignore)
5b467bf4 315 "Setup a buffer to enter a log message.
f077c462 316\\<log-edit-mode-map>The buffer will be put in `log-edit-mode'.
5b467bf4 317If SETUP is non-nil, the buffer is then erased and `log-edit-hook' is run.
2507310c
TTN
318Mark and point will be set around the entire contents of the buffer so
319that it is easy to kill the contents of the buffer with \\[kill-region].
5b467bf4 320Once you're done editing the message, pressing \\[log-edit-done] will call
cdf54749 321`log-edit-done' which will end up calling CALLBACK to do the actual commit.
2507310c
TTN
322
323PARAMS if non-nil is an alist. Possible keys and associated values:
324 `log-edit-listfun' -- function taking no arguments that returns the list of
325 files that are concerned by the current operation (using relative names);
326 `log-edit-diff-function' -- function taking no arguments that
327 displays a diff of the files concerned by the current operation.
328
cdf54749 329If BUFFER is non-nil `log-edit' will jump to that buffer, use it to edit the
2507310c
TTN
330log message and go back to the current buffer when done. Otherwise, it
331uses the current buffer."
cdf54749
SM
332 (let ((parent (current-buffer)))
333 (if buffer (pop-to-buffer buffer))
334 (when (and log-edit-setup-invert (not (eq setup 'force)))
335 (setq setup (not setup)))
336 (when setup (erase-buffer))
337 (log-edit-mode)
338 (set (make-local-variable 'log-edit-callback) callback)
93a142e1
DN
339 (if (listp params)
340 (dolist (crt params)
341 (set (make-local-variable (car crt)) (cdr crt)))
342 ;; For backward compatibility with log-edit up to version 22.2
343 ;; accept non-list PARAMS to mean `log-edit-list'.
344 (set (make-local-variable 'log-edit-listfun) params))
345
cdf54749 346 (if buffer (set (make-local-variable 'log-edit-parent-buffer) parent))
70c2a484 347 (set (make-local-variable 'log-edit-initial-files) (log-edit-files))
cdf54749
SM
348 (when setup (run-hooks 'log-edit-hook))
349 (goto-char (point-min)) (push-mark (point-max))
8a26c165 350 (message "%s" (substitute-command-keys
cdf54749 351 "Press \\[log-edit-done] when you are done editing."))))
5b467bf4
SM
352
353(define-derived-mode log-edit-mode text-mode "Log-Edit"
54877f36
SM
354 "Major mode for editing version-control log messages.
355When done editing the log entry, just type \\[log-edit-done] which
356will trigger the actual commit of the file(s).
357Several other handy support commands are provided of course and
358the package from which this is used might also provide additional
359commands (under C-x v for VC, for example).
360
1be77002 361\\{log-edit-mode-map}"
165a7fbe
SM
362 (set (make-local-variable 'font-lock-defaults)
363 '(log-edit-font-lock-keywords t))
9fe89a26 364 (make-local-variable 'log-edit-comment-ring-index))
5b467bf4
SM
365
366(defun log-edit-hide-buf (&optional buf where)
367 (when (setq buf (get-buffer (or buf log-edit-files-buf)))
368 (let ((win (get-buffer-window buf where)))
369 (if win (ignore-errors (delete-window win))))
370 (bury-buffer buf)))
371
372(defun log-edit-done ()
373 "Finish editing the log message and commit the files.
5b467bf4
SM
374If you want to abort the commit, simply delete the buffer."
375 (interactive)
ffe7dc64
SM
376 ;; Get rid of trailing empty lines
377 (goto-char (point-max))
378 (skip-syntax-backward " ")
379 (when (equal (char-after) ?\n) (forward-char 1))
380 (delete-region (point) (point-max))
381 ;; Check for final newline
cdf54749
SM
382 (if (and (> (point-max) (point-min))
383 (/= (char-before (point-max)) ?\n)
5b467bf4
SM
384 (or (eq log-edit-require-final-newline t)
385 (and log-edit-require-final-newline
386 (y-or-n-p
387 (format "Buffer %s does not end in newline. Add one? "
388 (buffer-name))))))
389 (save-excursion
390 (goto-char (point-max))
391 (insert ?\n)))
1be77002 392 (let ((comment (buffer-string)))
9fe89a26
SM
393 (when (or (ring-empty-p log-edit-comment-ring)
394 (not (equal comment (ring-ref log-edit-comment-ring 0))))
395 (ring-insert log-edit-comment-ring comment)))
5b467bf4
SM
396 (let ((win (get-buffer-window log-edit-files-buf)))
397 (if (and log-edit-confirm
398 (not (and (eq log-edit-confirm 'changed)
399 (equal (log-edit-files) log-edit-initial-files)))
400 (progn
401 (log-edit-show-files)
ce5a3ac0 402 (not (y-or-n-p "Really commit? "))))
5b467bf4
SM
403 (progn (when (not win) (log-edit-hide-buf))
404 (message "Oh, well! Later maybe?"))
405 (run-hooks 'log-edit-done-hook)
406 (log-edit-hide-buf)
cdf54749
SM
407 (unless (or log-edit-keep-buffer (not log-edit-parent-buffer))
408 (cvs-bury-buffer (current-buffer) log-edit-parent-buffer))
5b467bf4
SM
409 (call-interactively log-edit-callback))))
410
411(defun log-edit-files ()
412 "Return the list of files that are about to be committed."
413 (ignore-errors (funcall log-edit-listfun)))
414
5b467bf4
SM
415(defun log-edit-mode-help ()
416 "Provide help for the `log-edit-mode-map'."
417 (interactive)
418 (if (eq last-command 'log-edit-mode-help)
419 (describe-function major-mode)
8a26c165 420 (message "%s"
5b467bf4
SM
421 (substitute-command-keys
422 "Type `\\[log-edit-done]' to finish commit. Try `\\[describe-function] log-edit-done' for more help."))))
423
43a9a0c4
SM
424(defcustom log-edit-common-indent 0
425 "Minimum indentation to use in `log-edit-set-common-indentation'."
426 :group 'log-edit
427 :type 'integer)
428
429(defun log-edit-set-common-indentation ()
430 "(Un)Indent the current buffer rigidly to `log-edit-common-indent'."
5b467bf4
SM
431 (save-excursion
432 (let ((common (point-max)))
433 (goto-char (point-min))
434 (while (< (point) (point-max))
435 (if (not (looking-at "^[ \t]*$"))
436 (setq common (min common (current-indentation))))
437 (forward-line 1))
43a9a0c4
SM
438 (indent-rigidly (point-min) (point-max)
439 (- log-edit-common-indent common)))))
5b467bf4 440
93a142e1
DN
441(defun log-edit-show-diff ()
442 "Show the diff for the files to be committed."
443 (interactive)
37b72bf5
DN
444 (if (functionp log-edit-diff-function)
445 (funcall log-edit-diff-function)
446 (error "Diff functionality has not been setup")))
93a142e1 447
5b467bf4
SM
448(defun log-edit-show-files ()
449 "Show the list of files to be committed."
450 (interactive)
451 (let* ((files (log-edit-files))
cdf54749 452 (buf (get-buffer-create log-edit-files-buf)))
5b467bf4
SM
453 (with-current-buffer buf
454 (log-edit-hide-buf buf 'all)
455 (setq buffer-read-only nil)
456 (erase-buffer)
a88e99b5 457 (cvs-insert-strings files)
5b467bf4
SM
458 (setq buffer-read-only t)
459 (goto-char (point-min))
460 (save-selected-window
461 (cvs-pop-to-buffer-same-frame buf)
462 (shrink-window-if-larger-than-buffer)
463 (selected-window)))))
464
465(defun log-edit-insert-cvs-template ()
f7eeab0d
SM
466 "Insert the template specified by the CVS administrator, if any.
467This simply uses the local CVS/Template file."
5b467bf4 468 (interactive)
f7eeab0d
SM
469 (when (or (interactive-p) (= (point-min) (point-max)))
470 (when (file-readable-p "CVS/Template")
471 (insert-file-contents "CVS/Template"))))
472
473(defun log-edit-insert-cvs-rcstemplate ()
474 "Insert the rcstemplate from the CVS repository.
475This contacts the repository to get the rcstemplate file and
476can thus take some time."
477 (interactive)
478 (when (or (interactive-p) (= (point-min) (point-max)))
21227135
SM
479 (when (file-readable-p "CVS/Root")
480 ;; Ignore the stderr stuff, even if it's an error.
481 (call-process "cvs" nil '(t nil) nil
482 "checkout" "-p" "CVSROOT/rcstemplate"))))
f1180544 483
f7eeab0d
SM
484(defun log-edit-insert-filenames ()
485 "Insert the list of files that are to be committed."
486 (interactive)
487 (insert "Affected files: \n"
488 (mapconcat 'identity (log-edit-files) " \n")))
5b467bf4
SM
489
490(defun log-edit-add-to-changelog ()
491 "Insert this log message into the appropriate ChangeLog file."
492 (interactive)
493 ;; Yuck!
9fe89a26
SM
494 (unless (string= (buffer-string) (ring-ref log-edit-comment-ring 0))
495 (ring-insert log-edit-comment-ring (buffer-string)))
5b467bf4
SM
496 (dolist (f (log-edit-files))
497 (let ((buffer-file-name (expand-file-name f)))
498 (save-excursion
9fe89a26 499 (log-edit-comment-to-change-log)))))
5b467bf4 500
f7eeab0d
SM
501(defvar log-edit-changelog-use-first nil)
502(defun log-edit-insert-changelog (&optional use-first)
503 "Insert a log message by looking at the ChangeLog.
504The idea is to write your ChangeLog entries first, and then use this
505command to commit your changes.
506
507To select default log text, we:
508- find the ChangeLog entries for the files to be checked in,
509- verify that the top entry in the ChangeLog is on the current date
510 and by the current user; if not, we don't provide any default text,
511- search the ChangeLog entry for paragraphs containing the names of
512 the files we're checking in, and finally
513- use those paragraphs as the log text.
514
515If the optional prefix arg USE-FIRST is given (via \\[universal-argument]),
516or if the command is repeated a second time in a row, use the first log entry
517regardless of user name or time."
518 (interactive "P")
519 (let ((log-edit-changelog-use-first
520 (or use-first (eq last-command 'log-edit-insert-changelog))))
521 (log-edit-insert-changelog-entries (log-edit-files)))
522 (log-edit-set-common-indentation)
523 (goto-char (point-min))
524 (when (looking-at "\\*\\s-+")
525 (forward-line 1)
526 (when (not (re-search-forward "^\\*\\s-+" nil t))
527 (goto-char (point-min))
528 (skip-chars-forward "^():")
529 (skip-chars-forward ": ")
530 (delete-region (point-min) (point)))))
531
f1180544 532;;;;
5b467bf4
SM
533;;;; functions for getting commit message from ChangeLog a file...
534;;;; Courtesy Jim Blandy
f1180544 535;;;;
5b467bf4 536
14188021 537(defun log-edit-narrow-changelog ()
5b467bf4
SM
538 "Narrow to the top page of the current buffer, a ChangeLog file.
539Actually, the narrowed region doesn't include the date line.
540A \"page\" in a ChangeLog file is the area between two dates."
541 (or (eq major-mode 'change-log-mode)
14188021 542 (error "log-edit-narrow-changelog: current buffer isn't a ChangeLog"))
5b467bf4
SM
543
544 (goto-char (point-min))
545
546 ;; Skip date line and subsequent blank lines.
547 (forward-line 1)
548 (if (looking-at "[ \t\n]*\n")
549 (goto-char (match-end 0)))
550
551 (let ((start (point)))
552 (forward-page 1)
553 (narrow-to-region start (point))
554 (goto-char (point-min))))
555
14188021 556(defun log-edit-changelog-paragraph ()
5b467bf4
SM
557 "Return the bounds of the ChangeLog paragraph containing point.
558If we are between paragraphs, return the previous paragraph."
559 (save-excursion
560 (beginning-of-line)
561 (if (looking-at "^[ \t]*$")
562 (skip-chars-backward " \t\n" (point-min)))
563 (list (progn
564 (if (re-search-backward "^[ \t]*\n" nil 'or-to-limit)
565 (goto-char (match-end 0)))
566 (point))
567 (if (re-search-forward "^[ \t\n]*$" nil t)
568 (match-beginning 0)
3d200243 569 (point-max)))))
5b467bf4 570
14188021 571(defun log-edit-changelog-subparagraph ()
5b467bf4
SM
572 "Return the bounds of the ChangeLog subparagraph containing point.
573A subparagraph is a block of non-blank lines beginning with an asterisk.
574If we are between sub-paragraphs, return the previous subparagraph."
575 (save-excursion
576 (end-of-line)
577 (if (search-backward "*" nil t)
578 (list (progn (beginning-of-line) (point))
bc35d341 579 (progn
5b467bf4
SM
580 (forward-line 1)
581 (if (re-search-forward "^[ \t]*[\n*]" nil t)
582 (match-beginning 0)
583 (point-max))))
584 (list (point) (point)))))
585
14188021 586(defun log-edit-changelog-entry ()
5b467bf4 587 "Return the bounds of the ChangeLog entry containing point.
14188021 588The variable `log-edit-changelog-full-paragraphs' decides whether an
5b467bf4
SM
589\"entry\" is a paragraph or a subparagraph; see its documentation string
590for more details."
14188021
SM
591 (if log-edit-changelog-full-paragraphs
592 (log-edit-changelog-paragraph)
593 (log-edit-changelog-subparagraph)))
5b467bf4
SM
594
595(defvar user-full-name)
596(defvar user-mail-address)
14188021 597(defun log-edit-changelog-ours-p ()
5b467bf4 598 "See if ChangeLog entry at point is for the current user, today.
4837b516 599Return non-nil if it is."
5b467bf4
SM
600 ;; Code adapted from add-change-log-entry.
601 (let ((name (or (and (boundp 'add-log-full-name) add-log-full-name)
602 (and (fboundp 'user-full-name) (user-full-name))
603 (and (boundp 'user-full-name) user-full-name)))
604 (mail (or (and (boundp 'add-log-mailing-address) add-log-mailing-address)
605 ;;(and (fboundp 'user-mail-address) (user-mail-address))
606 (and (boundp 'user-mail-address) user-mail-address)))
607 (time (or (and (boundp 'add-log-time-format)
608 (functionp add-log-time-format)
609 (funcall add-log-time-format))
610 (format-time-string "%Y-%m-%d"))))
f7eeab0d
SM
611 (looking-at (if log-edit-changelog-use-first
612 "[^ \t]"
613 (regexp-quote (format "%s %s <%s>" time name mail))))))
5b467bf4 614
14188021 615(defun log-edit-changelog-entries (file)
5b467bf4
SM
616 "Return the ChangeLog entries for FILE, and the ChangeLog they came from.
617The return value looks like this:
618 (LOGBUFFER (ENTRYSTART . ENTRYEND) ...)
619where LOGBUFFER is the name of the ChangeLog buffer, and each
620\(ENTRYSTART . ENTRYEND\) pair is a buffer region."
d944ee49
SM
621 (let ((changelog-file-name
622 (let ((default-directory
623 (file-name-directory (expand-file-name file)))
624 (visiting-buffer (find-buffer-visiting file)))
625 ;; If there is a buffer visiting FILE, and it has a local
626 ;; value for `change-log-default-name', use that.
627 (if (and visiting-buffer
628 (local-variable-p 'change-log-default-name
629 visiting-buffer))
630 (with-current-buffer visiting-buffer
631 change-log-default-name)
632 ;; `find-change-log' uses `change-log-default-name' if set
633 ;; and sets it before exiting, so we need to work around
634 ;; that memoizing which is undesired here
635 (setq change-log-default-name nil)
636 (find-change-log)))))
637 (with-current-buffer (find-file-noselect changelog-file-name)
5b467bf4
SM
638 (unless (eq major-mode 'change-log-mode) (change-log-mode))
639 (goto-char (point-min))
640 (if (looking-at "\\s-*\n") (goto-char (match-end 0)))
14188021 641 (if (not (log-edit-changelog-ours-p))
5b467bf4
SM
642 (list (current-buffer))
643 (save-restriction
14188021 644 (log-edit-narrow-changelog)
5b467bf4 645 (goto-char (point-min))
f1180544 646
5b467bf4
SM
647 ;; Search for the name of FILE relative to the ChangeLog. If that
648 ;; doesn't occur anywhere, they're not using full relative
649 ;; filenames in the ChangeLog, so just look for FILE; we'll accept
650 ;; some false positives.
651 (let ((pattern (file-relative-name
652 file (file-name-directory changelog-file-name))))
653 (if (or (string= pattern "")
654 (not (save-excursion
655 (search-forward pattern nil t))))
656 (setq pattern (file-name-nondirectory file)))
657
b543ff57
NR
658 (setq pattern (concat "\\(^\\|[^[:alnum:]]\\)"
659 pattern
660 "\\($\\|[^[:alnum:]]\\)"))
661
5b467bf4 662 (let (texts)
b543ff57 663 (while (re-search-forward pattern nil t)
14188021 664 (let ((entry (log-edit-changelog-entry)))
5b467bf4
SM
665 (push entry texts)
666 (goto-char (elt entry 1))))
667
668 (cons (current-buffer) texts))))))))
669
14188021 670(defun log-edit-changelog-insert-entries (buffer regions)
5b467bf4
SM
671 "Insert those regions in BUFFER specified in REGIONS.
672Sort REGIONS front-to-back first."
673 (let ((regions (sort regions 'car-less-than-car))
674 (last))
675 (dolist (region regions)
676 (when (and last (< last (car region))) (newline))
677 (setq last (elt region 1))
678 (apply 'insert-buffer-substring buffer region))))
679
14188021 680(defun log-edit-insert-changelog-entries (files)
5b467bf4
SM
681 "Given a list of files FILES, insert the ChangeLog entries for them."
682 (let ((buffer-entries nil))
683
684 ;; Add each buffer to buffer-entries, and associate it with the list
685 ;; of entries we want from that file.
686 (dolist (file files)
14188021 687 (let* ((entries (log-edit-changelog-entries file))
5b467bf4
SM
688 (pair (assq (car entries) buffer-entries)))
689 (if pair
690 (setcdr pair (cvs-union (cdr pair) (cdr entries)))
691 (push entries buffer-entries))))
692
693 ;; Now map over each buffer in buffer-entries, sort the entries for
694 ;; each buffer, and extract them as strings.
695 (dolist (buffer-entry buffer-entries)
14188021 696 (log-edit-changelog-insert-entries (car buffer-entry) (cdr buffer-entry))
5b467bf4
SM
697 (when (cdr buffer-entry) (newline)))))
698
699(provide 'log-edit)
54877f36 700
b543ff57 701;; arch-tag: 8089b39c-983b-4e83-93cd-ed0a64c7fdcc
5b467bf4 702;;; log-edit.el ends here