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