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