aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2000-03-11 03:51:31 +0000
committerStefan Monnier2000-03-11 03:51:31 +0000
commit5b467bf4e2787e3290280cadbae9e915df88dacd (patch)
tree83e838669d3052e213f8f518602bae5ec0cf0a15
parentafa18a4e5d28a418fa9374c96be75a8e20f5fe08 (diff)
downloademacs-5b467bf4e2787e3290280cadbae9e915df88dacd.tar.gz
emacs-5b467bf4e2787e3290280cadbae9e915df88dacd.zip
*** empty log message ***
-rw-r--r--lisp/ChangeLog13
-rw-r--r--lisp/cvs-status.el523
-rw-r--r--lisp/emacs-lisp/ewoc.el620
-rw-r--r--lisp/log-edit.el448
-rw-r--r--lisp/log-view.el189
-rw-r--r--lisp/pcvs-defs.el501
-rw-r--r--lisp/pcvs-info.el455
-rw-r--r--lisp/pcvs-parse.el478
-rw-r--r--lisp/pcvs-util.el381
-rw-r--r--lisp/pcvs.el2122
10 files changed, 5730 insertions, 0 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index faa11a66aea..db0cc19d533 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,16 @@
12000-03-10 Stefan Monnier <monnier@cs.yale.edu>
2
3 * cvs-status, log-edit.el, log-view.el, pcvs-defs.el, pcvs-info.el,
4 pcvs-parse.el, pcvs-util.el, pcvs.el: New files.
5
6 * emacs-lisp/ewoc.el: New file. This is a merge of elib-node.el, dll.el
7 and cookie.el (from Elib) with heavy renaming and other massaging.
8
9 * emacs-lisp/easy-mmode.el (easy-mmode-defmap, easy-mmode-defsyntax):
10 Autoload the functions used.
11 (easy-mmode-define-syntax): Fix CL typo.
12 (easy-mmode-define-derived-mode): Improve the docstring generation.
13
12000-03-10 Gerd Moellmann <gerd@gnu.org> 142000-03-10 Gerd Moellmann <gerd@gnu.org>
2 15
3 * textmodes/texinfo.el (texinfo-version): Variable and function 16 * textmodes/texinfo.el (texinfo-version): Variable and function
diff --git a/lisp/cvs-status.el b/lisp/cvs-status.el
new file mode 100644
index 00000000000..33a6f6a1cfa
--- /dev/null
+++ b/lisp/cvs-status.el
@@ -0,0 +1,523 @@
1;;; cvs-status.el --- Major mode for browsing `cvs status' output
2
3;; Copyright (C) 1999-2000 Free Software Foundation, Inc.
4
5;; Author: Stefan Monnier <monnier@cs.yale.edu>
6;; Keywords: pcl-cvs cvs status tree
7;; Version: $Name: $
8;; Revision: $Id: cvs-status.el,v 1.14 2000/03/05 21:32:21 monnier Exp $
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
31;; - Rename to cvs-status-mode.el
32;; - Somehow allow cvs-status-tree to work on-the-fly
33
34;;; Code:
35
36(eval-when-compile (require 'cl))
37(require 'pcvs-util)
38
39;;;
40
41(defgroup cvs-status nil
42 "Major mode for browsing `cvs status' output."
43 :group 'pcl-cvs
44 :prefix "cvs-status-")
45
46(easy-mmode-defmap cvs-status-mode-map
47 '(("n" . next-line)
48 ("N" . cvs-status-next-entry)
49 ("\M-n" . cvs-status-next-entry)
50 ("p" . previous-line)
51 ("P" . cvs-status-prev-entry)
52 ("\M-p" . cvs-status-prev-entry)
53 ("t" . cvs-status-cvstrees)
54 ("T" . cvs-status-trees))
55 "CVS-Status' keymap."
56 :group 'cvs-status
57 :inherit 'cvs-mode-map)
58
59;;(easy-menu-define cvs-status-menu cvs-status-mode-map
60;; "Menu for `cvs-status-mode'."
61;; '("CVS-Status"
62;; ["Show Tag Trees" cvs-status-tree t]
63;; ))
64
65(defvar cvs-status-mode-hook nil
66 "Hook run at the end of `cvs-status-mode'.")
67
68(defconst cvs-status-tags-leader-re "^ Existing Tags:$")
69(defconst cvs-status-entry-leader-re "^File: \\(\\S-+\\)\\s-+Status: \\(.+\\)$")
70(defconst cvs-status-dir-re "^cvs[.ex]* [a-z]+: Examining \\(.+\\)$")
71(defconst cvs-status-rev-re "[0-9][.0-9]*\\.[.0-9]*[0-9]")
72(defconst cvs-status-tag-re "[ \t]\\([a-zA-Z][^ \t\n.]*\\)")
73
74(defconst cvs-status-font-lock-keywords
75 `((,cvs-status-entry-leader-re
76 (1 'cvs-filename-face)
77 (2 'cvs-need-action-face))
78 (,cvs-status-tags-leader-re
79 (,cvs-status-rev-re
80 (save-excursion (re-search-forward "^\n" nil 'move) (point))
81 (progn (re-search-backward cvs-status-tags-leader-re nil t)
82 (forward-line 1))
83 (0 font-lock-comment-face))
84 (,cvs-status-tag-re
85 (save-excursion (re-search-forward "^\n" nil 'move) (point))
86 (progn (re-search-backward cvs-status-tags-leader-re nil t)
87 (forward-line 1))
88 (1 font-lock-function-name-face)))))
89(defconst cvs-status-font-lock-defaults
90 '(cvs-status-font-lock-keywords t nil nil nil))
91
92
93(put 'cvs-status-mode 'mode-class 'special)
94;;;###autoload
95(autoload 'cvs-status-mode "cvs-status" "Mode used for cvs status output." t)
96(eval-when-compile (autoload 'easy-mmode-define-derived-mode "easy-mmode"))
97(easy-mmode-define-derived-mode cvs-status-mode fundamental-mode "CVS-Status"
98 "Mode used for cvs status output."
99 (set (make-local-variable 'font-lock-defaults) cvs-status-font-lock-defaults)
100 (set (make-local-variable 'cvs-minor-wrap-function) 'cvs-status-minor-wrap))
101
102
103(defun cvs-status-next-entry (n)
104 "Go to the N'th next cvs status entry."
105 (interactive "p")
106 (if (< n 0) (cvs-status-prev-entry (- n))
107 (forward-line 1)
108 (re-search-forward cvs-status-entry-leader-re nil t n)
109 (beginning-of-line)))
110
111(defun cvs-status-prev-entry (n)
112 "Go to the N'th previous cvs status entry."
113 (interactive "p")
114 (if (< n 0) (cvs-status-next-entry (- n))
115 (forward-line -1)
116 (re-search-backward cvs-status-entry-leader-re nil t n)
117 (beginning-of-line)))
118
119(defun cvs-status-current-file ()
120 (save-excursion
121 (forward-line 1)
122 (or (re-search-backward cvs-status-entry-leader-re nil t)
123 (re-search-forward cvs-status-entry-leader-re))
124 (let* ((file (match-string 1))
125 (cvsdir (and (re-search-backward cvs-status-dir-re nil t)
126 (match-string 1)))
127 (pcldir (and (re-search-backward cvs-pcl-cvs-dirchange-re nil t)
128 (match-string 1)))
129 (dir ""))
130 (let ((default-directory ""))
131 (when pcldir (setq dir (expand-file-name pcldir dir)))
132 (when cvsdir (setq dir (expand-file-name cvsdir dir)))
133 (expand-file-name file dir)))))
134
135(defun cvs-status-current-tag ()
136 (save-excursion
137 (let ((pt (point))
138 (col (current-column))
139 (start (progn (re-search-backward cvs-status-tags-leader-re nil t) (point)))
140 (end (progn (re-search-forward "^$" nil t) (point))))
141 (when (and (< start pt) (> end pt))
142 (goto-char pt)
143 (end-of-line)
144 (let ((tag nil) (dist pt) (end (point)))
145 (beginning-of-line)
146 (while (re-search-forward cvs-status-tag-re end t)
147 (let* ((cole (current-column))
148 (colb (save-excursion
149 (goto-char (match-beginning 1)) (current-column)))
150 (ndist (min (abs (- cole col)) (abs (- colb col)))))
151 (when (< ndist dist)
152 (setq dist ndist)
153 (setq tag (match-string 1)))))
154 tag)))))
155
156(defun cvs-status-minor-wrap (buf f)
157 (let ((data (with-current-buffer buf
158 (cons
159 (cons (cvs-status-current-file)
160 (cvs-status-current-tag))
161 (when (ignore-errors (mark))
162 ;; `mark-active' is not provided by XEmacs :-(
163 (save-excursion
164 (goto-char (mark))
165 (cons (cvs-status-current-file)
166 (cvs-status-current-tag))))))))
167 (let ((cvs-branch-prefix (cdar data))
168 (cvs-secondary-branch-prefix (and (cdar data) (cddr data)))
169 (cvs-minor-current-files
170 (cons (caar data)
171 (when (and (cadr data) (not (equal (caar data) (cadr data))))
172 (list (cadr data)))))
173 ;; FIXME: I need to force because the fileinfos are UNKNOWN
174 (cvs-force-command "/F"))
175 (funcall f))))
176
177;;
178;; Tagelt, tag element
179;;
180
181(defstruct (cvs-tag
182 (:constructor nil)
183 (:constructor cvs-tag-make
184 (vlist &optional name type))
185 (:conc-name cvs-tag->))
186 vlist
187 name
188 type)
189
190(defsubst cvs-status-vl-to-str (vl) (mapconcat 'number-to-string vl "."))
191
192(defun cvs-tag->string (tag)
193 (if (stringp tag) tag
194 (let ((name (cvs-tag->name tag))
195 (vl (cvs-tag->vlist tag)))
196 (if (null name) (cvs-status-vl-to-str vl)
197 (let ((rev (if vl (concat " (" (cvs-status-vl-to-str vl) ")") "")))
198 (if (consp name) (mapcar (lambda (name) (concat name rev)) name)
199 (concat name rev)))))))
200
201(defun cvs-tag-compare-1 (vl1 vl2)
202 (cond
203 ((and (null vl1) (null vl2)) 'equal)
204 ((null vl1) 'more2)
205 ((null vl2) 'more1)
206 (t (let ((v1 (car vl1))
207 (v2 (car vl2)))
208 (cond
209 ((> v1 v2) 'more1)
210 ((< v1 v2) 'more2)
211 (t (cvs-tag-compare-1 (cdr vl1) (cdr vl2))))))))
212
213(defsubst cvs-tag-compare (tag1 tag2)
214 (cvs-tag-compare-1 (cvs-tag->vlist tag1) (cvs-tag->vlist tag2)))
215
216(defun cvs-tag-merge (tag1 tag2)
217 "Merge TAG1 and TAG2 into one."
218 (let ((type1 (cvs-tag->type tag1))
219 (type2 (cvs-tag->type tag2))
220 (name1 (cvs-tag->name tag1))
221 (name2 (cvs-tag->name tag2)))
222 (unless (equal (cvs-tag->vlist tag1) (cvs-tag->vlist tag2))
223 (setf (cvs-tag->vlist tag1) nil))
224 (if type1
225 (unless (or (not type2) (equal type1 type2))
226 (setf (cvs-tag->type tag1) nil))
227 (setf (cvs-tag->type tag1) type2))
228 (if name1
229 (setf (cvs-tag->name tag1) (cvs-append name1 name2))
230 (setf (cvs-tag->name tag1) name2))
231 tag1))
232
233(defun cvs-tree-print (tags printer column)
234 "Print the tree of TAGS where each tag's string is given by PRINTER.
235PRINTER should accept both a tag (in which case it should return a string)
236or a string (in which case it should simply return its argument).
237A tag cannot be a CONS. The return value can also be a list of strings,
238if several nodes where merged into one.
239The tree will be printed no closer than column COLUMN."
240
241 (let* ((eol (save-excursion (end-of-line) (current-column)))
242 (column (max (+ eol 2) column)))
243 (if (null tags) column
244 ;;(move-to-column-force column)
245 (let* ((rev (cvs-car tags))
246 (name (funcall printer (cvs-car rev)))
247 (rest (append (cvs-cdr name) (cvs-cdr tags)))
248 (prefix
249 (save-excursion
250 (or (= (forward-line 1) 0) (insert "\n"))
251 (cvs-tree-print rest printer column))))
252 (assert (>= prefix column))
253 (move-to-column prefix t)
254 (assert (eolp))
255 (insert (cvs-car name))
256 (dolist (br (cvs-cdr rev))
257 (let* ((column (current-column))
258 (brrev (funcall printer (cvs-car br)))
259 (brlength (length (cvs-car brrev)))
260 (brfill (concat (make-string (/ brlength 2) ? ) "|"))
261 (prefix
262 (save-excursion
263 (insert " -- ")
264 (cvs-tree-print (cvs-append brrev brfill (cvs-cdr br))
265 printer (current-column)))))
266 (delete-region (save-excursion (move-to-column prefix) (point))
267 (point))
268 (insert " " (make-string (- prefix column 2) ?-) " ")
269 (end-of-line)))
270 prefix))))
271
272(defun cvs-tree-merge (tree1 tree2)
273 "Merge tags trees TREE1 and TREE2 into one.
274BEWARE: because of stability issues, this is not a symetric operation."
275 (assert (and (listp tree1) (listp tree2)))
276 (cond
277 ((null tree1) tree2)
278 ((null tree2) tree1)
279 (t
280 (let* ((rev1 (car tree1))
281 (tag1 (cvs-car rev1))
282 (vl1 (cvs-tag->vlist tag1))
283 (l1 (length vl1))
284 (rev2 (car tree2))
285 (tag2 (cvs-car rev2))
286 (vl2 (cvs-tag->vlist tag2))
287 (l2 (length vl2)))
288 (cond
289 ((= l1 l2)
290 (case (cvs-tag-compare tag1 tag2)
291 (more1 (list* rev2 (cvs-tree-merge tree1 (cdr tree2))))
292 (more2 (list* rev1 (cvs-tree-merge (cdr tree1) tree2)))
293 (equal
294 (cons (cons (cvs-tag-merge tag1 tag2)
295 (cvs-tree-merge (cvs-cdr rev1) (cvs-cdr rev2)))
296 (cvs-tree-merge (cdr tree1) (cdr tree2))))))
297 ((> l1 l2)
298 (cvs-tree-merge (list (cons (cvs-tag-make (butlast vl1)) tree1)) tree2))
299 ((< l1 l2)
300 (cvs-tree-merge tree1 (list (cons (cvs-tag-make (butlast vl2)) tree2)))))))))
301
302(defun cvs-tag-make-tag (tag)
303 (let ((vl (mapcar 'string-to-number (split-string (third tag) "\\."))))
304 (cvs-tag-make vl (first tag) (intern (second tag)))))
305
306(defun cvs-tags->tree (tags)
307 "Make a tree out of a list of TAGS."
308 (let ((tags
309 (mapcar (lambda (tag)
310 (let ((tag (cvs-tag-make-tag tag)))
311 (list (if (not (eq (cvs-tag->type tag) 'branch)) tag
312 (list (cvs-tag-make (butlast (cvs-tag->vlist tag)))
313 tag)))))
314 tags)))
315 (while (cdr tags)
316 (let (tl)
317 (while tags
318 (push (cvs-tree-merge (pop tags) (pop tags)) tl))
319 (setq tags (nreverse tl))))
320 (car tags)))
321
322(defun cvs-status-get-tags ()
323 "Look for a list of tags, read them in and delete them.
324Returns NIL if there was an empty list of tags and T if there wasn't
325even a list. Else, return the list of tags where each element of
326the list is a three-string list TAG, KIND, REV."
327 (let ((tags nil))
328 (if (not (re-search-forward cvs-status-tags-leader-re nil t)) t
329 (forward-char 1)
330 (let ((pt (point))
331 (lastrev nil)
332 (case-fold-search t))
333 (or
334 (looking-at "\\s-+no\\s-+tags")
335
336 (progn ; normal listing
337 (while (looking-at "^[ \t]+\\([^ \t\n]+\\)[ \t]+(\\([a-z]+\\): \\(.+\\))$")
338 (push (list (match-string 1) (match-string 2) (match-string 3)) tags)
339 (forward-line 1))
340 (unless (looking-at "^$") (setq tags nil) (goto-char pt))
341 tags)
342
343 (progn ; cvstree-style listing
344 (while (or (looking-at "^ .+\\(.\\) \\([0-9.]+\\): \\([^\n\t .0-9][^\n\t ]*\\)?$")
345 (and lastrev
346 (looking-at "^ .+\\(\\) \\(8\\)? \\([^\n\t .0-9][^\n\t ]*\\)$")))
347 (setq lastrev (or (match-string 2) lastrev))
348 (push (list (match-string 3)
349 (if (equal (match-string 1) " ") "branch" "revision")
350 lastrev) tags)
351 (forward-line 1))
352 (unless (looking-at "^$") (setq tags nil) (goto-char pt))
353 (setq tags (nreverse tags)))
354
355 (progn ; new tree style listing
356 (let* ((re-lead "[ \t]*\\(-+\\)?\\(|\n?[ \t]+\\)?")
357 (re3 (concat re-lead "\\(\\.\\)?\\(" cvs-status-rev-re "\\)"))
358 (re2 (concat re-lead cvs-status-tag-re "\\(\\)"))
359 (re1 (concat re-lead cvs-status-tag-re
360 " (\\(" cvs-status-rev-re "\\))")))
361 (while (or (looking-at re1) (looking-at re2) (looking-at re3))
362 (push (list (match-string 3)
363 (if (match-string 1) "branch" "revision")
364 (match-string 4)) tags)
365 (goto-char (match-end 0))
366 (when (eolp) (forward-char 1))))
367 (unless (looking-at "^$") (setq tags nil) (goto-char pt))
368 (setq tags (nreverse tags))))
369
370 (delete-region pt (point)))
371 tags)))
372
373(defvar font-lock-mode)
374(defun cvs-refontify (beg end)
375 (when (and (boundp 'font-lock-mode)
376 font-lock-mode
377 (fboundp 'font-lock-fontify-region))
378 (font-lock-fontify-region (1- beg) (1+ end))))
379
380(defun cvs-status-trees ()
381 "Look for a lists of tags, and replace them with trees."
382 (interactive)
383 (save-excursion
384 (goto-char (point-min))
385 (let ((inhibit-read-only t)
386 (tags nil))
387 (while (listp (setq tags (cvs-status-get-tags)))
388 ;;(let ((pt (save-excursion (forward-line -1) (point))))
389 (save-restriction
390 (narrow-to-region (point) (point))
391 ;;(newline)
392 (cvs-tree-print (cvs-tags->tree tags) 'cvs-tag->string 3))
393 ;;(cvs-refontify pt (point))
394 (sit-for 0)
395 ;;)
396 ))))
397
398;;;;
399;;;; CVSTree-style trees
400;;;;
401
402;; chars sets. Ripped from cvstree
403(defvar cvstree-dstr-2byte-ready
404 (when (featurep 'mule)
405 (if (boundp 'current-language-environment)
406 (string= current-language-environment "Japanese")
407 t)) ; mule/emacs-19
408 "*Variable that specifies characters set used in cvstree tree graph.
409If non-nil, 2byte (Japanese?) characters set is used.
410If nil, 1byte characters set is used.
4112byte characters might be available with Mule or Emacs with Mule extension.")
412
413(defconst cvstree-dstr-char-space
414 (if cvstree-dstr-2byte-ready "$B!!(B" " "))
415(defconst cvstree-dstr-char-hbar
416 (if cvstree-dstr-2byte-ready "$B(,(B" "-"))
417(defconst cvstree-dstr-char-vbar
418 (if cvstree-dstr-2byte-ready "$B(-(B" "|"))
419(defconst cvstree-dstr-char-branch
420 (if cvstree-dstr-2byte-ready "$B(2(B" "+"))
421(defconst cvstree-dstr-char-eob ;end of branch
422 (if cvstree-dstr-2byte-ready "$B(1(B" "`"))
423(defconst cvstree-dstr-char-bob ;beginning of branch
424 (if cvstree-dstr-2byte-ready "$B(3(B" "+"))
425
426(defun cvs-tag-lessp (tag1 tag2)
427 (eq (cvs-tag-compare tag1 tag2) 'more2))
428
429(defvar cvs-tree-nomerge t)
430
431(defun cvs-status-cvstrees (&optional arg)
432 "Look for a list of tags, and replace it with a tree.
433Optional prefix ARG chooses between two representations."
434 (interactive "P")
435 (save-excursion
436 (goto-char (point-min))
437 (let ((inhibit-read-only t)
438 (tags nil)
439 (cvs-tree-nomerge (if arg (not cvs-tree-nomerge) cvs-tree-nomerge)))
440 (while (listp (setq tags (cvs-status-get-tags)))
441 (let ((tags (mapcar 'cvs-tag-make-tag tags))
442 ;;(pt (save-excursion (forward-line -1) (point)))
443 )
444 (setq tags (sort tags 'cvs-tag-lessp))
445 (let* ((first (first tags))
446 (prev (if (cvs-tag-p first)
447 (list (first (cvs-tag->vlist first))) nil)))
448 (cvs-tree-tags-insert tags prev)
449 ;;(cvs-refontify pt (point))
450 (sit-for 0)))))))
451
452(defun cvs-tree-tags-insert (tags prev)
453 (when tags
454 (let* ((tag (car tags))
455 (vlist (cvs-tag->vlist tag))
456 (nprev ;"next prev"
457 (let* ((next (cvs-car (cadr tags)))
458 (nprev (if (and cvs-tree-nomerge next
459 (equal vlist (cvs-tag->vlist next)))
460 prev vlist)))
461 (cvs-map (lambda (v p) v) nprev prev)))
462 (after (save-excursion
463 (newline)
464 (cvs-tree-tags-insert (cdr tags) nprev)))
465 (pe t) ;"prev equal"
466 (nas nil)) ;"next afters" to be returned
467 (insert " ")
468 (do* ((vs vlist (cdr vs))
469 (ps prev (cdr ps))
470 (as after (cdr as)))
471 ((and (null as) (null vs) (null ps))
472 (let ((revname (cvs-status-vl-to-str vlist)))
473 (if (cvs-every 'identity (cvs-map 'equal prev vlist))
474 (insert (make-string (+ 4 (length revname)) ? )
475 (or (cvs-tag->name tag) ""))
476 (insert " " revname ": " (or (cvs-tag->name tag) "")))))
477 (let* ((eq (and pe (equal (car ps) (car vs))))
478 (next-eq (equal (cadr ps) (cadr vs))))
479 (let* ((na+char
480 (if (car as)
481 (if eq
482 (if next-eq (cons t cvstree-dstr-char-vbar)
483 (cons t cvstree-dstr-char-branch))
484 (cons nil cvstree-dstr-char-bob))
485 (if eq
486 (if next-eq (cons nil cvstree-dstr-char-space)
487 (cons t cvstree-dstr-char-eob))
488 (cons nil (if (and (eq (cvs-tag->type tag) 'branch)
489 (cvs-every 'null as))
490 cvstree-dstr-char-space
491 cvstree-dstr-char-hbar))))))
492 (insert (cdr na+char))
493 (push (car na+char) nas))
494 (setq pe eq)))
495 (nreverse nas))))
496
497;;;;
498;;;; Merged trees from different files
499;;;;
500
501(defun cvs-tree-fuzzy-merge-1 (trees tree prev)
502 )
503
504(defun cvs-tree-fuzzy-merge (trees tree)
505 "Do the impossible: merge TREE into TREES."
506 ())
507
508(defun cvs-tree ()
509 "Get tags from the status output and merge tham all into a big tree."
510 (save-excursion
511 (goto-char (point-min))
512 (let ((inhibit-read-only t)
513 (trees (make-vector 31 0)) tree)
514 (while (listp (setq tree (cvs-tags->tree (cvs-status-get-tags))))
515 (cvs-tree-fuzzy-merge trees tree))
516 (erase-buffer)
517 (let ((cvs-tag-print-rev nil))
518 (cvs-tree-print tree 'cvs-tag->string 3)))))
519
520
521(provide 'cvs-status)
522
523;;; cvs-status.el ends here
diff --git a/lisp/emacs-lisp/ewoc.el b/lisp/emacs-lisp/ewoc.el
new file mode 100644
index 00000000000..2af8dd49298
--- /dev/null
+++ b/lisp/emacs-lisp/ewoc.el
@@ -0,0 +1,620 @@
1;;; ewoc.el -- Utility to maintain a view of a list of objects in a buffer
2
3;; Copyright (C) 1991-2000 Free Software Foundation
4
5;; Author: Per Cederqvist <ceder@lysator.liu.se>
6;; Inge Wallin <inge@lysator.liu.se>
7;; Maintainer: monnier@gnu.org
8;; Created: 3 Aug 1992
9;; Keywords: extensions, lisp
10
11;; This file is part of GNU Emacs.
12
13;; GNU Emacs is free software; you can redistribute it and/or modify
14;; it under the terms of the GNU General Public License as published by
15;; the Free Software Foundation; either version 2, or (at your option)
16;; any later version.
17
18;; GNU Emacs is distributed in the hope that it will be useful,
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
24;; along with GNU Emacs; see the file COPYING. If not, write to the
25;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
26;; Boston, MA 02111-1307, USA.
27
28;;; Commentary:
29
30;; Ewoc Was Once Cookie
31;; But now it's Emacs' Widget for Object Collections
32
33;; As the name implies this derives from the `cookie' package (part
34;; of Elib). The changes are mostly superficial:
35
36;; - uses CL (and its `defstruct'
37;; - separate from Elib.
38;; - uses its own version of a doubly-linked list which allows us
39;; to merge the elib-wrapper and the elib-node structures into ewoc-node
40;; - dropping functions not used by PCL-CVS (the only client of ewoc at the
41;; time of writing)
42;; - removing unused arguments
43;; - renaming:
44;; elib-node ==> ewoc--node
45;; collection ==> ewoc
46;; tin ==> ewoc--node
47;; cookie ==> data or element or elem
48
49;; Introduction
50;; ============
51;;
52;; Ewoc is a package that implements a connection between an
53;; dll (a doubly linked list) and the contents of a buffer.
54;; Possible uses are dired (have all files in a list, and show them),
55;; buffer-list, kom-prioritize (in the LysKOM elisp client) and
56;; others. pcl-cvs.el uses ewoc.el.
57;;
58;; Ewoc can be considered as the `view' part of a model-view-controller.
59;;
60;; A `element' can be any lisp object. When you use the ewoc
61;; package you specify a pretty-printer, a function that inserts
62;; a printable representation of the element in the buffer. (The
63;; pretty-printer should use "insert" and not
64;; "insert-before-markers").
65;;
66;; A `ewoc' consists of a doubly linked list of elements, a
67;; header, a footer and a pretty-printer. It is displayed at a
68;; certain point in a certain buffer. (The buffer and point are
69;; fixed when the ewoc is created). The header and the footer
70;; are constant strings. They appear before and after the elements.
71;; (Currently, once set, they can not be changed).
72;;
73;; Ewoc does not affect the mode of the buffer in any way. It
74;; merely makes it easy to connect an underlying data representation
75;; to the buffer contents.
76;;
77;; A `ewoc--node' is an object that contains one element. There are
78;; functions in this package that given an ewoc--node extracts the data, or
79;; gives the next or previous ewoc--node. (All ewoc--nodes are linked together
80;; in a doubly linked list. The 'previous' ewoc--node is the one that appears
81;; before the other in the buffer.) You should not do anything with
82;; an ewoc--node except pass it to the functions in this package.
83;;
84;; An ewoc is a very dynamic thing. You can easily add or delete elements.
85;; You can apply a function to all elements in an ewoc, etc, etc.
86;;
87;; Remember that an element can be anything. Your imagination is the
88;; limit! It is even possible to have another ewoc as an
89;; element. In that way some kind of tree hierarchy can be created.
90;;
91;; Full documentation will, God willing, soon be available in a
92;; Texinfo manual.
93
94;; In the mean time `grep '^(.*ewoc-[^-]' emacs-lisp/ewoc.el' can help
95;; you find all the exported functions:
96;;
97;; (defun ewoc-create (buffer pretty-printer &optional header footer pos)
98;; (defalias 'ewoc-data 'ewoc--node-data)
99;; (defun ewoc-enter-first (ewoc data)
100;; (defun ewoc-enter-last (ewoc data)
101;; (defun ewoc-enter-after (ewoc node data)
102;; (defun ewoc-enter-before (ewoc node data)
103;; (defun ewoc-next (ewoc node)
104;; (defun ewoc-prev (ewoc node)
105;; (defun ewoc-nth (ewoc n)
106;; (defun ewoc-map (map-function ewoc &rest args)
107;; (defun ewoc-filter (ewoc predicate &rest args)
108;; (defun ewoc-locate (ewoc pos &optional guess)
109;; (defun ewoc-invalidate (ewoc &rest nodes)
110;; (defun ewoc-goto-prev (ewoc pos arg)
111;; (defun ewoc-goto-next (ewoc pos arg)
112;; (defun ewoc-goto-node (ewoc node)
113;; (defun ewoc-refresh (ewoc)
114;; (defun ewoc-collect (ewoc predicate &rest args)
115;; (defun ewoc-buffer (ewoc)
116
117
118;; Coding conventions
119;; ==================
120;;
121;; All functions of course start with `ewoc'. Functions and macros
122;; starting with the prefix `ewoc--' are meant for internal use,
123;; while those starting with `ewoc-' are exported for public use.
124;; There are currently no global or buffer-local variables used.
125
126
127;;; Code:
128
129(eval-when-compile (require 'cl)) ;because of CL compiler macros
130
131;; The doubly linked list is implemented as a circular list
132;; with a dummy node first and last. The dummy node is used as
133;; "the dll" (or rather is the dll handle passed around).
134
135(defstruct (ewoc--node
136 (:type vector) ;required for ewoc--node-branch hack
137 (:constructor ewoc--node-create (start-marker data)))
138 left right data start-marker)
139
140(defalias 'ewoc--node-branch 'aref)
141
142(defun ewoc--dll-create ()
143 "Create an empty doubly linked list."
144 (let ((dummy-node (ewoc--node-create 'DL-LIST 'DL-LIST)))
145 (setf (ewoc--node-right dummy-node) dummy-node)
146 (setf (ewoc--node-left dummy-node) dummy-node)
147 dummy-node))
148
149(defun ewoc--node-enter-before (node elemnode)
150 "Insert ELEMNODE before NODE in a DLL."
151 (assert (and (null (ewoc--node-left elemnode)) (null (ewoc--node-right elemnode))))
152 (setf (ewoc--node-left elemnode) (ewoc--node-left node))
153 (setf (ewoc--node-right elemnode) node)
154 (setf (ewoc--node-right (ewoc--node-left node)) elemnode)
155 (setf (ewoc--node-left node) elemnode))
156
157(defun ewoc--node-enter-first (dll node)
158 "Add a free floating NODE first in DLL."
159 (ewoc--node-enter-before (ewoc--node-right dll) node))
160
161(defun ewoc--node-enter-last (dll node)
162 "Add a free floating NODE last in DLL."
163 (ewoc--node-enter-before dll node))
164
165(defun ewoc--node-next (dll node)
166 "Return the node after NODE, or nil if NODE is the last node."
167 (unless (eq (ewoc--node-right node) dll) (ewoc--node-right node)))
168
169(defun ewoc--node-prev (dll node)
170 "Return the node before NODE, or nil if NODE is the first node."
171 (unless (eq (ewoc--node-left node) dll) (ewoc--node-left node)))
172
173(defun ewoc--node-delete (node)
174 "Unbind NODE from its doubly linked list and return it."
175 ;; This is a no-op when applied to the dummy node. This will return
176 ;; nil if applied to the dummy node since it always contains nil.
177 (setf (ewoc--node-right (ewoc--node-left node)) (ewoc--node-right node))
178 (setf (ewoc--node-left (ewoc--node-right node)) (ewoc--node-left node))
179 (setf (ewoc--node-left node) nil)
180 (setf (ewoc--node-right node) nil)
181 node)
182
183(defun ewoc--node-nth (dll n)
184 "Return the Nth node from the doubly linked list DLL.
185N counts from zero. If DLL is not that long, nil is returned.
186If N is negative, return the -(N+1)th last element.
187Thus, (ewoc--node-nth dll 0) returns the first node,
188and (ewoc--node-nth dll -1) returns the last node."
189 ;; Branch 0 ("follow left pointer") is used when n is negative.
190 ;; Branch 1 ("follow right pointer") is used otherwise.
191 (let* ((branch (if (< n 0) 0 1))
192 (node (ewoc--node-branch dll branch)))
193 (if (< n 0) (setq n (- -1 n)))
194 (while (and (not (eq dll node)) (> n 0))
195 (setq node (ewoc--node-branch node branch))
196 (setq n (1- n)))
197 (unless (eq dll node) node)))
198
199
200;;; The ewoc data type
201
202(defstruct (ewoc
203 (:constructor nil)
204 (:constructor ewoc--create
205 (buffer pretty-printer header footer dll))
206 (:conc-name ewoc--))
207 buffer pretty-printer header footer dll last-node)
208
209(defmacro ewoc--set-buffer-bind-dll-let* (ewoc varlist &rest forms)
210 "Execute FORMS with ewoc--buffer selected as current buffer,
211dll bound to ewoc--dll, and VARLIST bound as in a let*.
212dll will be bound when VARLIST is initialized, but the current
213buffer will *not* have been changed.
214Return value of last form in FORMS."
215 (let ((old-buffer (make-symbol "old-buffer"))
216 (hnd (make-symbol "ewoc")))
217 (` (let* (((, old-buffer) (current-buffer))
218 ((, hnd) (, ewoc))
219 (dll (ewoc--dll (, hnd)))
220 (,@ varlist))
221 (set-buffer (ewoc--buffer (, hnd)))
222 (unwind-protect
223 (progn (,@ forms))
224 (set-buffer (, old-buffer)))))))
225
226(defmacro ewoc--set-buffer-bind-dll (ewoc &rest forms)
227 `(ewoc--set-buffer-bind-dll-let* ,ewoc nil ,@forms))
228
229(defsubst ewoc--filter-hf-nodes (ewoc node)
230 "Evaluate NODE once and return it.
231BUT if it is the header or the footer in EWOC return nil instead."
232 (unless (or (eq node (ewoc--header ewoc))
233 (eq node (ewoc--footer ewoc)))
234 node))
235
236
237(defun ewoc--create-special-node (data string pos)
238 "Insert STRING at POS in current buffer. Remember the start
239position. Create a wrapper containing that start position and the
240element DATA."
241 (save-excursion
242 ;; Remember the position as a number so that it doesn't move
243 ;; when we insert the string.
244 (when (markerp pos) (setq pos (marker-position pos)))
245 (goto-char pos)
246 (let ((inhibit-read-only t))
247 ;; Use insert-before-markers so that the marker for the
248 ;; next element is updated.
249 (insert-before-markers string)
250 ;; Always insert a newline. You want invisible elements? You
251 ;; lose. (At least in this version). FIXME-someday. (It is
252 ;; harder to fix than it might seem. All markers have to point
253 ;; to the right place all the time...)
254 (insert-before-markers ?\n)
255 (ewoc--node-create (copy-marker pos) data))))
256
257
258(defun ewoc--create-node (data pretty-printer pos)
259 "Call PRETTY-PRINTER with point set at POS in current buffer.
260Remember the start position. Create a wrapper containing that
261start position and the element DATA."
262 (save-excursion
263 ;; Remember the position as a number so that it doesn't move
264 ;; when we insert the string.
265 (when (markerp pos) (setq pos (marker-position pos)))
266 (goto-char pos)
267 (let ((inhibit-read-only t))
268 ;; Insert the trailing newline using insert-before-markers
269 ;; so that the start position for the next element is updated.
270 (insert-before-markers ?\n)
271 ;; Move back, and call the pretty-printer.
272 (backward-char 1)
273 (funcall pretty-printer data)
274 (ewoc--node-create (copy-marker pos) data))))
275
276
277(defun ewoc--delete-node-internal (ewoc node)
278 "Delete a data string from EWOC.
279Can not be used on the footer. Returns the wrapper that is deleted.
280The start-marker in the wrapper is set to nil, so that it doesn't
281consume any more resources."
282 (let ((dll (ewoc--dll ewoc))
283 (inhibit-read-only t))
284 ;; If we are about to delete the node pointed at by last-node,
285 ;; set last-node to nil.
286 (if (eq (ewoc--last-node ewoc) node)
287 (setf (ewoc--last-node ewoc) nil))
288
289 (delete-region (ewoc--node-start-marker node)
290 (ewoc--node-start-marker (ewoc--node-next dll node)))
291 (set-marker (ewoc--node-start-marker node) nil)
292 ;; Delete the node, and return the wrapper.
293 (ewoc--node-delete node)))
294
295
296(defvar dll) ;passed by dynamic binding
297
298(defun ewoc--refresh-node (ewoc node)
299 "Redisplay the element represented by NODE.
300Can not be used on the footer. dll *must* be bound to
301\(ewoc--dll ewoc)."
302 (let ((inhibit-read-only t))
303 (save-excursion
304 ;; First, remove the string from the buffer:
305 (delete-region (ewoc--node-start-marker node)
306 (1- (marker-position
307 (ewoc--node-start-marker (ewoc--node-next dll node)))))
308 ;; Calculate and insert the string.
309 (goto-char (ewoc--node-start-marker node))
310 (funcall (ewoc--pretty-printer ewoc)
311 (ewoc--node-data node)))))
312
313;;; ===========================================================================
314;;; Public members of the Ewoc package
315
316
317(defun ewoc-create (buffer pretty-printer &optional header footer pos)
318 "Create an empty ewoc.
319
320The ewoc will be inserted in BUFFER. BUFFER may be a
321buffer or a buffer name. It is created if it does not exist.
322
323PRETTY-PRINTER should be a function that takes one argument, an
324element, and inserts a string representing it in the buffer (at
325point). The string PRETTY-PRINTER inserts may be empty or span
326several linse. A trailing newline will always be inserted
327automatically. The PRETTY-PRINTER should use insert, and not
328insert-before-markers.
329
330Optional third argument HEADER is a string that will always be
331present at the top of the ewoc. HEADER should end with a
332newline. Optionaly fourth argument FOOTER is similar, and will
333always be inserted at the bottom of the ewoc.
334
335Optional fifth argument POS is a buffer position, specifying
336where the ewoc will be inserted. It defaults to the
337beginning of the buffer."
338 (let ((new-ewoc
339 (ewoc--create (get-buffer-create buffer)
340 pretty-printer nil nil (ewoc--dll-create))))
341 (ewoc--set-buffer-bind-dll new-ewoc
342 ;; Set default values
343 (unless header (setq header ""))
344 (unless footer (setq footer ""))
345 (unless pos (setq pos (point-min)))
346 ;; Force header to be above footer.
347 (if (markerp pos) (setq pos (marker-position pos)))
348 (let ((foot (ewoc--create-special-node footer footer pos))
349 (head (ewoc--create-special-node header header pos)))
350 (ewoc--node-enter-first dll head)
351 (ewoc--node-enter-last dll foot)
352 (setf (ewoc--header new-ewoc) (ewoc--node-nth dll 0))
353 (setf (ewoc--footer new-ewoc) (ewoc--node-nth dll -1))))
354 ;; Return the ewoc
355 new-ewoc))
356
357(defalias 'ewoc-data 'ewoc--node-data)
358
359(defun ewoc-enter-first (ewoc data)
360 "Enter DATA first in EWOC."
361 (ewoc--set-buffer-bind-dll ewoc
362 (ewoc-enter-after ewoc (ewoc--node-nth dll 0) data)))
363
364(defun ewoc-enter-last (ewoc data)
365 "Enter DATA last in EWOC."
366 (ewoc--set-buffer-bind-dll ewoc
367 (ewoc-enter-before ewoc (ewoc--node-nth dll -1) data)))
368
369
370(defun ewoc-enter-after (ewoc node data)
371 "Enter a new element DATA after NODE in EWOC."
372 (ewoc--set-buffer-bind-dll ewoc
373 (ewoc-enter-before ewoc (ewoc--node-next dll node) data)))
374
375(defun ewoc-enter-before (ewoc node data)
376 "Enter a new element DATA before NODE in EWOC."
377 (ewoc--set-buffer-bind-dll ewoc
378 (ewoc--node-enter-before
379 node
380 (ewoc--create-node
381 data
382 (ewoc--pretty-printer ewoc)
383 (ewoc--node-start-marker node)))))
384
385(defun ewoc-next (ewoc node)
386 "Get the next node.
387Returns nil if NODE is nil or the last element."
388 (when node
389 (ewoc--filter-hf-nodes
390 ewoc (ewoc--node-next (ewoc--dll ewoc) node))))
391
392(defun ewoc-prev (ewoc node)
393 "Get the previous node.
394Returns nil if NODE is nil or the first element."
395 (when node
396 (ewoc--filter-hf-nodes
397 ewoc
398 (ewoc--node-prev (ewoc--dll ewoc) node))))
399
400
401(defun ewoc-nth (ewoc n)
402 "Return the Nth node.
403N counts from zero. Nil is returned if there is less than N elements.
404If N is negative, return the -(N+1)th last element.
405Thus, (ewoc-nth dll 0) returns the first node,
406and (ewoc-nth dll -1) returns the last node.
407Use `ewoc--node-data' to extract the data from the node."
408 ;; Skip the header (or footer, if n is negative).
409 (setq n (if (< n 0) (1- n) (1+ n)))
410 (ewoc--filter-hf-nodes ewoc
411 (ewoc--node-nth (ewoc--dll ewoc) n)))
412
413(defun ewoc-map (map-function ewoc &rest args)
414 "Apply MAP-FUNCTION to all elements in EWOC.
415MAP-FUNCTION is applied to the first element first.
416If MAP-FUNCTION returns non-nil the element will be refreshed (its
417pretty-printer will be called once again).
418
419Note that the buffer for EWOC will be current buffer when MAP-FUNCTION
420is called. MAP-FUNCTION must restore the current buffer to BUFFER before
421it returns, if it changes it.
422
423If more than two arguments are given, the remaining
424arguments will be passed to MAP-FUNCTION."
425 (ewoc--set-buffer-bind-dll-let* ewoc
426 ((footer (ewoc--footer ewoc))
427 (node (ewoc--node-nth dll 1)))
428 (while (not (eq node footer))
429 (if (apply map-function (ewoc--node-data node) args)
430 (ewoc--refresh-node ewoc node))
431 (setq node (ewoc--node-next dll node)))))
432
433(defun ewoc-filter (ewoc predicate &rest args)
434 "Remove all elements in EWOC for which PREDICATE returns nil.
435Note that the buffer for EWOC will be current-buffer when PREDICATE
436is called. PREDICATE must restore the current buffer before it returns
437if it changes it.
438The PREDICATE is called with the element as its first argument. If any
439ARGS are given they will be passed to the PREDICATE."
440 (ewoc--set-buffer-bind-dll-let* ewoc
441 ((node (ewoc--node-nth dll 1))
442 (footer (ewoc--footer ewoc))
443 (next nil))
444 (while (not (eq node footer))
445 (setq next (ewoc--node-next dll node))
446 (unless (apply predicate (ewoc--node-data node) args)
447 (ewoc--delete-node-internal ewoc node))
448 (setq node next))))
449
450(defun ewoc-locate (ewoc pos &optional guess)
451 "Return the node that POS (a buffer position) is within.
452POS may be a marker or an integer.
453GUESS should be a node that it is likely that POS is near.
454
455If POS points before the first element, the first node is returned.
456If POS points after the last element, the last node is returned.
457If the EWOC is empty, nil is returned."
458 (ewoc--set-buffer-bind-dll-let* ewoc
459 ((footer (ewoc--footer ewoc)))
460
461 (cond
462 ;; Nothing present?
463 ((eq (ewoc--node-nth dll 1) (ewoc--node-nth dll -1))
464 nil)
465
466 ;; Before second elem?
467 ((< pos (ewoc--node-start-marker (ewoc--node-nth dll 2)))
468 (ewoc--node-nth dll 1))
469
470 ;; After one-before-last elem?
471 ((>= pos (ewoc--node-start-marker (ewoc--node-nth dll -2)))
472 (ewoc--node-nth dll -2))
473
474 ;; We now know that pos is within a elem.
475 (t
476 ;; Make an educated guess about which of the three known
477 ;; node'es (the first, the last, or GUESS) is nearest.
478 (let* ((best-guess (ewoc--node-nth dll 1))
479 (distance (abs (- pos (ewoc--node-start-marker best-guess)))))
480 (when guess
481 (let ((d (abs (- pos (ewoc--node-start-marker guess)))))
482 (when (< d distance)
483 (setq distance d)
484 (setq best-guess guess))))
485
486 (let* ((g (ewoc--node-nth dll -1)) ;Check the last elem
487 (d (abs (- pos (ewoc--node-start-marker g)))))
488 (when (< d distance)
489 (setq distance d)
490 (setq best-guess g)))
491
492 (when (ewoc--last-node ewoc) ;Check "previous".
493 (let* ((g (ewoc--last-node ewoc))
494 (d (abs (- pos (ewoc--node-start-marker g)))))
495 (when (< d distance)
496 (setq distance d)
497 (setq best-guess g))))
498
499 ;; best-guess is now a "best guess".
500 ;; Find the correct node. First determine in which direction
501 ;; it lies, and then move in that direction until it is found.
502
503 (cond
504 ;; Is pos after the guess?
505 ((>= pos
506 (ewoc--node-start-marker best-guess))
507 ;; Loop until we are exactly one node too far down...
508 (while (>= pos (ewoc--node-start-marker best-guess))
509 (setq best-guess (ewoc--node-next dll best-guess)))
510 ;; ...and return the previous node.
511 (ewoc--node-prev dll best-guess))
512
513 ;; Pos is before best-guess
514 (t
515 (while (< pos (ewoc--node-start-marker best-guess))
516 (setq best-guess (ewoc--node-prev dll best-guess)))
517 best-guess)))))))
518
519(defun ewoc-invalidate (ewoc &rest nodes)
520 "Refresh some elements.
521The pretty-printer that for EWOC will be called for all NODES."
522 (ewoc--set-buffer-bind-dll ewoc
523 (dolist (node nodes)
524 (ewoc--refresh-node ewoc node))))
525
526(defun ewoc-goto-prev (ewoc pos arg)
527 "Move point to the ARGth previous element.
528Don't move if we are at the first element, or if EWOC is empty.
529Returns the node we moved to."
530 (ewoc--set-buffer-bind-dll-let* ewoc
531 ((node (ewoc-locate ewoc pos (ewoc--last-node ewoc))))
532 (when node
533 (while (and node (> arg 0))
534 (setq arg (1- arg))
535 (setq node (ewoc--node-prev dll node)))
536 ;; Never step above the first element.
537 (unless (ewoc--filter-hf-nodes ewoc node)
538 (setq node (ewoc--node-nth dll 1)))
539 (ewoc-goto-node ewoc node))))
540
541(defun ewoc-goto-next (ewoc pos arg)
542 "Move point to the ARGth next element.
543Don't move if we are at the last element.
544Returns the node."
545 (ewoc--set-buffer-bind-dll-let* ewoc
546 ((node (ewoc-locate ewoc pos (ewoc--last-node ewoc))))
547 (while (and node (> arg 0))
548 (setq arg (1- arg))
549 (setq node (ewoc--node-next dll node)))
550 ;; Never step below the first element.
551 (unless (ewoc--filter-hf-nodes ewoc node)
552 (setq node (ewoc--node-nth dll -2)))
553 (ewoc-goto-node ewoc node)))
554
555(defun ewoc-goto-node (ewoc node)
556 "Move point to NODE."
557 (ewoc--set-buffer-bind-dll ewoc
558 (goto-char (ewoc--node-start-marker node))
559 (if goal-column (move-to-column goal-column))
560 (setf (ewoc--last-node ewoc) node)))
561
562(defun ewoc-refresh (ewoc)
563 "Refresh all data in EWOC.
564The pretty-printer that was specified when the EWOC was created
565will be called for all elements in EWOC.
566Note that `ewoc-invalidate' is more efficient if only a small
567number of elements needs to be refreshed."
568 (ewoc--set-buffer-bind-dll-let* ewoc
569 ((header (ewoc--header ewoc))
570 (footer (ewoc--footer ewoc)))
571 (let ((inhibit-read-only t))
572 (delete-region (ewoc--node-start-marker (ewoc--node-nth dll 1))
573 (ewoc--node-start-marker footer))
574 (goto-char (ewoc--node-start-marker footer))
575 (let ((node (ewoc--node-nth dll 1)))
576 (while (not (eq node footer))
577 (set-marker (ewoc--node-start-marker node) (point))
578 (funcall (ewoc--pretty-printer ewoc)
579 (ewoc--node-data node))
580 (insert "\n")
581 (setq node (ewoc--node-next dll node)))))
582 (set-marker (ewoc--node-start-marker footer) (point))))
583
584(defun ewoc-collect (ewoc predicate &rest args)
585 "Select elements from EWOC using PREDICATE.
586Return a list of all selected data elements.
587PREDICATE is a function that takes a data element as its first argument.
588The elements on the returned list will appear in the same order as in
589the buffer. You should not rely on in which order PREDICATE is
590called.
591Note that the buffer the EWOC is displayed in is current-buffer
592when PREDICATE is called. If PREDICATE must restore current-buffer if
593it changes it.
594If more than two arguments are given the
595remaining arguments will be passed to PREDICATE."
596 (ewoc--set-buffer-bind-dll-let* ewoc
597 ((header (ewoc--header ewoc))
598 (node (ewoc--node-nth dll -2))
599 result)
600 (while (not (eq node header))
601 (if (apply predicate (ewoc--node-data node) args)
602 (push (ewoc--node-data node) result))
603 (setq node (ewoc--node-prev dll node)))
604 result))
605
606(defun ewoc-buffer (ewoc)
607 "Return the buffer that is associated with EWOC.
608Returns nil if the buffer has been deleted."
609 (let ((buf (ewoc--buffer ewoc)))
610 (when (buffer-name buf) buf)))
611
612
613(provide 'ewoc)
614
615;;; Local Variables:
616;;; eval: (put 'ewoc--set-buffer-bind-dll 'lisp-indent-hook 1)
617;;; eval: (put 'ewoc--set-buffer-bind-dll-let* 'lisp-indent-hook 2)
618;;; End:
619
620;;; ewoc.el ends here
diff --git a/lisp/log-edit.el b/lisp/log-edit.el
new file mode 100644
index 00000000000..6b238835a9c
--- /dev/null
+++ b/lisp/log-edit.el
@@ -0,0 +1,448 @@
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: $
8;; Revision: $Id: log-edit.el,v 1.8 2000/03/05 21:32:21 monnier Exp $
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
31;; - Remove a single leading `* <file>' in log-edit-insert-changelog
32;; - Move in VC's code
33;; - Add compatibility for VC's hook variables
34;; - add compatibility with cvs-edit.el
35
36;;; Code:
37
38(eval-when-compile (require 'cl))
39(require 'add-log) ; for all the ChangeLog goodies
40(require 'pcvs-util)
41(require 'ring)
42(require 'vc)
43
44;;;;
45;;;; Global Variables
46;;;;
47
48(defgroup log-edit nil
49 "Major mode for editing commit messages for PCL-CVS."
50 :group 'pcl-cvs
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
63 :inherit (if (boundp 'vc-log-entry-mode) vc-log-entry-mode))
64
65(defcustom log-edit-confirm t
66 "*If non-nil, `log-edit-done' will request confirmation.
67If 'changed, only request confirmation if the list of files has
68 changed since the beginning of the log-edit session."
69 :group 'log-edit
70 :type '(choice (const changed) (const t) (const nil)))
71
72(defcustom log-edit-keep-buffer nil
73 "*If non-nil, don't hide the buffer after `log-edit-done'."
74 :group 'log-edit
75 :type 'boolean)
76
77(defvar cvs-commit-buffer-require-final-newline t
78 "Obsolete, use `log-edit-require-final-newline'.")
79
80(defcustom log-edit-require-final-newline
81 cvs-commit-buffer-require-final-newline
82 "*Enforce a newline at the end of commit log messages.
83Enforce it silently if t, query if non-nil and don't do anything if nil."
84 :group 'log-edit
85 :type '(choice (const ask) (const t) (const nil)))
86
87(defcustom log-edit-setup-invert nil
88 "*Non-nil means `log-edit' should invert the meaning of its SETUP arg.
89If SETUP is 'force, this variable has no effect."
90 :group 'log-edit
91 :type 'boolean)
92
93(defcustom log-edit-hook '(log-edit-insert-cvs-template
94 log-edit-insert-changelog)
95 "*Hook run at the end of `log-edit'."
96 :group 'log-edit
97 :type '(hook :options (log-edit-insert-cvs-template
98 log-edit-insert-changelog)))
99
100(defcustom log-edit-mode-hook nil
101 "*Hook run when entering `log-edit-mode'."
102 :group 'log-edit
103 :type 'hook)
104
105(defcustom log-edit-done-hook nil
106 "*Hook run before doing the actual commit.
107This hook can be used to cleanup the message, enforce various
108conventions, or to allow recording the message in some other database,
109such as a bug-tracking system. The list of files about to be committed
110can be obtained from `log-edit-files'."
111 :group 'log-edit
112 :type '(hook :options (log-edit-delete-common-indentation
113 log-edit-add-to-changelog)))
114
115(defvar cvs-changelog-full-paragraphs t
116 "*If non-nil, include full ChangeLog paragraphs in the CVS log.
117This may be set in the ``local variables'' section of a ChangeLog, to
118indicate the policy for that ChangeLog.
119
120A ChangeLog paragraph is a bunch of log text containing no blank lines;
121a paragraph usually describes a set of changes with a single purpose,
122but perhaps spanning several functions in several files. Changes in
123different paragraphs are unrelated.
124
125You could argue that the CVS log entry for a file should contain the
126full ChangeLog paragraph mentioning the change to the file, even though
127it may mention other files, because that gives you the full context you
128need to understand the change. This is the behaviour you get when this
129variable is set to t.
130
131On the other hand, you could argue that the CVS log entry for a change
132should contain only the text for the changes which occurred in that
133file, because the CVS log is per-file. This is the behaviour you get
134when this variable is set to nil.")
135
136;;;; Internal global or buffer-local vars
137
138(defconst log-edit-files-buf "*log-edit-files*")
139(defvar log-edit-initial-files nil)
140(defvar log-edit-callback nil)
141(defvar log-edit-listfun nil)
142
143;;;;
144;;;; Actual code
145;;;;
146
147;;;###autoload
148(defun log-edit (callback &optional setup listfun &rest ignore)
149 "Setup a buffer to enter a log message.
150The buffer will be put in `log-edit-mode'.
151If SETUP is non-nil, the buffer is then erased and `log-edit-hook' is run.
152Mark and point will be set around the entire contents of the
153buffer so that it is easy to kill the contents of the buffer with \\[kill-region].
154Once you're done editing the message, pressing \\[log-edit-done] will call
155`log-edit-done' which will end up calling CALLBACK to do the actual commit."
156 (when (and log-edit-setup-invert (not (eq setup 'force)))
157 (setq setup (not setup)))
158 (when setup (erase-buffer))
159 (log-edit-mode)
160 (set (make-local-variable 'log-edit-callback) callback)
161 (set (make-local-variable 'log-edit-listfun) listfun)
162 (when setup (run-hooks 'log-edit-hook))
163 (goto-char (point-min)) (push-mark (point-max))
164 (set (make-local-variable 'log-edit-initial-files) (log-edit-files))
165 (message (substitute-command-keys
166 "Press \\[log-edit-done] when you are done editing.")))
167
168(define-derived-mode log-edit-mode text-mode "Log-Edit"
169 "Major mode for entering commit messages.
170This mode is intended for entering messages in a *cvs-commit*
171buffer when using PCL-CVS. It provides a binding for the
172\\[log-edit-done] command that should be used when done editing
173to trigger the actual commit, as well as a few handy support
174commands.
175\\{log-edit-mode-map}")
176
177(defun log-edit-hide-buf (&optional buf where)
178 (when (setq buf (get-buffer (or buf log-edit-files-buf)))
179 (let ((win (get-buffer-window buf where)))
180 (if win (ignore-errors (delete-window win))))
181 (bury-buffer buf)))
182
183(defun log-edit-done ()
184 "Finish editing the log message and commit the files.
185This can only be used in the *cvs-commit* buffer.
186With a prefix argument, prompt for cvs commit flags.
187If you want to abort the commit, simply delete the buffer."
188 (interactive)
189 (if (and (> (point-max) 1)
190 (/= (char-after (1- (point-max))) ?\n)
191 (or (eq log-edit-require-final-newline t)
192 (and log-edit-require-final-newline
193 (y-or-n-p
194 (format "Buffer %s does not end in newline. Add one? "
195 (buffer-name))))))
196 (save-excursion
197 (goto-char (point-max))
198 (insert ?\n)))
199 (if (boundp 'vc-comment-ring) (ring-insert vc-comment-ring (buffer-string)))
200 (let ((win (get-buffer-window log-edit-files-buf)))
201 (if (and log-edit-confirm
202 (not (and (eq log-edit-confirm 'changed)
203 (equal (log-edit-files) log-edit-initial-files)))
204 (progn
205 (log-edit-show-files)
206 (not (y-or-n-p "Really commit ? "))))
207 (progn (when (not win) (log-edit-hide-buf))
208 (message "Oh, well! Later maybe?"))
209 (run-hooks 'log-edit-done-hook)
210 (log-edit-hide-buf)
211 (unless log-edit-keep-buffer
212 (cvs-bury-buffer (current-buffer)
213 (when (boundp 'cvs-buffer) cvs-buffer)))
214 (call-interactively log-edit-callback))))
215
216(defun log-edit-files ()
217 "Return the list of files that are about to be committed."
218 (ignore-errors (funcall log-edit-listfun)))
219
220
221(defun log-edit-insert-changelog ()
222 "Insert a log message by looking at the ChangeLog.
223The idea is to write your ChangeLog entries first, and then use this
224command to commit your changes.
225
226To select default log text, we:
227- find the ChangeLog entries for the files to be checked in,
228- verify that the top entry in the ChangeLog is on the current date
229 and by the current user; if not, we don't provide any default text,
230- search the ChangeLog entry for paragraphs containing the names of
231 the files we're checking in, and finally
232- use those paragraphs as the log text."
233 (interactive)
234 (cvs-insert-changelog-entries (log-edit-files))
235 (log-edit-delete-common-indentation))
236
237(defun log-edit-mode-help ()
238 "Provide help for the `log-edit-mode-map'."
239 (interactive)
240 (if (eq last-command 'log-edit-mode-help)
241 (describe-function major-mode)
242 (message
243 (substitute-command-keys
244 "Type `\\[log-edit-done]' to finish commit. Try `\\[describe-function] log-edit-done' for more help."))))
245
246(defun log-edit-delete-common-indentation ()
247 "Unindent the current buffer rigidly until at least one line is flush left."
248 (save-excursion
249 (let ((common (point-max)))
250 (goto-char (point-min))
251 (while (< (point) (point-max))
252 (if (not (looking-at "^[ \t]*$"))
253 (setq common (min common (current-indentation))))
254 (forward-line 1))
255 (indent-rigidly (point-min) (point-max) (- common)))))
256
257(defun log-edit-show-files ()
258 "Show the list of files to be committed."
259 (interactive)
260 (let* ((files (log-edit-files))
261 (editbuf (current-buffer))
262 (buf (get-buffer-create "*log-edit-files*")))
263 (with-current-buffer buf
264 (log-edit-hide-buf buf 'all)
265 (setq buffer-read-only nil)
266 (erase-buffer)
267 (insert (mapconcat 'identity files "\n"))
268 (setq buffer-read-only t)
269 (goto-char (point-min))
270 (save-selected-window
271 (cvs-pop-to-buffer-same-frame buf)
272 (shrink-window-if-larger-than-buffer)
273 (selected-window)))))
274
275(defun log-edit-insert-cvs-template ()
276 "Insert the template specified by the CVS administrator, if any."
277 (interactive)
278 (when (file-readable-p "CVS/Template")
279 (insert-file-contents "CVS/Template")))
280
281
282(defun log-edit-add-to-changelog ()
283 "Insert this log message into the appropriate ChangeLog file."
284 (interactive)
285 ;; Yuck!
286 (unless (string= (buffer-string) (ring-ref vc-comment-ring 0))
287 (ring-insert vc-comment-ring (buffer-string)))
288 (dolist (f (log-edit-files))
289 (let ((buffer-file-name (expand-file-name f)))
290 (save-excursion
291 (vc-comment-to-change-log)))))
292
293;;;;
294;;;; functions for getting commit message from ChangeLog a file...
295;;;; Courtesy Jim Blandy
296;;;;
297
298(defun cvs-narrow-changelog ()
299 "Narrow to the top page of the current buffer, a ChangeLog file.
300Actually, the narrowed region doesn't include the date line.
301A \"page\" in a ChangeLog file is the area between two dates."
302 (or (eq major-mode 'change-log-mode)
303 (error "cvs-narrow-changelog: current buffer isn't a ChangeLog"))
304
305 (goto-char (point-min))
306
307 ;; Skip date line and subsequent blank lines.
308 (forward-line 1)
309 (if (looking-at "[ \t\n]*\n")
310 (goto-char (match-end 0)))
311
312 (let ((start (point)))
313 (forward-page 1)
314 (narrow-to-region start (point))
315 (goto-char (point-min))))
316
317(defun cvs-changelog-paragraph ()
318 "Return the bounds of the ChangeLog paragraph containing point.
319If we are between paragraphs, return the previous paragraph."
320 (save-excursion
321 (beginning-of-line)
322 (if (looking-at "^[ \t]*$")
323 (skip-chars-backward " \t\n" (point-min)))
324 (list (progn
325 (if (re-search-backward "^[ \t]*\n" nil 'or-to-limit)
326 (goto-char (match-end 0)))
327 (point))
328 (if (re-search-forward "^[ \t\n]*$" nil t)
329 (match-beginning 0)
330 (point)))))
331
332(defun cvs-changelog-subparagraph ()
333 "Return the bounds of the ChangeLog subparagraph containing point.
334A subparagraph is a block of non-blank lines beginning with an asterisk.
335If we are between sub-paragraphs, return the previous subparagraph."
336 (save-excursion
337 (end-of-line)
338 (if (search-backward "*" nil t)
339 (list (progn (beginning-of-line) (point))
340 (progn
341 (forward-line 1)
342 (if (re-search-forward "^[ \t]*[\n*]" nil t)
343 (match-beginning 0)
344 (point-max))))
345 (list (point) (point)))))
346
347(defun cvs-changelog-entry ()
348 "Return the bounds of the ChangeLog entry containing point.
349The variable `cvs-changelog-full-paragraphs' decides whether an
350\"entry\" is a paragraph or a subparagraph; see its documentation string
351for more details."
352 (if cvs-changelog-full-paragraphs
353 (cvs-changelog-paragraph)
354 (cvs-changelog-subparagraph)))
355
356(defvar user-full-name)
357(defvar user-mail-address)
358(defun cvs-changelog-ours-p ()
359 "See if ChangeLog entry at point is for the current user, today.
360Return non-nil iff it is."
361 ;; Code adapted from add-change-log-entry.
362 (let ((name (or (and (boundp 'add-log-full-name) add-log-full-name)
363 (and (fboundp 'user-full-name) (user-full-name))
364 (and (boundp 'user-full-name) user-full-name)))
365 (mail (or (and (boundp 'add-log-mailing-address) add-log-mailing-address)
366 ;;(and (fboundp 'user-mail-address) (user-mail-address))
367 (and (boundp 'user-mail-address) user-mail-address)))
368 (time (or (and (boundp 'add-log-time-format)
369 (functionp add-log-time-format)
370 (funcall add-log-time-format))
371 (format-time-string "%Y-%m-%d"))))
372 (looking-at (regexp-quote (format "%s %s <%s>" time name mail)))))
373
374(defun cvs-changelog-entries (file)
375 "Return the ChangeLog entries for FILE, and the ChangeLog they came from.
376The return value looks like this:
377 (LOGBUFFER (ENTRYSTART . ENTRYEND) ...)
378where LOGBUFFER is the name of the ChangeLog buffer, and each
379\(ENTRYSTART . ENTRYEND\) pair is a buffer region."
380 (save-excursion
381 (let ((changelog-file-name
382 (let ((default-directory
383 (file-name-directory (expand-file-name file))))
384 ;; `find-change-log' uses `change-log-default-name' if set
385 ;; and sets it before exiting, so we need to work around
386 ;; that memoizing which is undesired here
387 (setq change-log-default-name nil)
388 (find-change-log))))
389 (set-buffer (find-file-noselect changelog-file-name))
390 (unless (eq major-mode 'change-log-mode) (change-log-mode))
391 (goto-char (point-min))
392 (if (looking-at "\\s-*\n") (goto-char (match-end 0)))
393 (if (not (cvs-changelog-ours-p))
394 (list (current-buffer))
395 (save-restriction
396 (cvs-narrow-changelog)
397 (goto-char (point-min))
398
399 ;; Search for the name of FILE relative to the ChangeLog. If that
400 ;; doesn't occur anywhere, they're not using full relative
401 ;; filenames in the ChangeLog, so just look for FILE; we'll accept
402 ;; some false positives.
403 (let ((pattern (file-relative-name
404 file (file-name-directory changelog-file-name))))
405 (if (or (string= pattern "")
406 (not (save-excursion
407 (search-forward pattern nil t))))
408 (setq pattern (file-name-nondirectory file)))
409
410 (let (texts)
411 (while (search-forward pattern nil t)
412 (let ((entry (cvs-changelog-entry)))
413 (push entry texts)
414 (goto-char (elt entry 1))))
415
416 (cons (current-buffer) texts))))))))
417
418(defun cvs-changelog-insert-entries (buffer regions)
419 "Insert those regions in BUFFER specified in REGIONS.
420Sort REGIONS front-to-back first."
421 (let ((regions (sort regions 'car-less-than-car))
422 (last))
423 (dolist (region regions)
424 (when (and last (< last (car region))) (newline))
425 (setq last (elt region 1))
426 (apply 'insert-buffer-substring buffer region))))
427
428(defun cvs-insert-changelog-entries (files)
429 "Given a list of files FILES, insert the ChangeLog entries for them."
430 (let ((buffer-entries nil))
431
432 ;; Add each buffer to buffer-entries, and associate it with the list
433 ;; of entries we want from that file.
434 (dolist (file files)
435 (let* ((entries (cvs-changelog-entries file))
436 (pair (assq (car entries) buffer-entries)))
437 (if pair
438 (setcdr pair (cvs-union (cdr pair) (cdr entries)))
439 (push entries buffer-entries))))
440
441 ;; Now map over each buffer in buffer-entries, sort the entries for
442 ;; each buffer, and extract them as strings.
443 (dolist (buffer-entry buffer-entries)
444 (cvs-changelog-insert-entries (car buffer-entry) (cdr buffer-entry))
445 (when (cdr buffer-entry) (newline)))))
446
447(provide 'log-edit)
448;;; log-edit.el ends here
diff --git a/lisp/log-view.el b/lisp/log-view.el
new file mode 100644
index 00000000000..c157b392ad9
--- /dev/null
+++ b/lisp/log-view.el
@@ -0,0 +1,189 @@
1;;; log-view.el --- Major mode for browsing CVS log output
2
3;; Copyright (C) 1999-2000 Free Software Foundation, Inc.
4
5;; Author: Stefan Monnier <monnier@cs.yale.edu>
6;; Keywords: pcl-cvs cvs log
7;; Version: $Name: $
8;; Revision: $Id: log-view.el,v 1.2 2000/03/03 20:58:09 monnier Exp $
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
31;; - extract version info in log-view-current-tag
32;; - add support for SCCS' output format
33;; - add compatibility with cvs-log.el
34;; - add ability to modify a log-entry (via cvs-mode-admin ;-)
35
36;;; Code:
37
38(eval-when-compile (require 'cl))
39;;(require 'pcvs-defs)
40(require 'pcvs-util)
41
42
43(defgroup log-view nil
44 "Major mode for browsing log output for PCL-CVS."
45 :group 'pcl-cvs
46 :prefix "log-view-")
47
48(easy-mmode-defmap log-view-mode-map
49 '(("n" . log-view-next-message)
50 ("N" . log-view-next-file)
51 ("M-n" . log-view-next-file)
52 ("p" . log-view-prev-message)
53 ("P" . log-view-prev-file)
54 ("M-p" . log-view-prev-file))
55 "Log-View's keymap."
56 :group 'log-view
57 :inherit 'cvs-mode-map)
58
59(defvar log-view-mode-hook nil
60 "Hook run at the end of `log-view-mode'.")
61
62(defface log-view-file-face
63 '((((class color) (background light))
64 (:background "grey70" :bold t))
65 (t (:bold t)))
66 "Face for the file header line in `log-view-mode'."
67 :group 'log-view)
68(defvar log-view-file-face 'log-view-file-face)
69
70(defface log-view-message-face
71 '((((class color) (background light))
72 (:background "grey85"))
73 (t (:bold t)))
74 "Face for the message header line in `log-view-mode'."
75 :group 'log-view)
76(defvar log-view-message-face 'log-view-message-face)
77
78(defconst log-view-file-re
79 (concat "^\\("
80 "Working file: \\(.+\\)"
81 "\\|SCCS/s\\.\\(.+\\):"
82 "\\)\n"))
83(defconst log-view-message-re "^----------------------------$")
84
85(defconst log-view-font-lock-keywords
86 `((,log-view-file-re
87 (2 'cvs-filename-face nil t)
88 (3 'cvs-filename-face nil t)
89 (0 'log-view-file-face append))
90 (,log-view-message-re . log-view-message-face)))
91(defconst log-view-font-lock-defaults
92 '(log-view-font-lock-keywords t nil nil nil))
93
94;;;;
95;;;; Actual code
96;;;;
97
98;;;###autoload
99(autoload 'log-view-mode "log-view" "Major mode for browsing CVS log output." t)
100(eval-when-compile (autoload 'easy-mmode-define-derived-mode "easy-mmode"))
101(easy-mmode-define-derived-mode log-view-mode fundamental-mode "Log-View"
102 "Major mode for browsing CVS log output."
103 (set (make-local-variable 'font-lock-defaults) log-view-font-lock-defaults)
104 (set (make-local-variable 'cvs-minor-wrap-function) 'log-view-minor-wrap))
105
106;;;;
107;;;; Navigation
108;;;;
109
110(defun log-view-next-message (&optional count)
111 "Move to next (COUNT'th) log message."
112 (interactive "p")
113 (unless count (setq count 1))
114 (if (< count 0) (log-view-prev-message (- count))
115 (when (looking-at log-view-message-re) (incf count))
116 (re-search-forward log-view-message-re nil nil count)
117 (goto-char (match-beginning 0))))
118
119(defun log-view-next-file (&optional count)
120 "Move to next (COUNT'th) file."
121 (interactive "p")
122 (unless count (setq count 1))
123 (if (< count 0) (log-view-prev-file (- count))
124 (when (looking-at log-view-file-re) (incf count))
125 (re-search-forward log-view-file-re nil nil count)
126 (goto-char (match-beginning 0))))
127
128(defun log-view-prev-message (&optional count)
129 "Move to previous (COUNT'th) log message."
130 (interactive "p")
131 (unless count (setq count 1))
132 (if (< count 0) (log-view-next-message (- count))
133 (re-search-backward log-view-message-re nil nil count)))
134
135(defun log-view-prev-file (&optional count)
136 "Move to previous (COUNT'th) file."
137 (interactive "p")
138 (unless count (setq count 1))
139 (if (< count 0) (log-view-next-file (- count))
140 (re-search-backward log-view-file-re nil nil count)))
141
142;;;;
143;;;; Linkage to PCL-CVS (mostly copied from cvs-status.el)
144;;;;
145
146(defconst log-view-dir-re "^cvs[.ex]* [a-z]+: Logging \\(.+\\)$")
147
148(defun log-view-current-file ()
149 (save-excursion
150 (forward-line 1)
151 (or (re-search-backward log-view-file-re nil t)
152 (re-search-forward log-view-file-re))
153 (let* ((file (or (match-string 2) (match-string 3)))
154 (cvsdir (and (re-search-backward log-view-dir-re nil t)
155 (match-string 1)))
156 (pcldir (and (re-search-backward cvs-pcl-cvs-dirchange-re nil t)
157 (match-string 1)))
158 (dir ""))
159 (let ((default-directory ""))
160 (when pcldir (setq dir (expand-file-name pcldir dir)))
161 (when cvsdir (setq dir (expand-file-name cvsdir dir)))
162 (expand-file-name file dir)))))
163
164(defun log-view-current-tag ()
165 nil);; FIXME
166
167(defun log-view-minor-wrap (buf f)
168 (let ((data (with-current-buffer buf
169 (cons
170 (cons (log-view-current-file)
171 (log-view-current-tag))
172 (when (ignore-errors (mark))
173 ;; `mark-active' is not provided by XEmacs :-(
174 (save-excursion
175 (goto-char (mark))
176 (cons (log-view-current-file)
177 (log-view-current-tag))))))))
178 (let ((cvs-branch-prefix (cdar data))
179 (cvs-secondary-branch-prefix (and (cdar data) (cddr data)))
180 (cvs-minor-current-files
181 (cons (caar data)
182 (when (and (cadr data) (not (equal (caar data) (cadr data))))
183 (list (cadr data)))))
184 ;; FIXME: I need to force because the fileinfos are UNKNOWN
185 (cvs-force-command "/F"))
186 (funcall f))))
187
188(provide 'log-view)
189;;; log-view.el ends here
diff --git a/lisp/pcvs-defs.el b/lisp/pcvs-defs.el
new file mode 100644
index 00000000000..1a7ea9a5173
--- /dev/null
+++ b/lisp/pcvs-defs.el
@@ -0,0 +1,501 @@
1;;; pcvs-defs.el --- variable definitions for PCL-CVS
2
3;; Copyright (C) 1991-2000 Free Software Foundation, Inc.
4
5;; Author: Stefan Monnier <monnier@cs.yale.edu>
6;; Keywords: pcl-cvs
7;; Version: $Name: $
8;; Revision: $Id: pcl-cvs-defs.el,v 1.27 2000/03/03 20:58:09 monnier Exp $
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
30;;; Code:
31
32(defconst pcl-cvs-version "$Name: $")
33
34(eval-when-compile (require 'cl))
35(require 'pcvs-util)
36
37;;;; -------------------------------------------------------
38;;;; START OF THINGS TO CHECK WHEN INSTALLING
39
40(defvar cvs-program "cvs"
41 "*Name or full path of the cvs executable.")
42
43(defvar cvs-version
44 (ignore-errors
45 (with-temp-buffer
46 (call-process "cvs" nil t nil "-v")
47 (goto-char (point-min))
48 (when (re-search-forward "(CVS) \\([0-9]+\\)\\.\\([0-9]+\\)" nil t)
49 (cons (string-to-number (match-string 1))
50 (string-to-number (match-string 2))))))
51 "*Version of `cvs' installed on your system.
52It must be in the (MAJOR . MINOR) format.")
53
54;; FIXME: this is only used by cvs-mode-diff-backup
55(defvar cvs-diff-program (or (and (boundp 'diff-command) diff-command) "diff")
56 "*Name or full path of the best diff program you've got.
57NOTE: there are some nasty bugs in the context diff variants of some vendor
58versions, such as the one in SunOS-4.")
59
60;;;; END OF THINGS TO CHECK WHEN INSTALLING
61;;;; --------------------------------------------------------
62
63;;;;
64;;;; User configuration variables:
65;;;;
66;;;; NOTE: these should be set in your ~/.emacs (or site-lisp/default.el) file.
67;;;;
68
69(defgroup pcl-cvs nil
70 "Special support for the CVS versioning system."
71 :group 'tools
72 :prefix "cvs-")
73
74;;
75;; cvsrc options
76;;
77
78(defcustom cvs-cvsrc-file "~/.cvsrc"
79 "Path to your cvsrc file."
80 :group 'pcl-cvs
81 :type '(file))
82
83(defvar cvs-shared-start 4
84 "Index of the first shared flag.
85If set to 4, for instance, a numeric argument smaller than 4 will
86select a non-shared flag, while a numeric argument greater than 3
87will select a shared-flag.")
88
89(defvar cvs-shared-flags (make-list cvs-shared-start nil)
90 "List of flags whose settings is shared among several commands.")
91
92(defvar cvs-cvsroot nil
93 "*Specifies where the (current) cvs master repository is.
94Overrides the environment variable $CVSROOT by sending \" -d dir\" to
95all CVS commands. This switch is useful if you have multiple CVS
96repositories. It can be set interactively with \\[cvs-change-cvsroot.]
97There is no need to set this if $CVSROOT is set to a correct value.")
98
99(defcustom cvs-auto-remove-handled nil
100 "*If up-to-date files should be acknowledged automatically.
101If T, they will be removed from the *cvs* buffer after every command.
102If DELAYED, they will be removed from the *cvs* buffer before every command.
103If STATUS, they will only be removed after a `cvs-mode-status' command.
104Else, they will never be automatically removed from the *cvs* buffer."
105 :group 'pcl-cvs
106 :type '(choice (const nil) (const status) (const delayed) (const t)))
107
108(defcustom cvs-auto-remove-directories 'handled
109 "*If ALL, directory entries will never be shown.
110If HANLDED, only non-handled directories will be shown.
111If EMPTY, only non-empty directories will be shown."
112 :group 'pcl-cvs
113 :type '(choice (const :tag "No" nil) (const all) (const handled) (const empty)))
114
115(defcustom cvs-auto-revert t
116 "*Non-nil if changed files should automatically be reverted."
117 :group 'pcl-cvs
118 :type '(boolean))
119
120(defcustom cvs-sort-ignore-file t
121 "*Non-nil if `cvs-mode-ignore' should sort the .cvsignore automatically."
122 :group 'pcl-cvs
123 :type '(boolean))
124
125(defcustom cvs-force-dir-tag t
126 "*If non-nil, tagging can only be applied to directories.
127Tagging should generally be applied a directory at a time, but sometimes it is
128useful to be able to tag a single file. The normal way to do that is to use
129`cvs-mode-force-command' so as to temporarily override the restrictions,"
130 :group 'pcl-cvs
131 :type '(boolean))
132
133(defcustom cvs-default-ignore-marks nil
134 "*Non-nil if cvs mode commands should ignore any marked files.
135Normally they run on the files that are marked (with `cvs-mode-mark'),
136or the file under the cursor if no files are marked. If this variable
137is set to a non-nil value they will by default run on the file on the
138current line. See also `cvs-ignore-marks'"
139 :group 'pcl-cvs
140 :type '(boolean))
141
142(defvar cvs-diff-ignore-marks t
143 "Obsolete variable: use cvs-ignore-marks instead.")
144
145(defcustom cvs-invert-ignore-marks
146 (let ((l ()))
147 (unless (equal cvs-diff-ignore-marks cvs-default-ignore-marks)
148 (push "diff" l))
149 (when (and cvs-force-dir-tag (not cvs-default-ignore-marks))
150 (push "tag" l))
151 l)
152 "*List of cvs commands that invert the default ignore-mark behavior.
153Commands in this set will use the opposite default from the one set
154in `cvs-default-ignore-marks'."
155 :group 'pcl-cvs
156 :type '(set (const "diff")
157 (const "tag")
158 (const "ignore")))
159
160(defcustom cvs-confirm-removals t
161 "*Ask for confirmation before removing files.
162Non-nil means that PCL-CVS will ask confirmation before removing files
163except for files whose content can readily be recovered from the repository.
164A value of LIST means that the list of files to be deleted will be
165displayed when asking for confirmation."
166 :group 'pcl-cvs
167 :type '(choice (const list)
168 (const t)
169 (const nil)))
170
171(defcustom cvs-add-default-message nil
172 "*Default message to use when adding files.
173If set to NIL, `cvs-mode-add' will always prompt for a message."
174 :group 'pcl-cvs
175 :type '(choice (const :tag "Prompt" nil)
176 (string)))
177
178(defvar cvs-diff-buffer-name "*cvs-diff*"
179 "Obsolete variable: use `cvs-buffer-name-alist' instead.")
180
181(defcustom cvs-find-file-and-jump t
182 "Jump to the modified area when finding a file.
183If non-nil, `cvs-mode-file-file' will place the cursor at the beginning of
184the modified area. If the file is not locally modified, this will obviously
185have no effect."
186 :group 'pcl-cvs
187 :type '(boolean))
188
189(defcustom cvs-buffer-name-alist
190 '(("diff" cvs-diff-buffer-name diff-mode)
191 ("status" "*cvs-info*" cvs-status-mode)
192 ("tree" (format "*cvs-%s*" cmd) cvs-status-mode)
193 ("message" "*cvs-commit*" nil log-edit)
194 ("log" "*cvs-info*" log-view-mode))
195 "*Buffer name and mode to be used for each command.
196This is a list of elements of the form
197
198 (CMD BUFNAME MODE &optional POSTPROC)
199
200CMD is the name of the command.
201BUFNAME is an expression that should evaluate to a string used as
202 a buffer name. It can use the variable CMD if it wants to.
203MODE is the command to use to setup the buffer.
204POSTPROC is a function that should be executed when the command terminates
205
206The CMD used for `cvs-mode-commit' is \"message\". For that special
207 case, POSTPROC is called just after MODE with special arguments."
208 :group 'pcl-cvs
209 :type '(repeat
210 (list (choice (const "diff")
211 (const "status")
212 (const "tree")
213 (const "message")
214 (const "log")
215 (string))
216 (choice (const "*vc-diff*")
217 (const "*cvs-info*")
218 (const "*cvs-commit*")
219 (const (expand-file-name "*cvs-commit*"))
220 (const (format "*cvs-%s*" cmd))
221 (const (expand-file-name (format "*cvs-%s*" cmd)))
222 (sexp :value "my-cvs-info-buffer")
223 (const nil))
224 (choice (function-item diff-mode)
225 (function-item cvs-edit-mode)
226 (function-item cvs-status-mode)
227 function
228 (const nil))
229 (set :inline t
230 (choice (function-item cvs-status-cvstrees)
231 (function-item cvs-status-trees)
232 function)))))
233
234(defvar cvs-buffer-name '(expand-file-name "*cvs*" dir) ;; "*cvs*"
235 "Name of the cvs buffer.
236This expression will be evaluated in an environment where DIR is set to
237the directory name of the cvs buffer.")
238
239(defvar cvs-temp-buffer-name '(expand-file-name " *cvs-tmp*" dir)
240 "*Name of the cvs temporary buffer.
241Output from cvs is placed here for asynchronous commands.")
242
243(defcustom cvs-idiff-imerge-handlers
244 (if (fboundp 'ediff)
245 '(cvs-ediff-diff . cvs-ediff-merge)
246 '(cvs-emerge-diff . cvs-emerge-merge))
247 "*Pair of functions to be used for resp. diff'ing and merg'ing interactively."
248 :group 'pcl-cvs
249 :type '(choice (const :tag "Ediff" (cvs-ediff-diff . cvs-ediff-merge))
250 (const :tag "Emerge" (cvs-emerge-diff . cvs-emerge-merge))))
251
252(defvar pcl-cvs-load-hook nil
253 "Run after loading pcl-cvs.")
254
255(defvar cvs-mode-hook nil
256 "Run after `cvs-mode' was setup.")
257
258
259;;;;
260;;;; Internal variables, used in the process buffer.
261;;;;
262
263(defvar cvs-postprocess nil
264 "(Buffer local) what to do once the process exits.")
265
266;;;;
267;;;; Internal variables for the *cvs* buffer.
268;;;;
269
270(defcustom cvs-reuse-cvs-buffer 'subdir
271 "When to reuse an existing cvs buffer.
272Alternatives are:
273 CURRENT: just reuse the current buffer if it is a cvs buffer
274 SAMEDIR: reuse any cvs buffer displaying the same directory
275 SUBDIR: or reuse any cvs buffer displaying any sub- or super- directory
276 ALWAYS: reuse any cvs buffer."
277 :group 'pcl-cvs
278 :type '(choice (const always) (const subdir) (const samedir) (const current)))
279
280(defvar cvs-temp-buffer nil
281 "(Buffer local) The temporary buffer associated with this *cvs* buffer.")
282
283(defvar cvs-lock-file nil
284 "Full path to a lock file that CVS is waiting for (or was waiting for).
285This variable is buffer local and only used in the *cvs* buffer.")
286
287(defvar cvs-lock-file-regexp "^#cvs\\.\\([trw]fl\\.[-.a-z0-9]+\\|lock\\)\\'"
288 "Regexp matching the possible names of locks in the CVS repository.")
289
290(defconst cvs-cursor-column 22
291 "Column to position cursor in in `cvs-mode'.")
292
293;;;;
294;;;; Global internal variables
295;;;;
296
297(defconst cvs-startup-message
298 (concat "PCL-CVS release " pcl-cvs-version)
299 "*Startup message for CVS.")
300
301(defconst cvs-vendor-branch "1.1.1"
302 "The default branch used by CVS for vendor code.")
303
304(defvar cvs-menu
305 '("CVS"
306 ["Open File.." cvs-mode-find-file t]
307 [" ..Other Window" cvs-mode-find-file-other-window t]
308 ["Interactive Merge" cvs-mode-imerge t]
309 ["Interactive Diff" cvs-mode-idiff t]
310 ["View Diff" cvs-mode-diff (cvs-enabledp 'diff)]
311 ["Diff with Vendor" cvs-mode-diff-vendor t]
312 ["Diff with Backup" cvs-mode-diff-backup t]
313 ["View Log" cvs-mode-log t]
314 ["View Status" cvs-mode-status t]
315 "----"
316 ["Update" cvs-mode-update (cvs-enabledp 'update)]
317 ["Re-Examine" cvs-mode-examine t]
318 ["Commit" cvs-mode-commit-setup (cvs-enabledp 'commit)]
319 ["Undo Changes" cvs-mode-undo (cvs-enabledp 'undo)]
320 ["Add" cvs-mode-add (cvs-enabledp 'add)]
321 ["Remove" cvs-mode-remove (cvs-enabledp 'remove)]
322 ["Ignore" cvs-mode-ignore (cvs-enabledp 'ignore)]
323 ["Add ChangeLog" cvs-mode-add-change-log-entry-other-window t]
324 "----"
325 ["Mark All" cvs-mode-mark-all-files t]
326 ["Unmark All" cvs-mode-unmark-all-files t]
327 ["Hide Handled" cvs-mode-remove-handled t]
328 "----"
329 ;; ["Update Directory" cvs-update t]
330 ;; ["Examine Directory" cvs-examine t]
331 ;; ["Status Directory" cvs-status t]
332 ;; ["Checkout Module" cvs-checkout t]
333 ;; "----"
334 ["Quit" cvs-mode-quit t]
335 ))
336
337(easy-mmode-defmap cvs-mode-diff-map
338 '(("=" . cvs-mode-diff)
339 ("b" . cvs-mode-diff-backup)
340 ("2" . cvs-mode-idiff-other)
341 ("h" . cvs-mode-diff-head)
342 ("v" . cvs-mode-diff-vendor)
343 ("?" . cvs-mode-diff-help)
344 ("e" . cvs-mode-idiff)
345 ("E" . cvs-mode-imerge))
346 "Keymap for diff-related operations in `cvs-mode'.")
347
348(easy-mmode-defmap cvs-mode-map
349 ;;(define-prefix-command 'cvs-mode-map-diff-prefix)
350 ;;(define-prefix-command 'cvs-mode-map-control-c-prefix)
351 `(;; simulate `suppress-keymap'
352 (self-insert-command . undefined)
353 (("0" "1" "2" "3" "4" "5" "6" "7" "8" "9") . digit-argument)
354 ("-" . negative-argument)
355 ;; various
356 (undo . cvs-mode-undo)
357 ("?" . cvs-help)
358 ("h" . cvs-help)
359 ("q" . cvs-bury-buffer)
360 ;;("Q" . kill-buffer)
361 ("F" . cvs-mode-set-flags)
362 ("\M-f" . cvs-mode-force-command)
363 ("\C-c\C-c" . cvs-mode-kill-process)
364 ;; marking
365 ("m" . cvs-mode-mark)
366 ("M" . cvs-mode-mark-all-files)
367 ("u" . cvs-mode-unmark)
368 ("\C-?". cvs-mode-unmark-up)
369 ("%" . cvs-mode-mark-matching-files)
370 ("T" . cvs-mode-toggle-marks)
371 ("\M-\C-?" . cvs-mode-unmark-all-files)
372 ;; navigation keys
373 (" " . cvs-mode-next-line)
374 ("n" . cvs-mode-next-line)
375 ("p" . cvs-mode-previous-line)
376 ;; M- keys are usually those that operate on modules
377 ;;("\M-C". cvs-mode-rcs2log) ; i.e. "Create a ChangeLog"
378 ;;("\M-t". cvs-rtag)
379 ;;("\M-l". cvs-rlog)
380 ("\M-c". cvs-checkout)
381 ("\M-e". cvs-examine)
382 ("g" . cvs-mode-revert-buffer)
383 ("\M-u". cvs-update)
384 ("\M-s". cvs-status)
385 ;; diff commands
386 ("=" . cvs-mode-diff)
387 ("d" . ,cvs-mode-diff-map)
388 ;; keys that operate on individual files
389 ("\C-k". cvs-mode-acknowledge)
390 ("A" . cvs-mode-add-change-log-entry-other-window)
391 ;;("B" . cvs-mode-byte-compile-files)
392 ("C" . cvs-mode-commit-setup)
393 ("O" . cvs-mode-update)
394 ("U" . cvs-mode-undo)
395 ("I" . cvs-mode-insert)
396 ("a" . cvs-mode-add)
397 ("b" . cvs-set-branch-prefix)
398 ("B" . cvs-set-secondary-branch-prefix)
399 ("c" . cvs-mode-commit)
400 ("e" . cvs-mode-examine)
401 ("f" . cvs-mode-find-file)
402 ("i" . cvs-mode-ignore)
403 ("l" . cvs-mode-log)
404 ("o" . cvs-mode-find-file-other-window)
405 ("r" . cvs-mode-remove)
406 ("s" . cvs-mode-status)
407 ("t" . cvs-mode-tag)
408 ;;("v" . cvs-mode-diff-vendor)
409 ("x" . cvs-mode-remove-handled)
410 ;; cvstree bindings
411 ("+" . cvs-mode-tree)
412 ;; mouse bindings
413 ([(down-mouse-3)] . cvs-menu)
414 ;; Emacs-21 toolbar
415 ;;([tool-bar item1] . (menu-item "Examine" cvs-examine :image (image :file "/usr/share/icons/xpaint.xpm" :type xpm)))
416 ;;([tool-bar item2] . (menu-item "Update" cvs-update :image (image :file "/usr/share/icons/mail1.xpm" :type xpm)))
417 )
418 "Keymap for `cvs-mode'."
419 :dense t)
420
421(fset 'cvs-mode-map cvs-mode-map)
422
423;; add the cvs-menu to the map so it's added whenever we are in cvs-mode
424(when (ignore-errors (require 'easymenu))
425 (easy-menu-define cvs-menu-map
426 cvs-mode-map
427 "Menu used in cvs-mode."
428 cvs-menu))
429
430;;;;
431;;;; CVS-Minor mode
432;;;;
433
434(defcustom cvs-minor-mode-prefix "\C-xc"
435 "Prefix key for the `cvs-mode' bindings in `cvs-minor-mode'."
436 :group 'pcl-cvs)
437
438(easy-mmode-defmap cvs-minor-mode-map
439 `((,cvs-minor-mode-prefix . cvs-mode-map))
440 "Keymap for `cvs-minor-mode', used in buffers related to pcl-cvs.")
441
442(defvar cvs-buffer nil
443 "(Buffer local) The *cvs* buffer associated with this buffer.")
444(put 'cvs-buffer 'permanent-local t)
445;;(make-variable-buffer-local 'cvs-buffer)
446
447(defvar cvs-minor-wrap-function nil
448 "Function to call when switching to the *cvs* buffer.
449Takes two arguments:
450- a *cvs* buffer.
451- a zero-arg function which is guaranteed not to switch buffer.
452It is expected to call the function.")
453;;(make-variable-buffer-local 'cvs-minor-wrap-function)
454
455(defvar cvs-minor-current-files)
456;;"Current files in a `cvs-minor-mode' buffer."
457;; This should stay `void' because we want to be able to tell the difference
458;; between an empty list and no list at all.
459
460(defconst cvs-pcl-cvs-dirchange-re "^pcl-cvs: descending directory \\(.*\\)$")
461
462;;;;
463;;;;
464;;;;
465
466;;;###autoload
467(if (progn (condition-case () (require 'easymenu) (error nil))
468 (fboundp 'easy-menu-add-item))
469 (easy-menu-add-item nil '("tools")
470 '("PCL CVS"
471 ["Update Directory" cvs-update t]
472 ["Examine Directory" cvs-examine t]
473 ["Status Directory" cvs-status t]
474 ["Checkout Module" cvs-checkout t]) "vc"))
475
476
477;; cvs-1.10 and above can take file arguments in other directories
478;; while others need to be executed once per directory
479(defvar cvs-execute-single-dir
480 (if (and (consp cvs-version)
481 (or (>= (cdr cvs-version) 10) (> (car cvs-version) 1)))
482 '("status")
483 t)
484 "Whether cvs commands should be executed a directory at a time.
485If a list, specifies for which commands the single-dir mode should be used.
486If T, single-dir mode should be used for all operations.
487
488CVS versions before 1.10 did not allow passing them arguments in different
489directories, so pcl-cvs checks what version you're using to determine
490whether to use the new feature or not.
491Sadly, even with a new cvs executable, if you connect to an older cvs server
492\(typically a cvs-1.9 on the server), the old restriction applies. In such
493a case the sanity check made by pcl-cvs fails and you will have to manually
494set this variable to T (until the cvs server is upgraded).
495When the above problem occurs, pcl-cvs should (hopefully) catch cvs' error
496message and replace it with a message tell you to change this variable.")
497
498;;
499(provide 'pcvs-defs)
500
501;;; pcl-cvs-defs.el ends here
diff --git a/lisp/pcvs-info.el b/lisp/pcvs-info.el
new file mode 100644
index 00000000000..51b791e8ae3
--- /dev/null
+++ b/lisp/pcvs-info.el
@@ -0,0 +1,455 @@
1;;; pcvs-info.el --- Internal representation of a fileinfo entry
2
3;; Copyright (C) 1991-2000 Free Software Foundation, Inc.
4
5;; Author: Stefan Monnier <monnier@cs.yale.edu>
6;; Keywords: pcl-cvs
7;; Version: $Name: $
8;; Revision: $Id: pcl-cvs-info.el,v 1.28 2000/03/05 21:32:21 monnier Exp $
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;; The cvs-fileinfo data structure:
30;;
31;; When the `cvs update' is ready we parse the output. Every file
32;; that is affected in some way is added to the cookie collection as
33;; a "fileinfo" (as defined below in cvs-create-fileinfo).
34
35;;; Code:
36
37(eval-when-compile (require 'cl))
38(require 'pcvs-util)
39;;(require 'pcvs-defs)
40
41;;;;
42;;;; config variables
43;;;;
44
45(defcustom cvs-display-full-path t
46 "*Specifies how the filenames should look like in the listing.
47If t, their full path name will be displayed, else only the filename."
48 :group 'pcl-cvs
49 :type '(boolean))
50
51(defvar global-font-lock-mode)
52(defvar font-lock-auto-fontify)
53(defcustom cvs-highlight
54 (or (and (boundp 'font-lock-auto-fontify) font-lock-auto-fontify)
55 (and (boundp 'global-font-lock-mode) global-font-lock-mode))
56 "*Whether to use text highlighting (à la font-lock) or not."
57 :group 'pcl-cvs
58 :type '(boolean))
59
60(defcustom cvs-allow-dir-commit nil
61 "*Allow `cvs-mode-commit' on directories.
62If you commit without any marked file and with the cursor positioned
63on a directory entry, cvs would commit the whole directory. This seems
64to confuse some users sometimes."
65 :group 'pcl-cvs
66 :type '(boolean))
67
68
69;;;;
70;;;; Faces for fontification
71;;;;
72
73(defface cvs-header-face
74 '((((class color) (background dark))
75 (:foreground "lightyellow" :bold t))
76 (((class color) (background light))
77 (:foreground "blue4" :bold t))
78 (t (:bold t)))
79 "PCL-CVS face used to highlight directory changes."
80 :group 'pcl-cvs)
81
82(defface cvs-filename-face
83 '((((class color) (background dark))
84 (:foreground "lightblue"))
85 (((class color) (background light))
86 (:foreground "blue4"))
87 (t ()))
88 "PCL-CVS face used to highlight file names."
89 :group 'pcl-cvs)
90
91(defface cvs-unknown-face
92 '((((class color) (background dark))
93 (:foreground "red"))
94 (((class color) (background light))
95 (:foreground "red"))
96 (t (:italic t)))
97 "PCL-CVS face used to highlight unknown file status."
98 :group 'pcl-cvs)
99
100(defface cvs-handled-face
101 '((((class color) (background dark))
102 (:foreground "pink"))
103 (((class color) (background light))
104 (:foreground "pink"))
105 (t ()))
106 "PCL-CVS face used to highlight handled file status."
107 :group 'pcl-cvs)
108
109(defface cvs-need-action-face
110 '((((class color) (background dark))
111 (:foreground "orange"))
112 (((class color) (background light))
113 (:foreground "orange"))
114 (t (:italic t)))
115 "PCL-CVS face used to highlight status of files needing action."
116 :group 'pcl-cvs)
117
118(defface cvs-marked-face
119 '((((class color) (background dark))
120 (:foreground "green" :bold t))
121 (((class color) (background light))
122 (:foreground "green3" :bold t))
123 (t (:bold t)))
124 "PCL-CVS face used to highlight marked file indicator."
125 :group 'pcl-cvs)
126
127(defface cvs-msg-face
128 '((t (:italic t)))
129 "PCL-CVS face used to highlight CVS messages."
130 :group 'pcl-cvs)
131
132
133;; There is normally no need to alter the following variable, but if
134;; your site has installed CVS in a non-standard way you might have
135;; to change it.
136
137(defvar cvs-bakprefix ".#"
138 "The prefix that CVS prepends to files when rcsmerge'ing.")
139
140(easy-mmode-defmap cvs-filename-map
141 '(([(mouse-2)] . cvs-mode-find-file))
142 "Local keymap for text properties of file names"
143 :inherit 'cvs-mode-map)
144
145(easy-mmode-defmap cvs-status-map
146 '(([(mouse-2)] . cvs-mouse-toggle-mark))
147 "Local keymap for text properties of status"
148 :inherit 'cvs-mode-map)
149
150(easy-mmode-defmap cvs-dirname-map
151 '(([(mouse-2)] . cvs-mode-find-file))
152 "Local keymap for text properties of directory names"
153 :inherit 'cvs-mode-map)
154
155;; Constructor:
156
157(defstruct (cvs-fileinfo
158 (:constructor nil)
159 (:copier nil)
160 (:constructor -cvs-create-fileinfo (type dir file full-log
161 &key marked subtype
162 merge
163 base-rev
164 head-rev))
165 (:conc-name cvs-fileinfo->))
166 marked ;; t/nil.
167 type ;; See below
168 subtype ;; See below
169 dir ;; Relative directory the file resides in.
170 ;; (concat dir file) should give a valid path.
171 file ;; The file name sans the directory.
172 base-rev ;; During status: This is the revision that the
173 ;; working file is based on.
174 head-rev ;; During status: This is the highest revision in
175 ;; the repository.
176 merge ;; A cons cell containing the (ancestor . head) revisions
177 ;; of the merge that resulted in the current file.
178 ;;removed ;; t if the file no longer exists.
179 full-log ;; The output from cvs, unparsed.
180 ;;mod-time ;; Not used.
181
182 ;; In addition to the above, the following values can be extracted:
183
184 ;; handled ;; t if this file doesn't require further action.
185 ;; full-path ;; The complete relative filename.
186 ;; pp-name ;; The printed file name
187 ;; backup-file;; For MERGED and CONFLICT files after a \"cvs update\",
188 ;; this is a full path to the backup file where the
189 ;; untouched version resides.
190
191 ;; The meaning of the type field:
192
193 ;; Value ---Used by--- Explanation
194 ;; update status
195 ;; NEED-UPDATE x file needs update
196 ;; MODIFIED x x modified by you, unchanged in repository
197 ;; MERGED x x successful merge
198 ;; ADDED x x added by you, not yet committed
199 ;; MISSING x rm'd, but not yet `cvs remove'd
200 ;; REMOVED x x removed by you, not yet committed
201 ;; NEED-MERGE x need merge
202 ;; CONFLICT x conflict when merging
203 ;; ;;MOD-CONFLICT x removed locally, changed in repository.
204 ;; DIRCHANGE x x A change of directory.
205 ;; UNKNOWN x An unknown file.
206 ;; UP-TO-DATE x The file is up-to-date.
207 ;; UPDATED x x file copied from repository
208 ;; PATCHED x x diff applied from repository
209 ;; COMMITTED x x cvs commit'd
210 ;; DEAD An entry that should be removed
211 ;; MESSAGE x x This is a special fileinfo that is used
212 ;; to display a text that should be in
213 ;; full-log."
214 ;; TEMP A temporary message that should be removed
215 ;; HEADER A message that should stick at the top of the display
216 ;; FOOTER A message that should stick at the bottom of the display
217 )
218(defun cvs-create-fileinfo (type dir file msg &rest keys)
219 (cvs-check-fileinfo (apply #'-cvs-create-fileinfo type dir file msg keys)))
220
221;; Fake selectors:
222
223(defun cvs-fileinfo->full-path (fileinfo)
224 "Return the full path for the file that is described in FILEINFO."
225 (let ((dir (cvs-fileinfo->dir fileinfo)))
226 (if (eq (cvs-fileinfo->type fileinfo) 'DIRCHANGE)
227 (if (string= dir "") "." (directory-file-name dir))
228 ;; Here, I use `concat' rather than `expand-file-name' because I want
229 ;; the resulting path to stay relative if `dir' is relative.
230 ;; I could also use `expand-file-name' with `default-directory = ""'
231 (concat dir (cvs-fileinfo->file fileinfo)))))
232
233(defun cvs-fileinfo->pp-name (fi)
234 "Return the filename of FI as it should be displayed."
235 (if cvs-display-full-path
236 (cvs-fileinfo->full-path fi)
237 (cvs-fileinfo->file fi)))
238
239(defun cvs-fileinfo->backup-file (fileinfo)
240 "Construct the file name of the backup file for FILEINFO."
241 (let* ((dir (cvs-fileinfo->dir fileinfo))
242 (file (cvs-fileinfo->file fileinfo))
243 (default-directory (file-name-as-directory (expand-file-name dir)))
244 (files (directory-files "." nil
245 (concat "^" (regexp-quote cvs-bakprefix)
246 (regexp-quote file) "\\.")))
247 bf)
248 (dolist (f files bf)
249 (when (and (file-readable-p f)
250 (or (null bf) (file-newer-than-file-p f bf)))
251 (setq bf (concat dir f))))))
252
253;; (defun cvs-fileinfo->handled (fileinfo)
254;; "Tell if this requires further action"
255;; (memq (cvs-fileinfo->type fileinfo) '(UP-TO-DATE DEAD)))
256
257
258;; Predicate:
259
260(defun boolp (x) (or (eq t x) (null x)))
261(defun cvs-check-fileinfo (fi)
262 "Check FI's conformance to some conventions."
263 (let ((check 'none)
264 (type (cvs-fileinfo->type fi))
265 (subtype (cvs-fileinfo->subtype fi))
266 (marked (cvs-fileinfo->marked fi))
267 (dir (cvs-fileinfo->dir fi))
268 (file (cvs-fileinfo->file fi))
269 (base-rev (cvs-fileinfo->base-rev fi))
270 (head-rev (cvs-fileinfo->head-rev fi))
271 (full-log (cvs-fileinfo->full-log fi)))
272 (if (and (setq check 'marked) (boolp marked)
273 (setq check 'base-rev) (or (null base-rev) (stringp base-rev))
274 (setq check 'head-rev) (or (null head-rev) (stringp head-rev))
275 (setq check 'full-log) (stringp full-log)
276 (setq check 'dir)
277 (and (stringp dir)
278 (not (file-name-absolute-p dir))
279 (or (string= dir "")
280 (string= dir (file-name-as-directory dir))))
281 (setq check 'file)
282 (and (stringp file)
283 (string= file (file-name-nondirectory file)))
284 (setq check 'type) (symbolp type)
285 (setq check 'consistency)
286 (case type
287 (DIRCHANGE (and (null subtype) (string= "." file)))
288 ((NEED-UPDATE ADDED MISSING DEAD MODIFIED MESSAGE UP-TO-DATE
289 REMOVED NEED-MERGE CONFLICT UNKNOWN MESSAGE)
290 t)))
291 fi
292 (error "Invalid :%s in cvs-fileinfo %s" check fi))))
293
294
295;;;;
296;;;; State table to indicate what you can do when.
297;;;;
298
299(defconst cvs-states
300 `((NEED-UPDATE update diff)
301 (UP-TO-DATE update nil remove diff safe-rm revert)
302 (MODIFIED update commit undo remove diff merge diff-base)
303 (ADDED update commit remove)
304 (MISSING remove undo update safe-rm revert)
305 (REMOVED commit add undo safe-rm)
306 (NEED-MERGE update undo diff diff-base)
307 (CONFLICT merge remove undo commit diff diff-base)
308 (DIRCHANGE remove update diff ,(if cvs-allow-dir-commit 'commit) tag)
309 (UNKNOWN ignore add remove)
310 (DEAD )
311 (MESSAGE))
312 "Fileinfo state descriptions for pcl-cvs.
313This is an assoc list. Each element consists of (STATE . FUNS)
314- STATE (described in `cvs-create-fileinfo') is the key
315- FUNS is the list of applicable operations.
316 The first one (if any) should be the \"default\" action.
317Most of the actions have the obvious meaning.
318`safe-rm' indicates that the file can be removed without losing
319 any information.")
320
321;;;;
322;;;; Utility functions
323;;;;
324
325;;----------
326(defun cvs-applicable-p (fi-or-type func)
327 "Check if FUNC is applicable to FI-OR-TYPE.
328If FUNC is nil, always return t.
329FI-OR-TYPE can either be a symbol (a fileinfo-type) or a fileinfo."
330 (let ((type (if (symbolp fi-or-type) fi-or-type
331 (cvs-fileinfo->type fi-or-type))))
332 (and (not (eq type 'MESSAGE))
333 (eq (car (memq func (cdr (assq type cvs-states)))) func))))
334
335;; (defun cvs-default-action (fileinfo)
336;; "Return some kind of \"default\" action to be performed."
337;; (second (assq (cvs-fileinfo->type fileinfo) cvs-states)))
338
339;; fileinfo pretty-printers:
340
341(defun cvs-add-face (str face &optional keymap)
342 (when cvs-highlight
343 (add-text-properties 0 (length str)
344 (list* 'face face
345 (when keymap
346 (list 'mouse-face 'highlight
347 'local-map keymap)))
348 str))
349 str)
350
351;;----------
352(defun cvs-fileinfo-pp (fileinfo)
353 "Pretty print FILEINFO. Insert a printed representation in current buffer.
354For use by the cookie package."
355 (cvs-check-fileinfo fileinfo)
356 (let ((type (cvs-fileinfo->type fileinfo))
357 (subtype (cvs-fileinfo->subtype fileinfo)))
358 (insert
359 (case type
360 (DIRCHANGE (concat "In directory "
361 (cvs-add-face (cvs-fileinfo->full-path fileinfo)
362 'cvs-header-face cvs-dirname-map)
363 ":"))
364 (MESSAGE
365 (if (memq (cvs-fileinfo->subtype fileinfo) '(FOOTER HEADER))
366 (cvs-fileinfo->full-log fileinfo)
367 (cvs-add-face (format "Message: %s" (cvs-fileinfo->full-log fileinfo))
368 'cvs-msg-face)))
369 (t
370 (let* ((status (if (cvs-fileinfo->marked fileinfo)
371 (cvs-add-face "*" 'cvs-marked-face)
372 " "))
373 (file (cvs-add-face (cvs-fileinfo->pp-name fileinfo)
374 'cvs-filename-face cvs-filename-map))
375 (base (or (cvs-fileinfo->base-rev fileinfo) ""))
376 (head (cvs-fileinfo->head-rev fileinfo))
377 (type
378 (let ((str (case type
379 ;;(MOD-CONFLICT "Not Removed")
380 (DEAD "")
381 (t (capitalize (symbol-name type)))))
382 (face (case type
383 (UP-TO-DATE 'cvs-handled-face)
384 (UNKNOWN 'cvs-unknown-face)
385 (t 'cvs-need-action-face))))
386 (cvs-add-face str face cvs-status-map)))
387 (side (or
388 ;; maybe a subtype
389 (when subtype (downcase (symbol-name subtype)))
390 ;; or the head-rev
391 (when (and head (not (string= head base))) head)
392 ;; or nothing
393 ""))
394 ;; (action (cvs-add-face (case (cvs-default-action fileinfo)
395 ;; (commit "com")
396 ;; (update "upd")
397 ;; (undo "udo")
398 ;; (t " "))
399 ;; 'cvs-action-face
400 ;; cvs-action-map))
401 )
402 (concat (cvs-string-fill side 11) " "
403 status " "
404 (cvs-string-fill type 11) " "
405 ;; action " "
406 (cvs-string-fill base 11) " "
407 file)))))))
408;; it seems that `format' removes text-properties. Too bad!
409;; (format "%-11s %s %-11s %-11s %s"
410;; side status type base file)))))))
411
412
413(defun cvs-fileinfo-update (fi fi-new)
414 "Update FI with the information provided in FI-NEW."
415 (let ((type (cvs-fileinfo->type fi-new))
416 (merge (cvs-fileinfo->merge fi-new)))
417 (setf (cvs-fileinfo->type fi) type)
418 (setf (cvs-fileinfo->subtype fi) (cvs-fileinfo->subtype fi-new))
419 (setf (cvs-fileinfo->full-log fi) (cvs-fileinfo->full-log fi-new))
420 (setf (cvs-fileinfo->base-rev fi) (cvs-fileinfo->base-rev fi-new))
421 (setf (cvs-fileinfo->head-rev fi) (cvs-fileinfo->head-rev fi-new))
422 (cond
423 (merge (setf (cvs-fileinfo->merge fi) merge))
424 ((memq type '(UP-TO-DATE NEED-UPDATE))
425 (setf (cvs-fileinfo->merge fi) nil)))))
426
427;;----------
428(defun cvs-fileinfo< (a b)
429 "Compare fileinfo A with fileinfo B and return t if A is `less'.
430The ordering defined by this function is such that directories are
431sorted alphabetically, and inside every directory the DIRCHANGE
432fileinfo will appear first, followed by all files (alphabetically)."
433 (let ((subtypea (cvs-fileinfo->subtype a))
434 (subtypeb (cvs-fileinfo->subtype b)))
435 (cond
436 ;; keep header and footer where they belong. Note: the order is important
437 ((eq subtypeb 'HEADER) nil)
438 ((eq subtypea 'HEADER) t)
439 ((eq subtypea 'FOOTER) nil)
440 ((eq subtypeb 'FOOTER) t)
441
442 ;; Sort according to directories.
443 ((string< (cvs-fileinfo->dir a) (cvs-fileinfo->dir b)) t)
444 ((not (string= (cvs-fileinfo->dir a) (cvs-fileinfo->dir b))) nil)
445
446 ;; The DIRCHANGE entry is always first within the directory.
447 ((eq (cvs-fileinfo->type b) 'DIRCHANGE) nil)
448 ((eq (cvs-fileinfo->type a) 'DIRCHANGE) t)
449
450 ;; All files are sorted by file name.
451 ((string< (cvs-fileinfo->file a) (cvs-fileinfo->file b))))))
452
453(provide 'pcvs-info)
454
455;;; pcl-cvs-info.el ends here
diff --git a/lisp/pcvs-parse.el b/lisp/pcvs-parse.el
new file mode 100644
index 00000000000..b65f8d2eb60
--- /dev/null
+++ b/lisp/pcvs-parse.el
@@ -0,0 +1,478 @@
1;;; pcvs-parse.el --- The CVS output parser
2
3;; Copyright (C) 1991-2000 Free Software Foundation, Inc.
4
5;; Author: Stefan Monnier <monnier@cs.yale.edu>
6;; Keywords: pcl-cvs
7;; Version: $Name: $
8;; Revision: $Id: pcl-cvs-parse.el,v 1.41 2000/03/05 21:32:21 monnier Exp $
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
30;;; Code:
31
32(eval-when-compile (require 'cl))
33
34(require 'pcvs-util)
35(require 'pcvs-info)
36
37;; imported from pcvs.el
38(defvar cvs-execute-single-dir)
39
40;; parse vars
41
42(defcustom cvs-update-prog-output-skip-regexp "$"
43 "*A regexp that matches the end of the output from all cvs update programs.
44That is, output from any programs that are run by CVS (by the flag -u
45in the `modules' file - see cvs(5)) when `cvs update' is performed should
46terminate with a line that this regexp matches. It is enough that
47some part of the line is matched.
48
49The default (a single $) fits programs without output."
50 :group 'pcl-cvs
51 :type '(regexp :value "$"))
52
53(defcustom cvs-parse-ignored-messages
54 '("Executing ssh-askpass to query the password.*$"
55 ".*Remote host denied X11 forwarding.*$")
56 "*A list of regexps matching messages that should be ignored by the parser.
57Each regexp should match a whole set of lines and should hence be terminated
58by `$'."
59 :group 'pcl-cvs
60 :type '(repeat regexp))
61
62;; a few more defvars just to shut up the compiler
63(defvar cvs-start)
64(defvar cvs-current-dir)
65(defvar cvs-current-subdir)
66(defvar dont-change-disc)
67
68;;;; The parser
69
70(defconst cvs-parse-known-commands
71 '("status" "add" "commit" "update" "remove" "checkout" "ci")
72 "List of CVS commands whose output is understood by the parser.")
73
74(defun cvs-parse-buffer (parse-spec dont-change-disc &optional subdir)
75 "Parse current buffer according to PARSE-SPEC.
76PARSE-SPEC is a function of no argument advancing the point and returning
77 either a fileinfo or t (if the matched text should be ignored) or
78 nil if it didn't match anything.
79DONT-CHANGE-DISC just indicates whether the command was changing the disc
80 or not (useful to tell the difference btween `cvs-examine' and `cvs-update'
81 ouytput.
82The path names should be interpreted as relative to SUBDIR (defaults
83 to the `default-directory').
84Return a list of collected entries, or t if an error occured."
85 (goto-char (point-min))
86 (let ((fileinfos ())
87 (cvs-current-dir "")
88 (case-fold-search nil)
89 (cvs-current-subdir (or subdir "")))
90 (while (not (or (eobp) (eq fileinfos t)))
91 (let ((ret (cvs-parse-run-table parse-spec)))
92 (cond
93 ;; it matched a known information message
94 ((cvs-fileinfo-p ret) (push ret fileinfos))
95 ;; it didn't match anything at all (impossible)
96 ((and (consp ret) (cvs-fileinfo-p (car ret)))
97 (setq fileinfos (append ret fileinfos)))
98 ((null ret) (setq fileinfos t))
99 ;; it matched something that should be ignored
100 (t nil))))
101 (nreverse fileinfos)))
102
103
104;; All those parsing macros/functions should return a success indicator
105(defsubst cvs-parse-msg () (buffer-substring cvs-start (1- (point))))
106
107;;(defsubst COLLECT (exp) (push exp *result*))
108;;(defsubst PROG (e) t)
109;;(defmacro SEQ (&rest seqs) (cons 'and seqs))
110
111(defmacro cvs-match (re &rest matches)
112 "Try to match RE and extract submatches.
113If RE matches, advance the point until the line after the match and
114then assign the variables as specified in MATCHES (via `setq')."
115 (cons 'cvs-do-match
116 (cons re (mapcar (lambda (match)
117 `(cons ',(first match) ,(second match)))
118 matches))))
119
120(defun cvs-do-match (re &rest matches)
121 "Internal function for the `cvs-match' macro.
122Match RE and if successful, execute MATCHES."
123 ;; Is it a match?
124 (when (looking-at re)
125 (goto-char (match-end 0))
126 ;; Skip the newline (unless we already are at the end of the buffer).
127 (when (and (eolp) (< (point) (point-max))) (forward-char))
128 ;; assign the matches
129 (dolist (match matches t)
130 (let ((val (cdr match)))
131 (set (car match) (if (integerp val) (match-string val) val))))))
132
133(defmacro cvs-or (&rest alts)
134 "Try each one of the ALTS alternatives until one matches."
135 `(let ((-cvs-parse-point (point)))
136 ,(cons 'or
137 (mapcar (lambda (es)
138 `(or ,es (ignore (goto-char -cvs-parse-point))))
139 alts))))
140(def-edebug-spec cvs-or t)
141
142;; This is how parser tables should be executed
143(defun cvs-parse-run-table (parse-spec)
144 "Run PARSE-SPEC and provide sensible default behavior."
145 (unless (bolp) (forward-line 1)) ;this should never be needed
146 (let ((cvs-start (point)))
147 (cvs-or
148 (funcall parse-spec)
149
150 (dolist (re cvs-parse-ignored-messages)
151 (when (cvs-match re) (return t)))
152
153 ;; This is a parse error. Create a message-type fileinfo.
154 (and
155 (cvs-match ".*$")
156 (cvs-create-fileinfo 'MESSAGE cvs-current-dir " "
157 (concat " Parser Error: '" (cvs-parse-msg) "'")
158 :subtype 'ERROR)))))
159
160
161(defun cvs-parsed-fileinfo (type path &optional directory &rest keys)
162 "Create a fileinfo.
163TYPE can either be a type symbol or a cons of the form (TYPE . SUBTYPE).
164PATH is the filename.
165DIRECTORY influences the way PATH is interpreted:
166- if it's a string, it denotes the directory in which PATH (which should then be
167 a plain file name with no directory component) resides.
168- if it's nil, the PATH should not be trusted: if it has a directory
169 component, use it, else, assume it is relative to the current directory.
170- else, the PATH should be trusted to be relative to the root
171 directory (i.e. if there is no directory component, it means the file
172 is inside the main directory).
173The remaining KEYS are passed directly to `cvs-create-fileinfo'."
174 (let ((dir directory)
175 (file path))
176 ;; only trust the directory if it's a string
177 (unless (stringp directory)
178 ;; else, if the directory is true, the path should be trusted
179 (setq dir (or (file-name-directory path) (if directory "")))
180 (setq file (file-name-nondirectory path)))
181
182 (let ((type (if (consp type) (car type) type))
183 (subtype (if (consp type) (cdr type))))
184 (when dir (setq cvs-current-dir dir))
185 (apply 'cvs-create-fileinfo type
186 (concat cvs-current-subdir (or dir cvs-current-dir))
187 file (cvs-parse-msg) :subtype subtype keys))))
188
189
190;;;; CVS Process Parser Tables:
191;;;;
192;;;; The table for status and update could actually be merged since they
193;;;; don't conflict. But they don't overlap much either.
194
195(defun cvs-parse-table ()
196 "Table of message objects for `cvs-parse-process'."
197 (let (c file dir path type base-rev subtype)
198 (cvs-or
199
200 (cvs-parse-status)
201 (cvs-parse-merge)
202 (cvs-parse-commit)
203
204 ;; this is not necessary because the fileinfo merging will remove
205 ;; such duplicate info and luckily the second info is the one we want.
206 ;; (and (cvs-match "M \\(.*\\)$" (path 1))
207 ;; (cvs-parse-merge path))
208
209 ;; Normal file state indicator.
210 (and
211 (cvs-match "\\([MARCUPNJ?]\\) \\(.*\\)$" (c 1) (path 2))
212 ;; M: The file is modified by the user, and untouched in the repository.
213 ;; A: The file is "cvs add"ed, but not "cvs ci"ed.
214 ;; R: The file is "cvs remove"ed, but not "cvs ci"ed.
215 ;; C: Conflict
216 ;; U: The file is copied from the repository.
217 ;; P: The file was patched from the repository.
218 ;; ?: Unknown file.
219 (let ((code (aref c 0)))
220 (cvs-parsed-fileinfo (case code
221 (?M 'MODIFIED)
222 (?A 'ADDED)
223 (?R 'REMOVED)
224 (?? 'UNKNOWN)
225 (?C 'CONFLICT) ;(if dont-change-disc 'NEED-MERGE
226 (?J 'NEED-MERGE) ;not supported by standard CVS
227 ((?U ?P)
228 (if dont-change-disc
229 'NEED-UPDATE
230 (cons 'UP-TO-DATE
231 (if (eq code ?U) 'UPDATED 'PATCHED)))))
232 path 'trust)))
233
234 (and
235 (cvs-match "pcl-cvs: descending directory \\(.*\\)$" (dir 1))
236 (setq cvs-current-subdir dir))
237
238 ;; A special cvs message
239 (and
240 (cvs-match "cvs[.ex]* [a-z]+: ")
241 (cvs-or
242
243 ;; CVS is descending a subdirectory
244 ;; (status says `examining' while update says `updating')
245 (and
246 (cvs-match "\\(Examining\\|Updating\\) \\(.*\\)$" (dir 2))
247 (let ((dir (if (string= "." dir) "" (file-name-as-directory dir))))
248 (cvs-parsed-fileinfo 'DIRCHANGE "." dir)))
249
250 ;; [-n update] A new (or pruned) directory appeared but isn't traversed
251 (and
252 (cvs-match "New directory `\\(.*\\)' -- ignored$" (dir 1))
253 (cvs-parsed-fileinfo 'MESSAGE " " (file-name-as-directory dir)))
254
255 ;; File removed, since it is removed (by third party) in repository.
256 (and
257 (cvs-or
258 (cvs-match "warning: \\(.*\\) is not (any longer) pertinent$" (file 1))
259 (cvs-match "\\(.*\\) is no longer in the repository$" (file 1)))
260 (cvs-parsed-fileinfo 'DEAD file))
261
262 ;; [add]
263 (and
264 (cvs-or
265 (cvs-match "scheduling file `\\(.*\\)' for addition.*$" (path 1))
266 (cvs-match "re-adding file \\(.*\\) (in place of .*)$" (path 1)))
267 (cvs-parsed-fileinfo 'ADDED path))
268
269 ;; [add] this will also show up as a `U <file>'
270 (and
271 (cvs-match "\\(.*\\), version \\(.*\\), resurrected$"
272 (path 1) (base-rev 2))
273 (cvs-parsed-fileinfo '(UP-TO-DATE . RESURRECTED) path nil
274 :base-rev base-rev))
275
276 ;; [remove]
277 (and
278 (cvs-match "removed `\\(.*\\)'$" (path 1))
279 (cvs-parsed-fileinfo 'DEAD path))
280
281 ;; [remove,merge]
282 (and
283 (cvs-match "scheduling `\\(.*\\)' for removal$" (file 1))
284 (cvs-parsed-fileinfo 'REMOVED file))
285
286 ;; [update] File removed by you, but not cvs rm'd
287 (and
288 (cvs-match "warning: \\(.*\\) was lost$" (path 1))
289 (cvs-match (concat "U " (regexp-quote path) "$"))
290 (cvs-parsed-fileinfo (if dont-change-disc
291 'MISSING
292 '(UP-TO-DATE . UPDATED))
293 path))
294
295 ;; Mode conflicts (rather than contents)
296 (and
297 (cvs-match "conflict: ")
298 (cvs-or
299 (cvs-match "removed \\(.*\\) was modified by second party$"
300 (path 1) (subtype 'REMOVED))
301 (cvs-match "\\(.*\\) created independently by second party$"
302 (path 1) (subtype 'ADDED))
303 (cvs-match "\\(.*\\) is modified but no longer in the repository$"
304 (path 1) (subtype 'MODIFIED)))
305 (cvs-match (concat "C " (regexp-quote path)))
306 (cvs-parsed-fileinfo (cons 'CONFLICT subtype) path))
307
308 ;; Messages that should be shown to the user
309 (and
310 (cvs-or
311 (cvs-match "move away \\(.*\\); it is in the way$" (file 1))
312 (cvs-match "warning: new-born \\(.*\\) has disappeared$" (file 1))
313 (cvs-match "sticky tag .* for file `\\(.*\\)' is not a branch$"
314 (file 1)))
315 (cvs-parsed-fileinfo 'MESSAGE file))
316
317 ;; File unknown.
318 (and (cvs-match "use `.+ add' to create an entry for \\(.*\\)$" (path 1))
319 (cvs-parsed-fileinfo 'UNKNOWN path))
320
321 ;; We use cvs-execute-multi-dir but cvs can't handle it
322 ;; Probably because the cvs-client can but the cvs-server can't
323 (and (cvs-match ".* files with '?/'? in their name.*$")
324 (not cvs-execute-single-dir)
325 (setq cvs-execute-single-dir t)
326 (cvs-create-fileinfo
327 'MESSAGE "" " "
328 "*** Add (setq cvs-execute-single-dir t) to your .emacs ***
329 See the FAQ file or the variable's documentation for more info."))
330
331 ;; Cvs waits for a lock. Ignored: already handled by the process filter
332 (cvs-match "\\[..:..:..\\] \\(waiting for\\|obtained\\) .*lock in .*$")
333 ;; File you removed still exists. Ignore (will be noted as removed).
334 (cvs-match ".* should be removed and is still there$")
335 ;; just a note
336 (cvs-match "use '.+ commit' to \\sw+ th\\sw+ files? permanently$")
337 ;; [add,status] followed by a more complete status description anyway
338 (cvs-match "nothing known about .*$")
339 ;; [update] problem with patch
340 (cvs-match "checksum failure after patch to .*; will refetch$")
341 (cvs-match "refetching unpatchable files$")
342 ;; [commit]
343 (cvs-match "Rebuilding administrative file database$")
344
345 ;; CVS is running a *info program.
346 (and
347 (cvs-match "Executing.*$")
348 ;; Skip by any output the program may generate to stdout.
349 ;; Note that pcl-cvs will get seriously confused if the
350 ;; program prints anything to stderr.
351 (re-search-forward cvs-update-prog-output-skip-regexp))))
352
353 (and
354 (cvs-match "cvs[.ex]* \\[[a-z]+ aborted\\]:.*$")
355 (cvs-parsed-fileinfo 'MESSAGE ""))
356
357 ;; sadly you can't do much with these since the path is in the repository
358 (cvs-match "Directory .* added to the repository$")
359 )))
360
361
362(defun cvs-parse-merge ()
363 (let (path base-rev head-rev handled type)
364 ;; A merge (maybe with a conflict).
365 (and
366 (cvs-match "RCS file: .*$")
367 ;; Squirrel away info about the files that were retrieved for merging
368 (cvs-match "retrieving revision \\([0-9.]+\\)$" (base-rev 1))
369 (cvs-match "retrieving revision \\([0-9.]+\\)$" (head-rev 1))
370 (cvs-match "Merging differences between [0-9.]+ and [0-9.]+ into \\(.*\\)$"
371 (path 1))
372
373 ;; eat up potential conflict warnings
374 (cvs-or (cvs-match "\\(rcs\\)?merge:?\\( warning\\)?: \\(overlaps\\|conflicts\\) \\(or other problems \\)?during merge$" (type 'CONFLICT)) t)
375 (cvs-or
376 (and
377 (cvs-match "cvs[.ex]* [a-z]+: ")
378 (cvs-or
379 (cvs-match "conflicts found in \\(.*\\)$" (path 1) (type 'CONFLICT))
380 (cvs-match "could not merge .*$")
381 (cvs-match "restoring \\(.*\\) from backup file .*$" (path 1))))
382 t)
383
384 ;; Is it a succesful merge?
385 ;; Figure out result of merging (ie, was there a conflict?)
386 (let ((qfile (regexp-quote path)))
387 (cvs-or
388 ;; Conflict
389 (and
390 (cvs-match (concat "C \\(.*" qfile "\\)$") (path 1) (type 'CONFLICT))
391 ;; C might be followed by a "suprious" U for non-mergeable files
392 (cvs-or (cvs-match (concat "U \\(.*" qfile "\\)$")) t))
393 ;; Successful merge
394 (cvs-match (concat "M \\(.*" qfile "\\)$") (path 1))
395 ;; The file already contained the modifications
396 (cvs-match (concat "^\\(.*" qfile
397 "\\) already contains the differences between .*$")
398 (path 1) (type '(UP-TO-DATE . MERGED)))
399 t)
400 (cvs-parsed-fileinfo (if dont-change-disc 'NEED-MERGE
401 (or type '(MODIFIED . MERGED))) path nil
402 :merge (cons base-rev head-rev))))))
403
404(defun cvs-parse-status ()
405 (let (nofile path base-rev head-rev type)
406 (and
407 (cvs-match
408 "===================================================================$")
409 (cvs-match "File: \\(no file \\)?\\(.*[^ \t]\\)[ \t]+Status: "
410 (nofile 1) (path 2))
411 (cvs-or
412 (cvs-match "Needs \\(Checkout\\|Patch\\)$"
413 (type (if nofile 'MISSING 'NEED-UPDATE)))
414 (cvs-match "Up-to-date$"
415 (type (if nofile '(UP-TO-DATE . REMOVED) 'UP-TO-DATE)))
416 (cvs-match ".*[Cc]onflict.*$" (type 'CONFLICT))
417 (cvs-match "Locally Added$" (type 'ADDED))
418 (cvs-match "Locally Removed$" (type 'REMOVED))
419 (cvs-match "Locally Modified$" (type 'MODIFIED))
420 (cvs-match "Needs Merge$" (type 'NEED-MERGE))
421 (cvs-match "Unknown$" (type 'UNKNOWN)))
422 (cvs-match "$")
423 (cvs-or
424 (cvs-match " *Version:[ \t]*\\([0-9.]+\\).*$" (base-rev 1))
425 ;; NOTE: there's no date on the end of the following for server mode...
426 (cvs-match " *Working revision:[ \t]*-?\\([0-9.]+\\).*$" (base-rev 1))
427 ;; Let's not get all worked up if the format changes a bit
428 (cvs-match " *Working revision:.*$"))
429 (cvs-or
430 (cvs-match " *RCS Version:[ \t]*\\([0-9.]+\\)[ \t]*.*$" (head-rev 1))
431 (cvs-match " *Repository revision:[ \t]*\\([0-9.]+\\)[ \t]*\\(.*\\)$"
432 (head-rev 1))
433 (cvs-match " *Repository revision:.*"))
434 (cvs-or
435 (and;;sometimes those fields are missing
436 (cvs-match " *Sticky Tag:[ \t]*\\(.*\\)$") ; FIXME: use it
437 (cvs-match " *Sticky Date:[ \t]*\\(.*\\)$") ; FIXME: use it
438 (cvs-match " *Sticky Options:[ \t]*\\(.*\\)$")) ; FIXME: use it
439 t)
440 (cvs-match "$")
441 ;; ignore the tags-listing in the case of `status -v'
442 (cvs-or (cvs-match " *Existing Tags:\n\\(\t.*\n\\)*$") t)
443 (cvs-parsed-fileinfo type path nil
444 :base-rev base-rev
445 :head-rev head-rev))))
446
447(defun cvs-parse-commit ()
448 (let (path base-rev subtype)
449 (cvs-or
450
451 (and
452 (cvs-match "\\(Checking in\\|Removing\\) \\(.*\\);$" (path 2))
453 (cvs-match ".*,v <-- .*$")
454 (cvs-or
455 ;; deletion
456 (cvs-match "new revision: delete; previous revision: \\([0-9.]*\\)$"
457 (subtype 'REMOVED) (base-rev 1))
458 ;; addition
459 (cvs-match "initial revision: \\([0-9.]*\\)$"
460 (subtype 'ADDED) (base-rev 1))
461 ;; update
462 (cvs-match "new revision: \\([0-9.]*\\); previous revision: .*$"
463 (subtype 'COMMITTED) (base-rev 1)))
464 (cvs-match "done$")
465 ;; it's important here not to rely on the default directory management
466 ;; because `cvs commit' might begin by a series of Examining messages
467 ;; so the processing of the actual checkin messages might begin with
468 ;; a `current-dir' set to something different from ""
469 (cvs-parsed-fileinfo (cons 'UP-TO-DATE subtype) path 'trust
470 :base-rev base-rev))
471
472 ;; useless message added before the actual addition: ignored
473 (cvs-match "RCS file: .*\ndone$"))))
474
475
476(provide 'pcvs-parse)
477
478;;; pcl-cvs-parse.el ends here
diff --git a/lisp/pcvs-util.el b/lisp/pcvs-util.el
new file mode 100644
index 00000000000..9763fd82566
--- /dev/null
+++ b/lisp/pcvs-util.el
@@ -0,0 +1,381 @@
1;;; pcvs-util.el --- Utitlity functions for pcl-cvs
2
3;; Copyright (C) 1998-2000 Free Software Foundation, Inc.
4
5;; Author: Stefan Monnier <monnier@cs.yale.edu>
6;; Keywords: pcl-cvs
7;; Version: $Name: $
8;; Revision: $Id: pcl-cvs-util.el,v 1.26 2000/03/05 21:32:21 monnier Exp $
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
30;;; Code:
31
32(eval-when-compile (require 'cl))
33
34;;;;
35;;;; list processing
36;;;l
37
38(defsubst cvs-car (x) (if (consp x) (car x) x))
39(defalias 'cvs-cdr 'cdr-safe)
40(defsubst cvs-append (&rest xs)
41 (apply 'append (mapcar (lambda (x) (if (listp x) x (list x))) xs)))
42
43(defsubst cvs-every (-cvs-every-f -cvs-every-l)
44 (while (consp -cvs-every-l)
45 (unless (funcall -cvs-every-f (pop -cvs-every-l))
46 (setq -cvs-every-l t)))
47 (not -cvs-every-l))
48
49(defun cvs-union (xs ys)
50 (let ((zs ys))
51 (dolist (x xs zs)
52 (unless (member x ys) (push x zs)))))
53
54
55(defun cvs-map (-cvs-map-f &rest -cvs-map-ls)
56 (unless (cvs-every 'null -cvs-map-ls)
57 (cons (apply -cvs-map-f (mapcar 'car -cvs-map-ls))
58 (apply 'cvs-map -cvs-map-f (mapcar 'cdr -cvs-map-ls)))))
59
60(defun cvs-first (l &optional n)
61 (if (null n) (car l)
62 (when l
63 (let* ((nl (list (pop l)))
64 (ret nl))
65 (while (and l (> n 1))
66 (setcdr nl (list (pop l)))
67 (setq nl (cdr nl))
68 (decf n))
69 ret))))
70
71(defun cvs-partition (p l)
72 "Partition a list L into two lists based on predicate P.
73The function returns a `cons' cell where the `car' contains
74elements of L for which P is true while the `cdr' contains
75the other elements. The ordering among elements is maintained."
76 (let (car cdr)
77 (dolist (x l)
78 (if (funcall p x) (push x car) (push x cdr)))
79 (cons (nreverse car) (nreverse cdr))))
80
81;;;;
82;;;; frame, window, buffer handling
83;;;;
84
85(defun cvs-pop-to-buffer-same-frame (buf)
86 "Pop to BUF like `pop-to-buffer' but staying on the same frame.
87If `pop-to-buffer' would have opened a new frame, this function would
88try to split the a new window instead."
89 (let ((pop-up-windows (or pop-up-windows pop-up-frames))
90 (pop-up-frames nil))
91 (or (let ((buf (get-buffer-window buf))) (and buf (select-window buf)))
92 (and pop-up-windows
93 (ignore-errors (select-window (split-window-vertically)))
94 (switch-to-buffer buf))
95 (pop-to-buffer (current-buffer)))))
96
97(defun cvs-bury-buffer (buf &optional mainbuf)
98 "Hide the buffer BUF that was temporarily popped up.
99BUF is assumed to be a temporary buffer used from the buffer MAINBUF."
100 (interactive (list (current-buffer)))
101 (save-current-buffer
102 (let ((win (if (eq buf (window-buffer (selected-window))) (selected-window)
103 (get-buffer-window buf t))))
104 (when win
105 (if (window-dedicated-p win)
106 (condition-case ()
107 (delete-window win)
108 (error (iconify-frame (window-frame win))))
109 (if (and mainbuf (get-buffer-window mainbuf))
110 (delete-window win)))))
111 (with-current-buffer buf
112 (bury-buffer (unless (and (eq buf (window-buffer (selected-window)))
113 (not (window-dedicated-p (selected-window))))
114 buf)))
115 (when mainbuf
116 (let ((mainwin (or (get-buffer-window mainbuf)
117 (get-buffer-window mainbuf 'visible))))
118 (when mainwin (select-window mainwin))))))
119
120(defun cvs-get-buffer-create (name &optional noreuse)
121 "Create a buffer NAME unless such a buffer already exists.
122If the NAME looks like an absolute file name, the buffer will be created
123with `create-file-buffer' and will probably get another name than NAME.
124In such a case, the search for another buffer with the same name doesn't
125use the buffer name but the buffer's `list-buffers-directory' variable.
126If NOREUSE is non-nil, always return a new buffer."
127 (or (and (not (file-name-absolute-p name)) (get-buffer-create name))
128 (unless noreuse
129 (dolist (buf (buffer-list))
130 (with-current-buffer buf
131 (when (equal name list-buffers-directory)
132 (return buf)))))
133 (with-current-buffer (create-file-buffer name)
134 (set (make-local-variable 'list-buffers-directory) name)
135 (current-buffer))))
136
137;;;;
138;;;; string processing
139;;;;
140
141(defun cvs-file-to-string (file &optional oneline args)
142 "Read the content of FILE and return it as a string.
143If ONELINE is t, only the first line (no \\n) will be returned.
144If ARGS is non-nil, the file will be executed with ARGS as its
145arguments. If ARGS is not a list, no argument will be passed."
146 (with-temp-buffer
147 (condition-case nil
148 (progn
149 (if args
150 (apply 'call-process
151 file nil t nil (when (listp args) args))
152 (insert-file-contents file))
153 (buffer-substring (point-min)
154 (if oneline
155 (progn (goto-char (point-min)) (end-of-line) (point))
156 (point-max))))
157 (file-error nil))))
158
159(defun cvs-string-prefix-p (str1 str2)
160 "Tell whether STR1 is a prefix of STR2."
161 (let ((length1 (length str1)))
162 (and (>= (length str2) length1)
163 (string= str1 (substring str2 0 length1)))))
164
165;; (string->strings (strings->string X)) == X
166(defun cvs-strings->string (strings &optional separator)
167 "Concatenate the STRINGS, adding the SEPARATOR (default \" \").
168This tries to quote the strings to avoid ambiguity such that
169 (cvs-string->strings (cvs-strings->string strs)) == strs
170Only some SEPARATOR will work properly."
171 (let ((sep (or separator " ")))
172 (mapconcat
173 (lambda (str)
174 (if (string-match "[\\\"]" str)
175 (concat "\"" (replace-regexps-in-string "[\\\"]" "\\\\\\&" str) "\"")
176 str))
177 strings sep)))
178
179;; (string->strings (strings->string X)) == X
180(defun cvs-string->strings (string &optional separator)
181 "Split the STRING into a list of strings.
182It understands elisp style quoting within STRING such that
183 (cvs-string->strings (cvs-strings->string strs)) == strs
184The SEPARATOR regexp defaults to \"\\s-+\"."
185 (let ((sep (or separator "\\s-+"))
186 (i (string-match "[\"]" string)))
187 (if (null i) (split-string string sep) ; no quoting: easy
188 (append (unless (eq i 0) (split-string (substring string 0 i) sep))
189 (let ((rfs (read-from-string string i)))
190 (cons (car rfs)
191 (cvs-string->strings (substring string (cdr rfs)) sep)))))))
192
193
194(defun cvs-string-fill (str n &optional filling truncate)
195 "Add FILLING (defaults to the space char) to STR to reach size N.
196If STR is longer than N, truncate if TRUNCATE is set, else don't do anything."
197 (let ((l (length str)))
198 (if (> l n)
199 (if truncate (substring str 0 n) str)
200 (concat str (make-string (- n l) (or filling ? ))))))
201
202;;;;
203;;;; file names
204;;;;
205
206(defsubst cvs-expand-dir-name (d)
207 (file-name-as-directory (expand-file-name d)))
208
209;;;;
210;;;; (interactive <foo>) support function
211;;;;
212
213(defstruct (cvs-qtypedesc
214 (:constructor nil) (:copier nil)
215 (:constructor cvs-qtypedesc-create
216 (str2obj obj2str &optional complete hist-sym require)))
217 str2obj
218 obj2str
219 hist-sym
220 complete
221 require)
222
223
224(defconst cvs-qtypedesc-string1 (cvs-qtypedesc-create 'identity 'identity t))
225(defconst cvs-qtypedesc-string (cvs-qtypedesc-create 'identity 'identity))
226(defconst cvs-qtypedesc-strings
227 (cvs-qtypedesc-create 'cvs-string->strings 'cvs-strings->string nil))
228
229(defun cvs-query-read (default prompt qtypedesc &optional hist-sym)
230 (let* ((qtypedesc (or qtypedesc cvs-qtypedesc-strings))
231 (hist-sym (or hist-sym (cvs-qtypedesc-hist-sym qtypedesc)))
232 (complete (cvs-qtypedesc-complete qtypedesc))
233 (completions (and (functionp complete) (funcall complete)))
234 (initval (funcall (cvs-qtypedesc-obj2str qtypedesc) default)))
235 (funcall (cvs-qtypedesc-str2obj qtypedesc)
236 (cond
237 ((null complete) (read-string prompt initval hist-sym))
238 ((functionp complete)
239 (completing-read prompt completions
240 nil (cvs-qtypedesc-require qtypedesc)
241 initval hist-sym))
242 (t initval)))))
243
244;;;;
245;;;; Flags handling
246;;;;
247
248(defstruct (cvs-flags
249 (:constructor nil)
250 (:constructor -cvs-flags-make
251 (desc defaults &optional qtypedesc hist-sym)))
252 defaults persist desc qtypedesc hist-sym)
253
254(defmacro cvs-flags-define (sym defaults
255 &optional desc qtypedesc hist-sym docstring)
256 `(defconst ,sym
257 (let ((bound (boundp ',sym)))
258 (if (and bound (cvs-flags-p ,sym)) ,sym
259 (let ((defaults ,defaults))
260 (-cvs-flags-make ,desc
261 (if bound (cons ,sym (cdr defaults)) defaults)
262 ,qtypedesc ,hist-sym))))
263 ,docstring))
264
265(defun cvs-flags-query (sym &optional desc arg)
266 "Query flags based on SYM.
267Optional argument DESC will be used for the prompt
268If ARG (or a prefix argument) is nil, just use the 0th default.
269If it is a non-negative integer, use the corresponding default.
270If it is a negative integer query for a new value of the corresponding
271 default and return that new value.
272If it is \\[universal-argument], just query and return a value without
273 altering the defaults.
274If it is \\[universal-argument] \\[universal-argument], behave just
275 as if a negative zero was provided."
276 (let* ((flags (symbol-value sym))
277 (desc (or desc (cvs-flags-desc flags)))
278 (qtypedesc (cvs-flags-qtypedesc flags))
279 (hist-sym (cvs-flags-hist-sym flags))
280 (arg (if (eq arg 'noquery) 0 (or arg current-prefix-arg 0)))
281 (numarg (prefix-numeric-value arg))
282 (defaults (cvs-flags-defaults flags))
283 (permstr (if (< numarg 0) (format " (%sth default)" (- numarg)))))
284 ;; special case for universal-argument
285 (when (consp arg)
286 (setq permstr (if (> numarg 4) " (permanent)" ""))
287 (setq numarg 0))
288
289 ;; sanity check
290 (unless (< (abs numarg) (length defaults))
291 (error "There is no %sth default." (abs numarg)))
292
293 (if permstr
294 (let* ((prompt (format "%s%s: " desc permstr))
295 (fs (cvs-query-read (nth (- numarg) (cvs-flags-defaults flags))
296 prompt qtypedesc hist-sym)))
297 (when (not (equal permstr ""))
298 (setf (nth (- numarg) (cvs-flags-defaults flags)) fs))
299 fs)
300 (nth numarg defaults))))
301
302(defsubst cvs-flags-set (sym index value)
303 "Set SYM's INDEX'th setting to VALUE."
304 (setf (nth index (cvs-flags-defaults (symbol-value sym))) value))
305
306;;;;
307;;;; Prefix keys
308;;;;
309
310(defconst cvs-prefix-number 10)
311
312(defsubst cvs-prefix-sym (sym) (intern (concat (symbol-name sym) "-cps")))
313
314(defmacro cvs-prefix-define (sym docstring desc defaults
315 &optional qtypedesc hist-sym)
316 (let ((cps (cvs-prefix-sym sym)))
317 `(progn
318 (defvar ,sym nil ,(cons (or docstring "") "
319See `cvs-prefix-set' for further description of the behavior."))
320 (defconst ,cps
321 (let ((defaults ,defaults))
322 ;; sanity ensurance
323 (unless (>= (length defaults) cvs-prefix-number)
324 (setq defaults (append defaults
325 (make-list (1- cvs-prefix-number)
326 (first defaults)))))
327 (-cvs-flags-make ,desc defaults ,qtypedesc ,hist-sym))))))
328
329(defun cvs-prefix-make-local (sym)
330 (let ((cps (cvs-prefix-sym sym)))
331 (make-local-variable sym)
332 (set (make-local-variable cps) (copy-cvs-flags (symbol-value cps)))))
333
334(defun cvs-prefix-set (sym arg)
335 ;; we could distinguish between numeric and non-numeric prefix args instead of
336 ;; relying on that magic `4'.
337 "Set the cvs-prefix contained in SYM.
338If ARG is between 0 and 9, it selects the corresponding default.
339If ARG is negative (or \\[universal-argument] which corresponds to negative 0),
340 it queries the user and sets the -ARG'th default.
341If ARG is greater than 9 (or \\[universal-argument] \\[universal-argument]),
342 the (ARG mod 10)'th prefix is made persistent.
343If ARG is NIL toggle the PREFIX's value between its 0th default and NIL
344 and reset the persistence."
345 (let* ((prefix (symbol-value (cvs-prefix-sym sym)))
346 (numarg (if (integerp arg) arg 0))
347 (defs (cvs-flags-defaults prefix)))
348
349 ;; set persistence if requested
350 (when (> (prefix-numeric-value arg) 9)
351 (setf (cvs-flags-persist prefix) t)
352 (setq numarg (mod numarg 10)))
353
354 ;; set the value
355 (set sym
356 (cond
357 ((null arg)
358 (setf (cvs-flags-persist prefix) nil)
359 (unless (symbol-value sym) (first (cvs-flags-defaults prefix))))
360
361 ((or (consp arg) (< numarg 0))
362 (setf (nth (- numarg) (cvs-flags-defaults prefix))
363 (cvs-query-read (nth (- numarg) (cvs-flags-defaults prefix))
364 (format "%s: " (cvs-flags-desc prefix))
365 (cvs-flags-qtypedesc prefix)
366 (cvs-flags-hist-sym prefix))))
367 (t (nth numarg (cvs-flags-defaults prefix)))))
368 (force-mode-line-update)))
369
370(defun cvs-prefix-get (sym &optional read-only)
371 "Return the current value of the prefix SYM.
372and reset it unless READ-ONLY is non-nil."
373 (prog1 (symbol-value sym)
374 (unless (or read-only
375 (cvs-flags-persist (symbol-value (cvs-prefix-sym sym))))
376 (set sym nil)
377 (force-mode-line-update))))
378
379(provide 'pcvs-util)
380
381;;; pcl-cvs-util.el ends here
diff --git a/lisp/pcvs.el b/lisp/pcvs.el
new file mode 100644
index 00000000000..320ec3c58ac
--- /dev/null
+++ b/lisp/pcvs.el
@@ -0,0 +1,2122 @@
1;;; pcvs.el -- A Front-end to CVS.
2
3;; Copyright (C) 1991-2000 Free Software Foundation, Inc.
4
5;; Author: (The PCL-CVS Trust) pcl-cvs@cyclic.com
6;; (Per Cederqvist) ceder@lysator.liu.se
7;; (Greg A. Woods) woods@weird.com
8;; (Jim Blandy) jimb@cyclic.com
9;; (Karl Fogel) kfogel@floss.red-bean.com
10;; (Jim Kingdon) kingdon@cyclic.com
11;; (Stefan Monnier) monnier@cs.yale.edu
12;; (Greg Klanderman) greg@alphatech.com
13;; (Jari Aalto+mail.emacs) jari.aalto@poboxes.com
14;; Maintainer: (Stefan Monnier) monnier+lists/cvs/pcl@flint.cs.yale.edu
15;; Keywords: CVS, version control, release management
16;; Version: $Name: $
17;; Revision: $Id: pcl-cvs.el,v 1.75 2000/03/05 21:32:21 monnier Exp $
18
19;; This file is part of GNU Emacs.
20
21;; GNU Emacs is free software; you can redistribute it and/or modify
22;; it under the terms of the GNU General Public License as published by
23;; the Free Software Foundation; either version 2, or (at your option)
24;; any later version.
25
26;; GNU Emacs is distributed in the hope that it will be useful,
27;; but WITHOUT ANY WARRANTY; without even the implied warranty of
28;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29;; GNU General Public License for more details.
30
31;; You should have received a copy of the GNU General Public License
32;; along with GNU Emacs; see the file COPYING. If not, write to the
33;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
34;; Boston, MA 02111-1307, USA.
35
36;;; Commentary:
37
38;;; Todo:
39
40;; * FIX THE DOCUMENTATION
41;;
42;; * Emacs-21 adaptation
43;; ** use the new arg of save-some-buffers
44;; ** add toolbar entries
45;; ** use `format' now that it keeps properties
46;; ** use propertize
47;; ** add compatibility with older name's variables.
48;;
49;; * New Features
50;;
51;; ** marking
52;; *** marking directories should jump to just after the dir.
53;; *** allow (un)marking directories at a time with the mouse.
54;; *** marking with the mouse should not move point.
55;;
56;; ** liveness indicator
57;;
58;; ** indicate in docstring if the cmd understands the `b' prefix(es).
59;;
60;; ** call smerge-mode when opening CONFLICT files.
61;;
62;; ** after-parse-hook (to eliminate *.elc from Emacs' CVS repository :-)
63;;
64;; ** have vc-checkin delegate to cvs-mode-commit when applicable
65;;
66;; ** higher-level CVS operations
67;;
68;; *** cvs-mode-rename
69;; *** cvs-mode-branch
70;;
71;; ** module-level commands
72;;
73;; *** add support for parsing 'modules' file ("cvs co -c")
74;;
75;; *** cvs-mode-rcs2log
76;; *** cvs-rdiff
77;; *** cvs-release
78;; *** cvs-import
79;; *** C-u M-x cvs-checkout should ask for a cvsroot
80;;
81;; *** cvs-mode-handle-new-vendor-version
82;; - checks out module, or alternately does update join
83;; - does "cvs -n tag LAST_VENDOR" to find old files into *cvs*
84;;
85;; *** cvs-export
86;; (with completion on tag names and hooks to
87;; help generate full releases)
88;;
89;; ** allow cvs-cmd-do to either clear the marks or not.
90;;
91;; ** allow more concurrency: if the output buffer is busy, pick a new one.
92;;
93;; ** configurable layout/format of *cvs*.
94;;
95;; ** display stickiness information. And current CVS/Tag as well.
96;;
97;; ** cvs-log-mode should know how to extract version info
98;; cvs-log-current-tag is a nop right now :-(
99;;
100;; ** write 'cvs-mode-admin' to do arbitrary 'cvs admin' commands
101;;
102;; ** cvs-mode-incorporate
103;; It would merge in the status from one ``*cvs*'' buffer into another.
104;; This would be used to populate such a buffer that had been created with
105;; a `cvs {update,status,checkout} -l'.
106;;
107;; ** cvs-mode-(i)diff-other-{file,buffer,cvs-buffer}
108;;
109;; ** offer the choice to kill the process when the user kills the cvs buffer.
110;; right now, it's killed without further ado.
111;;
112;; ** make `cvs-mode-ignore' allow manually entering a pattern.
113;; to which dir should it apply ?
114;;
115;; ** cvs-mode-ignore should try to remove duplicate entries.
116;;
117;; * Old misfeatures
118;;
119;; ** cvs-mode-<foo> commands tend to require saving too many buffers
120;; they should only require saving the files concerned by the command
121;;
122;; * Secondary issues
123;;
124;; ** maybe poll/check CVS/Entries files to react to external `cvs' commands ?
125;;
126;; ** some kind of `cvs annotate' support ?
127;; but vc-annotate can be used instead.
128;;
129;; * probably not worth the trouble
130;;
131;; ** dynamic `g' mapping
132;; Make 'g', and perhaps other commands, use either cvs-update or
133;; cvs-examine depending on the read-only status of the cvs buffer, for
134;; instance.
135;;
136;; ** add message-levels so that we can hide some levels of messages
137
138;;; Code:
139
140(eval-when-compile (require 'cl))
141(require 'ewoc) ;Ewoc was once cookie
142(require 'pcvs-defs)
143(require 'pcvs-util)
144(require 'pcvs-parse)
145(require 'pcvs-info)
146
147
148;;;;
149;;;; global vars
150;;;;
151
152(defvar cvs-cookies) ;;nil
153 ;;"Handle for the cookie structure that is displayed in the *cvs* buffer.")
154;;(make-variable-buffer-local 'cvs-cookies)
155
156;;;;
157;;;; Dynamically scoped variables
158;;;;
159
160(defvar cvs-from-vc nil "Bound to t inside VC advice.")
161
162;;;;
163;;;; flags variables
164;;;;
165
166(defun cvs-defaults (&rest defs)
167 (let ((defs (cvs-first defs cvs-shared-start)))
168 (append defs
169 (make-list (- cvs-shared-start (length defs)) (first defs))
170 cvs-shared-flags)))
171
172;; For cvs flags, we need to add "-f" to override the cvsrc settings
173;; we also want to evict the annoying -q and -Q options that hide useful
174;; information from pcl-cvs.
175(cvs-flags-define cvs-cvs-flags '(("-f")))
176
177(cvs-flags-define cvs-checkout-flags (cvs-defaults '("-P")))
178(cvs-flags-define cvs-status-flags (cvs-defaults '("-v") nil))
179(cvs-flags-define cvs-log-flags (cvs-defaults nil))
180(cvs-flags-define cvs-diff-flags (cvs-defaults '("-u" "-N") '("-c" "-N")))
181(cvs-flags-define cvs-tag-flags (cvs-defaults nil))
182(cvs-flags-define cvs-add-flags (cvs-defaults nil))
183(cvs-flags-define cvs-commit-flags (cvs-defaults nil))
184(cvs-flags-define cvs-remove-flags (cvs-defaults nil))
185;;(cvs-flags-define cvs-undo-flags (cvs-defaults nil))
186(cvs-flags-define cvs-update-flags (cvs-defaults '("-d" "-P")))
187
188(defun cvs-reread-cvsrc ()
189 "Reset the default arguments to those in the `cvs-cvsrc-file'."
190 (interactive)
191 (let ((cvsrc (cvs-file-to-string cvs-cvsrc-file)))
192 (when (stringp cvsrc)
193 ;; fetch the values
194 (dolist (cmd '("cvs" "checkout" "status" "log" "diff" "tag"
195 "add" "commit" "remove" "update"))
196 (let* ((sym (intern (concat "cvs-" cmd "-flags")))
197 (val (when (string-match (concat "^" cmd "\\s-\\(.*\\)$") cvsrc)
198 (cvs-string->strings (match-string 1 cvsrc)))))
199 (cvs-flags-set sym 0 val)))
200 ;; ensure that cvs doesn't have -q or -Q
201 (cvs-flags-set 'cvs-cvs-flags 0
202 (cons "-f"
203 (cdr (cvs-partition
204 (lambda (x) (member x '("-q" "-Q")))
205 (cvs-flags-query 'cvs-cvs-flags
206 nil 'noquery))))))))
207
208;; initialize to cvsrc's default values
209(cvs-reread-cvsrc)
210
211
212;;;;
213;;;; Mouse bindings and mode motion
214;;;;
215
216(defun cvs-menu (e)
217 "Popup the CVS menu."
218 (interactive "e")
219 (mouse-set-point e)
220 (x-popup-menu e cvs-menu-map))
221
222(defvar cvs-mode-line-process nil
223 "Mode-line control for displaying info on cvs process status.")
224
225
226;;;;
227;;;; Query-Type-Descriptor for Tags
228;;;;
229
230(autoload 'cvs-status-get-tags "cvs-status")
231(defun cvs-tags-list ()
232 "Return a list of acceptable tags, ready for completions."
233 (assert (cvs-buffer-p))
234 (let ((marked (cvs-get-marked)))
235 (list* '("BASE") '("HEAD")
236 (when marked
237 (with-temp-buffer
238 (call-process cvs-program
239 nil ;no input
240 t ;output to current-buffer
241 nil ;don't update display while running
242 "status"
243 "-v"
244 (cvs-fileinfo->full-path (car marked)))
245 (goto-char (point-min))
246 (let ((tags (cvs-status-get-tags)))
247 (when (listp tags) tags)))))))
248
249(defvar cvs-tag-history nil)
250(defconst cvs-qtypedesc-tag
251 (cvs-qtypedesc-create 'identity 'identity 'cvs-tags-list 'cvs-tag-history))
252
253;;;;
254
255(defun cvs-mode! (&optional -cvs-mode!-fun -cvs-mode!-noerror)
256 "Switch to the *cvs* buffer.
257If -CVS-MODE!-FUN is provided, it is executed *cvs* being the current buffer
258 and with its window selected. Else, the *cvs* buffer is simply selected.
259If -CVS-MODE!-NOERROR is non-nil, then failure to find a *cvs* buffer does
260 not generate an error and the current buffer is kept selected.
261-CVS-MODE!-FUN is called interactively if applicable and else with no argument."
262 (let* ((-cvs-mode!-buf (current-buffer))
263 (cvsbuf (cond ((cvs-buffer-p) (current-buffer))
264 ((and cvs-buffer (cvs-buffer-p cvs-buffer)) cvs-buffer)
265 (-cvs-mode!-noerror (current-buffer))
266 (t (error "can't find the *cvs* buffer."))))
267 (-cvs-mode!-wrapper cvs-minor-wrap-function)
268 (-cvs-mode!-cont (lambda ()
269 (save-current-buffer
270 (if (commandp -cvs-mode!-fun)
271 (call-interactively -cvs-mode!-fun)
272 (funcall -cvs-mode!-fun))))))
273 (if (not -cvs-mode!-fun) (set-buffer cvsbuf)
274 (let ((cvs-mode!-buf (current-buffer))
275 (cvs-mode!-owin (selected-window))
276 (cvs-mode!-nwin (get-buffer-window cvsbuf 'visible)))
277 (unwind-protect
278 (progn
279 (set-buffer cvsbuf)
280 (when cvs-mode!-nwin (select-window cvs-mode!-nwin))
281 (if -cvs-mode!-wrapper
282 (funcall -cvs-mode!-wrapper -cvs-mode!-buf -cvs-mode!-cont)
283 (funcall -cvs-mode!-cont)))
284 (set-buffer cvs-mode!-buf)
285 (when (and cvs-mode!-nwin (eq cvs-mode!-nwin (selected-window)))
286 ;; the selected window has not been changed by FUN
287 (select-window cvs-mode!-owin)))))))
288
289;;;;
290;;;; Prefixes
291;;;;
292
293(defvar cvs-branches (list cvs-vendor-branch "HEAD" "HEAD"))
294(cvs-prefix-define cvs-branch-prefix
295 "Current selected branch."
296 "version"
297 (cons cvs-vendor-branch cvs-branches)
298 cvs-qtypedesc-tag)
299
300(defun cvs-set-branch-prefix (arg)
301 "Set the branch prefix to take action at the next command.
302See `cvs-prefix-set' for a further the description of the behavior.
303\\[universal-argument] 1 selects the vendor branch
304and \\[universal-argument] 2 selects the HEAD."
305 (interactive "P")
306 (cvs-mode!)
307 (cvs-prefix-set 'cvs-branch-prefix arg))
308
309(defun cvs-add-branch-prefix (flags &optional arg)
310 "Add branch selection argument if the branch prefix was set.
311The argument is added (or not) to the list of FLAGS and is constructed
312by appending the branch to ARG which defaults to \"-r\"."
313 (let ((branch (cvs-prefix-get 'cvs-branch-prefix)))
314 ;; deactivate the secondary prefix, even if not used.
315 (cvs-prefix-get 'cvs-secondary-branch-prefix)
316 (if branch (cons (concat (or arg "-r") branch) flags) flags)))
317
318(cvs-prefix-define cvs-secondary-branch-prefix
319 "Current secondary selected branch."
320 "version"
321 (cons cvs-vendor-branch cvs-branches)
322 cvs-qtypedesc-tag)
323
324(defun cvs-set-secondary-branch-prefix (arg)
325 "Set the branch prefix to take action at the next command.
326See `cvs-prefix-set' for a further the description of the behavior.
327\\[universal-argument] 1 selects the vendor branch
328and \\[universal-argument] 2 selects the HEAD."
329 (interactive "P")
330 (cvs-mode!)
331 (cvs-prefix-set 'cvs-secondary-branch-prefix arg))
332
333(defun cvs-add-secondary-branch-prefix (flags &optional arg)
334 "Add branch selection argument if the secondary branch prefix was set.
335The argument is added (or not) to the list of FLAGS and is constructed
336by appending the branch to ARG which defaults to \"-r\".
337Since the `cvs-secondary-branch-prefix' is only active if the primary
338prefix is active, it is important to read the secondary prefix before
339the primay since reading the primary can deactivate it."
340 (let ((branch (and (cvs-prefix-get 'cvs-branch-prefix 'read-only)
341 (cvs-prefix-get 'cvs-secondary-branch-prefix))))
342 (if branch (cons (concat (or arg "-r") branch) flags) flags)))
343
344;;;;
345
346(define-minor-mode
347 cvs-minor-mode
348 "
349This mode is used for buffers related to a main *cvs* buffer.
350All the `cvs-mode' buffer operations are simply rebound under
351the \\[cvs-mode-map] prefix.
352"
353 nil " CVS")
354(put 'cvs-minor-mode 'permanent-local t)
355
356
357(defvar cvs-temp-buffers nil)
358(defun cvs-temp-buffer (&optional cmd normal nosetup)
359 "Create a temporary buffer to run CMD in.
360If CMD is a string, use it to lookup `cvs-buffer-name-alist' to find
361the buffer name to be used and its `major-mode'.
362
363The selected window will not be changed. The new buffer will not maintain undo
364information and will be read-only unless NORMAL is non-nil. It will be emptied
365\(unless NOSETUP is non-nil\) and its `default-directory' will be inherited
366from the current buffer."
367 (let* ((cvs-buf (current-buffer))
368 (info (cdr (assoc cmd cvs-buffer-name-alist)))
369 (name (eval (first info)))
370 (mode (second info))
371 (dir default-directory)
372 (buf (cond
373 (name (cvs-get-buffer-create name))
374 ((and (bufferp cvs-temp-buffer) (buffer-name cvs-temp-buffer))
375 cvs-temp-buffer)
376 (t
377 (set (make-local-variable 'cvs-temp-buffer)
378 (cvs-get-buffer-create
379 (eval cvs-temp-buffer-name) 'noreuse))))))
380
381 ;; handle the potential pre-existing process
382 (let ((proc (get-buffer-process buf)))
383 (when (and (not normal) (processp proc)
384 (memq (process-status proc) '(run stop)))
385 (error "Can not run two cvs processes simultaneously")))
386
387 (if (not name) (kill-local-variable 'other-window-scroll-buffer)
388 ;; Strangely, if no window is created, `display-buffer' ends up
389 ;; doing a `switch-to-buffer' which does a `set-buffer', hence
390 ;; the need for `save-excursion'.
391 (unless nosetup (save-excursion (display-buffer buf)))
392 ;; FIXME: this doesn't do the right thing if the user later on
393 ;; does a `find-file-other-window' and `scroll-other-window'
394 (set (make-local-variable 'other-window-scroll-buffer) buf))
395
396 (add-to-list 'cvs-temp-buffers buf)
397
398 (with-current-buffer buf
399 (setq buffer-read-only nil)
400 (setq default-directory dir)
401 (unless nosetup (erase-buffer))
402 (set (make-local-variable 'cvs-buffer) cvs-buf)
403 ;;(cvs-minor-mode 1)
404 (let ((lbd list-buffers-directory))
405 (if (fboundp mode) (funcall mode) (fundamental-mode))
406 (when lbd (set (make-local-variable 'list-buffers-directory) lbd)))
407 (cvs-minor-mode 1)
408 ;;(set (make-local-variable 'cvs-buffer) cvs-buf)
409 (unless normal
410 (setq buffer-read-only t)
411 (buffer-disable-undo))
412 buf)))
413
414(defun cvs-mode-kill-buffers ()
415 "Kill all the \"temporary\" buffers created by the *cvs* buffer."
416 (interactive)
417 (dolist (buf cvs-temp-buffers) (ignore-errors (kill-buffer buf))))
418
419(defun cvs-make-cvs-buffer (dir &optional new)
420 "Create the *cvs* buffer for directory DIR.
421If non-nil, NEW means to create a new buffer no matter what."
422 ;; the real cvs-buffer creation
423 (setq dir (cvs-expand-dir-name dir))
424 (let* ((buffer-name (eval cvs-buffer-name))
425 (buffer
426 (or (and (not new)
427 (eq cvs-reuse-cvs-buffer 'current)
428 (cvs-buffer-p) ;reuse the current buffer if possible
429 (current-buffer))
430 ;; look for another cvs buffer visiting the same directory
431 (save-excursion
432 (unless new
433 (dolist (buffer (cons (current-buffer) (buffer-list)))
434 (set-buffer buffer)
435 (and (cvs-buffer-p)
436 (case cvs-reuse-cvs-buffer
437 (always t)
438 (subdir
439 (or (cvs-string-prefix-p default-directory dir)
440 (cvs-string-prefix-p dir default-directory)))
441 (samedir (string= default-directory dir)))
442 (return buffer)))))
443 ;; we really have to create a new buffer:
444 ;; we temporarily bind cwd to "" to prevent
445 ;; create-file-buffer from using directory info
446 ;; unless it is explicitly in the cvs-buffer-name.
447 (cvs-get-buffer-create buffer-name new))))
448 (with-current-buffer buffer
449 (or
450 (and (string= dir default-directory) (cvs-buffer-p)
451 ;; just a refresh
452 (ignore-errors
453 (cvs-cleanup-collection cvs-cookies nil nil t)
454 (current-buffer)))
455 ;; setup from scratch
456 (progn
457 (setq default-directory dir)
458 (setq buffer-read-only nil)
459 (erase-buffer)
460 (setq buffer-read-only t)
461 (cvs-mode)
462 (set (make-local-variable 'list-buffers-directory) buffer-name)
463 ;;(set (make-local-variable 'cvs-temp-buffer) (cvs-temp-buffer))
464 (let ((cookies
465 (ewoc-create
466 buffer 'cvs-fileinfo-pp
467 (format "%s\n\nRepository : %s\nWorking directory: %s\n"
468 cvs-startup-message
469 (directory-file-name (cvs-get-cvsroot))
470 dir))))
471 (set (make-local-variable 'cvs-cookies) cookies)
472 (ewoc-enter-first
473 cookies
474 (cvs-create-fileinfo 'MESSAGE "" " " "\n" :subtype 'HEADER))
475 (ewoc-enter-last
476 cookies
477 (cvs-create-fileinfo 'MESSAGE "" " " "\n" :subtype 'FOOTER))
478 (make-local-hook 'kill-buffer-hook)
479 (add-hook 'kill-buffer-hook
480 (lambda ()
481 (ignore-errors (kill-buffer cvs-temp-buffer)))
482 nil t)
483 ;;(set-buffer buf)
484 buffer))))))
485
486(defun* cvs-cmd-do (cmd dir flags fis new
487 &key cvsargs noexist dont-change-disc noshow)
488 (let* ((dir (file-name-as-directory
489 (abbreviate-file-name (expand-file-name dir))))
490 (cvsbuf (cvs-make-cvs-buffer dir new)))
491 ;; Check that dir is under CVS control.
492 (unless (file-directory-p dir)
493 (error "%s is not a directory." dir))
494 (unless (or noexist (file-directory-p (expand-file-name "CVS" dir)))
495 (error "%s does not contain CVS controlled files." dir))
496
497 (set-buffer cvsbuf)
498 (cvs-mode-run cmd flags fis
499 :cvsargs cvsargs :dont-change-disc dont-change-disc)
500
501 (if noshow cvsbuf
502 (let ((pop-up-windows nil)) (pop-to-buffer cvsbuf)))))
503;; (funcall (if (and (boundp 'pop-up-frames) pop-up-frames)
504;; 'pop-to-buffer 'switch-to-buffer)
505;; cvsbuf))))
506
507;;----------
508(defun cvs-run-process (args fis postprocess &optional single-dir)
509 (assert (cvs-buffer-p cvs-buffer))
510 (save-current-buffer
511 (let ((procbuf (current-buffer))
512 (cvsbuf cvs-buffer)
513 (single-dir (or single-dir (eq cvs-execute-single-dir t))))
514
515 (set-buffer procbuf)
516 (goto-char (point-max))
517 (unless (bolp) (let ((inhibit-read-only t)) (insert "\n")))
518 ;; find the set of files we'll process in this round
519 (let* ((dir+files+rest
520 (if (or (null fis) (not single-dir))
521 ;; not single-dir mode: just process the whole thing
522 (list "" (mapcar 'cvs-fileinfo->full-path fis) nil)
523 ;; single-dir mode: extract the same-dir-elements
524 (let ((dir (cvs-fileinfo->dir (car fis))))
525 ;; output the concerned dir so the parser can translate paths
526 (let ((inhibit-read-only t))
527 (insert "pcl-cvs: descending directory " dir "\n"))
528 ;; loop to find the same-dir-elems
529 (do* ((files () (cons (cvs-fileinfo->file fi) files))
530 (fis fis (cdr fis))
531 (fi (car fis) (car fis)))
532 ((not (and fis (string= dir (cvs-fileinfo->dir fi))))
533 (list dir files fis))))))
534 (dir (first dir+files+rest))
535 (files (second dir+files+rest))
536 (rest (third dir+files+rest)))
537
538 ;; setup the (current) process buffer
539 (set (make-local-variable 'cvs-postprocess)
540 (if (null rest)
541 ;; this is the last invocation
542 postprocess
543 ;; else, we have to register ourselves to be rerun on the rest
544 `(cvs-run-process ',args ',rest ',postprocess ',single-dir)))
545 (make-local-hook 'kill-buffer-hook)
546 (add-hook 'kill-buffer-hook
547 (lambda ()
548 (let ((proc (get-buffer-process (current-buffer))))
549 (when (processp proc)
550 (set-process-filter proc nil)
551 (set-process-sentinel proc nil)
552 (delete-process proc))))
553 nil t)
554
555 ;; create the new process and setup the procbuffer correspondingly
556 (let* ((args (append (cvs-flags-query 'cvs-cvs-flags nil 'noquery)
557 (if cvs-cvsroot (list "-d" cvs-cvsroot))
558 args
559 files))
560 (process-connection-type nil) ; Use a pipe, not a pty.
561 (process
562 ;; the process will be run in the selected dir
563 (let ((default-directory (cvs-expand-dir-name dir)))
564 (apply 'start-process "cvs" procbuf cvs-program args))))
565 (set-process-sentinel process 'cvs-sentinel)
566 (set-process-filter process 'cvs-update-filter)
567 (set-marker (process-mark process) (point-max))
568 (ignore-errors (process-send-eof process)) ;close its stdin to avoid hangs
569
570 ;; now finish setting up the cvs-buffer
571 (set-buffer cvsbuf)
572 (setq cvs-mode-line-process (symbol-name (process-status process)))
573 (force-mode-line-update)))))
574
575 ;; The following line is said to improve display updates on some
576 ;; emacsen. It shouldn't be needed, but it does no harm.
577 (sit-for 0))
578
579(defun cvs-update-header (args fis) ; inline
580 (let* ((lastarg nil)
581 ;; filter out the largish commit message
582 (args (mapcar (lambda (arg)
583 (cond
584 ((and (eq lastarg nil) (string= arg "commit"))
585 (setq lastarg 'commit) arg)
586 ((and (eq lastarg 'commit) (string= arg "-m"))
587 (setq lastarg '-m) arg)
588 ((eq lastarg '-m)
589 (setq lastarg 'done) "<log message>")
590 (t arg)))
591 args))
592 ;; turn them into a string
593 (arg (cvs-strings->string
594 (append (cvs-flags-query 'cvs-cvs-flags nil 'noquery)
595 (if cvs-cvsroot (list "-d" cvs-cvsroot))
596 args
597 (mapcar 'cvs-fileinfo->full-path fis))))
598 (str (if args (concat "-- Running " cvs-program " " arg " ...\n")
599 "\n")))
600 (if nil (insert str) ;inline
601 ;;(with-current-buffer cvs-buffer
602 (let* ((tin0 (ewoc-nth cvs-cookies 0))
603 (tin-1 (ewoc-nth cvs-cookies -1))
604 (header (ewoc-data tin0))
605 (footer (ewoc-data tin-1))
606 (prev-msg (cvs-fileinfo->full-log header))
607 (tin tin0))
608 (assert (and (eq 'HEADER (cvs-fileinfo->subtype header))
609 (eq 'FOOTER (cvs-fileinfo->subtype footer))))
610 ;; look for the first *real* fileinfo (to determine emptyness)
611 (while
612 (and tin
613 (memq (cvs-fileinfo->type (ewoc-data tin))
614 '(MESSAGE DIRCHANGE)))
615 (setq tin (ewoc-next cvs-cookies tin)))
616 ;; cleanup the prev-msg
617 (when (string-match "Running \\(.*\\) ...\n" prev-msg)
618 (setq prev-msg
619 (concat
620 "-- last cmd: "
621 (match-string 1 prev-msg)
622 " --")))
623 ;; set the new header and footer
624 (setf (cvs-fileinfo->full-log header) str)
625 (setf (cvs-fileinfo->full-log footer)
626 (concat "\n--------------------- "
627 (if tin "End" "Empty")
628 " ---------------------\n"
629 prev-msg))
630 (ewoc-invalidate cvs-cookies tin0 tin-1)))));;)
631
632
633;;----------
634(defun cvs-sentinel (proc msg)
635 "Sentinel for the cvs update process.
636This is responsible for parsing the output from the cvs update when
637it is finished."
638 (when (memq (process-status proc) '(signal exit))
639 (if (null (buffer-name (process-buffer proc)))
640 ;;(set-process-buffer proc nil)
641 (error "cvs' process buffer was killed")
642 (let* ((obuf (current-buffer))
643 (procbuffer (process-buffer proc)))
644 (set-buffer (with-current-buffer procbuffer cvs-buffer))
645 (setq cvs-mode-line-process (symbol-name (process-status proc)))
646 (force-mode-line-update)
647 (set-buffer procbuffer)
648 (let ((cvs-postproc cvs-postprocess))
649 ;; Since the buffer and mode line will show that the
650 ;; process is dead, we can delete it now. Otherwise it
651 ;; will stay around until M-x list-processes.
652 (delete-process proc)
653 (setq cvs-postprocess nil)
654 ;; do the postprocessing like parsing and such
655 (save-excursion (eval cvs-postproc))
656 ;; check whether something is left
657 (unless cvs-postprocess
658 (buffer-enable-undo)
659 (with-current-buffer cvs-buffer
660 (cvs-update-header nil nil) ;FIXME: might need to be inline
661 (message "CVS process has completed"))))
662 ;; This might not even be necessary
663 (set-buffer obuf)))))
664
665;;----------
666(defun cvs-parse-process (dcd &optional subdir)
667 "FIXME: bad name, no doc"
668 (let* ((from-buf (current-buffer))
669 (fileinfos (cvs-parse-buffer 'cvs-parse-table dcd subdir))
670 (_ (set-buffer cvs-buffer))
671 last
672 (from-pt (point)))
673 ;; add the new fileinfos
674 (dolist (fi fileinfos)
675 (setq last (cvs-addto-collection cvs-cookies fi last)))
676 (cvs-cleanup-collection cvs-cookies
677 (eq cvs-auto-remove-handled t)
678 cvs-auto-remove-directories
679 nil)
680 ;; update the display (might be unnecessary)
681 (ewoc-refresh cvs-cookies)
682 ;; revert buffers if necessary
683 (when (and cvs-auto-revert (not dcd) (not cvs-from-vc))
684 (cvs-revert-if-needed fileinfos))
685 ;; get back to where we were. `save-excursion' doesn't seem to
686 ;; work in this case, probably because the buffer is reconstructed
687 ;; by the cookie code.
688 (goto-char from-pt)
689 (set-buffer from-buf)))
690
691(defmacro defun-cvs-mode (fun args docstring interact &rest body)
692 "Define a function to be used in a *cvs* buffer.
693This will look for a *cvs* buffer and execute BODY in it.
694Since the interactive arguments might need to be queried after
695switching to the *cvs* buffer, the generic code is rather ugly,
696but luckily we can often use simpler alternatives.
697
698FUN can be either a symbol (i.e. STYLE is nil) or a cons (FUN . STYLE).
699ARGS and DOCSTRING are the normal argument list.
700INTERACT is the interactive specification or nil for non-commands.
701
702STYLE can be either SIMPLE, NOARGS or DOUBLE. It's an error for it
703to have any other value, unless other details of the function make it
704clear what alternative to use.
705- SIMPLE will get all the interactive arguments from the original buffer.
706- NOARGS will get all the arguments from the *cvs* buffer and will
707 always behave as if called interactively.
708- DOUBLE is the generic case."
709 (let ((style (cvs-cdr fun))
710 (fun (cvs-car fun)))
711 (cond
712 ;; a trivial interaction, no need to move it
713 ((or (eq style 'SIMPLE)
714 (null (second interact))
715 (stringp (second interact)))
716 `(defun ,fun ,args ,docstring ,interact
717 (cvs-mode! (lambda () ,@body))))
718
719 ;; fun is only called interactively: move all the args to the inner fun
720 ((eq style 'NOARGS)
721 `(defun ,fun () ,docstring (interactive)
722 (cvs-mode! (lambda ,args ,interact ,@body))))
723
724 ;; bad case
725 ((eq style 'DOUBLE)
726 (string-match ".*" docstring)
727 (let ((line1 (match-string 0 docstring))
728 (restdoc (substring docstring (match-end 0)))
729 (fun-1 (intern (concat (symbol-name fun) "-1"))))
730 `(progn
731 (defun ,fun-1 ,args
732 ,(concat docstring "\nThis function only works within a *cvs* buffer.
733For interactive use, use `" (symbol-name fun) "' instead.")
734 ,interact
735 ,@body)
736 (defun ,fun ()
737 ,(concat line1 "\nWrapper function that switches to a *cvs* buffer
738before calling the real function `" (symbol-name fun-1) "'.\n")
739 (interactive)
740 (cvs-mode! ',fun-1)))))
741
742 (t (error "unknown style %s in `defun-cvs-mode'" style)))))
743(def-edebug-spec defun-cvs-mode (&define sexp lambda-list stringp ("interactive" interactive) def-body))
744
745(defun-cvs-mode cvs-mode-kill-process ()
746 "Kill the temporary buffer and associated process."
747 (interactive)
748 (when (and (bufferp cvs-temp-buffer) (buffer-name cvs-temp-buffer))
749 (let ((proc (get-buffer-process cvs-temp-buffer)))
750 (when proc (delete-process proc)))))
751
752;;;
753;;; Maintaining the collection in the face of updates
754;;;
755
756(defun cvs-addto-collection (c fi &optional tin)
757 "Add FI to C and return a tin.
758FI is inserted in its proper place or maybe even merged with a preexisting
759 fileinfo if applicable.
760TIN specifies an optional starting point."
761 (unless tin (setq tin (ewoc-nth c 0)))
762 (while (and tin (cvs-fileinfo< fi (ewoc-data tin)))
763 (setq tin (ewoc-prev c tin)))
764 (if (null tin) (progn (ewoc-enter-first c fi) nil) ;empty collection
765 (assert (not (cvs-fileinfo< fi (ewoc-data tin))))
766 (let ((next-tin (ewoc-next c tin)))
767 (while (not (or (null next-tin)
768 (cvs-fileinfo< fi (ewoc-data next-tin))))
769 (setq tin next-tin next-tin (ewoc-next c next-tin)))
770 (if (cvs-fileinfo< (ewoc-data tin) fi)
771 ;; tin < fi < next-tin
772 (ewoc-enter-after c tin fi)
773 ;; fi == tin
774 (cvs-fileinfo-update (ewoc-data tin) fi)
775 (ewoc-invalidate c tin))
776 tin)))
777
778;; called at the following times:
779;; - postparse ((eq cvs-auto-remove-handled t) cvs-auto-remove-directories nil)
780;; - pre-run ((eq cvs-auto-remove-handled 'delayed) nil t)
781;; - remove-handled (t (or cvs-auto-remove-directories 'handled) t)
782;; - cvs-cmd-do (nil nil t)
783;; - post-ignore (nil nil nil)
784;; - acknowledge (nil nil nil)
785;; - remove (nil nil nil)
786(defun cvs-cleanup-collection (c rm-handled rm-dirs rm-msgs)
787 "Remove undesired entries.
788C is the collection
789RM-HANDLED if non-nil means remove handled entries.
790RM-DIRS behaves like `cvs-auto-remove-directories'.
791RM-MSGS if non-nil means remove messages."
792 (let (last-fi first-dir (rerun t))
793 (while rerun
794 (setq rerun nil)
795 (setq first-dir t)
796 (setq last-fi (cvs-create-fileinfo 'DEAD "../" "" "")) ;place-holder
797 (ewoc-filter
798 c (lambda (fi)
799 (let* ((type (cvs-fileinfo->type fi))
800 (subtype (cvs-fileinfo->subtype fi))
801 (keep
802 (case type
803 ;; remove temp messages and keep the others
804 (MESSAGE
805 (or (memq subtype '(HEADER FOOTER))
806 (not (or rm-msgs (eq subtype 'TEMP)))))
807 ;; remove entries
808 (DEAD nil)
809 ;; handled also?
810 (UP-TO-DATE (not rm-handled))
811 ;; keep the rest
812 (t t))))
813
814 ;; mark dirs for removal
815 (when (and keep rm-dirs
816 (eq (cvs-fileinfo->type last-fi) 'DIRCHANGE)
817 (not (when first-dir (setq first-dir nil) t))
818 (or (eq rm-dirs 'all)
819 (not (cvs-string-prefix-p
820 (cvs-fileinfo->dir last-fi)
821 (cvs-fileinfo->dir fi)))
822 (and (eq type 'DIRCHANGE) (eq rm-dirs 'empty))
823 (eq subtype 'FOOTER)))
824 (setf (cvs-fileinfo->type last-fi) 'DEAD)
825 (setq rerun t))
826 (when keep (setq last-fi fi))))))))
827
828(defun cvs-get-cvsroot ()
829 "Gets the CVSROOT for DIR."
830 (let ((cvs-cvsroot-file (expand-file-name "Root" "CVS")))
831 (or (cvs-file-to-string cvs-cvsroot-file t)
832 cvs-cvsroot
833 (getenv "CVSROOT")
834 "?????")))
835
836(defun cvs-get-module ()
837 "Return the current CVS module.
838This usually doesn't really work but is a handy initval in a prompt."
839 (let* ((repfile (expand-file-name "Repository" "CVS"))
840 (rep (cvs-file-to-string repfile t)))
841 (cond
842 ((null rep) "")
843 ((not (file-name-absolute-p rep)) rep)
844 (t
845 (let* ((root (cvs-get-cvsroot))
846 (str (concat (file-name-as-directory (or root "/")) " || " rep)))
847 (if (and root (string-match "\\(.*\\) || \\1\\(.*\\)\\'" str))
848 (match-string 2 str)
849 (file-name-nondirectory rep)))))))
850
851
852
853;;;;
854;;;; running a "cvs checkout".
855;;;;
856
857;;;###autoload
858(defun cvs-checkout (modules dir flags)
859 "Run a 'cvs checkout MODULES' in DIR.
860Feed the output to a *cvs* buffer, display it in the current window,
861and run `cvs-mode' on it.
862
863With a prefix argument, prompt for cvs FLAGS to use."
864 (interactive
865 (list (cvs-string->strings (read-string "Module(s): " (cvs-get-module)))
866 (read-file-name "CVS Checkout Directory: "
867 nil default-directory nil)
868 (cvs-add-branch-prefix
869 (cvs-flags-query 'cvs-checkout-flags "cvs checkout flags"))))
870 (when (eq flags t)
871 (setf flags (cvs-flags-query 'cvs-checkout-flags nil 'noquery)))
872 (cvs-cmd-do "checkout" (or dir default-directory)
873 (append flags modules) nil 'new
874 :noexist t))
875
876
877;;;;
878;;;; The code for running a "cvs update" and friends in various ways.
879;;;;
880
881(defun-cvs-mode (cvs-mode-revert-buffer . SIMPLE)
882 (&optional ignore-auto noconfirm)
883 "Rerun cvs-examine on the current directory with the defauls flags."
884 (interactive)
885 (cvs-examine default-directory t))
886
887(defun cvs-query-directory (msg)
888 ;; last-command-char = ?\r hints that the command was run via M-x
889 (if (and (cvs-buffer-p)
890 (not current-prefix-arg)
891 (not (eq last-command-char ?\r)))
892 default-directory
893 (read-file-name msg nil default-directory nil)))
894
895
896;;;###autoload
897(defun cvs-examine (directory flags &optional noshow)
898 "Run a `cvs -n update' in the specified DIRECTORY.
899That is, check what needs to be done, but don't change the disc.
900Feed the output to a *cvs* buffer and run `cvs-mode' on it.
901With a prefix argument, prompt for a directory and cvs FLAGS to use.
902A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]),
903 prevents reuse of an existing *cvs* buffer.
904Optional argument NOSHOW if non-nil means not to display the buffer."
905 (interactive (list (cvs-query-directory "CVS Examine (directory): ")
906 (cvs-flags-query 'cvs-update-flags "cvs -n update flags")))
907 (when (eq flags t)
908 (setf flags (cvs-flags-query 'cvs-update-flags nil 'noquery)))
909 (cvs-cmd-do "update" directory flags nil
910 (> (prefix-numeric-value current-prefix-arg) 8)
911 :cvsargs '("-n")
912 :noshow noshow
913 :dont-change-disc t))
914
915
916;;;###autoload
917(defun cvs-update (directory flags)
918 "Run a `cvs update' in the current working DIRECTORY.
919Feed the output to a *cvs* buffer and run `cvs-mode' on it.
920With a prefix argument, prompt for a directory and cvs FLAGS to use.
921A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]),
922 prevents reuse of an existing *cvs* buffer."
923 (interactive (list (cvs-query-directory "CVS Update (directory): ")
924 (cvs-flags-query 'cvs-update-flags "cvs update flags")))
925 (when (eq flags t)
926 (setf flags (cvs-flags-query 'cvs-update-flags nil 'noquery)))
927 (cvs-cmd-do "update" directory flags nil
928 (> (prefix-numeric-value current-prefix-arg) 8)))
929
930
931;;;###autoload
932(defun cvs-status (directory flags &optional noshow)
933 "Run a `cvs status' in the current working DIRECTORY.
934Feed the output to a *cvs* buffer and run `cvs-mode' on it.
935With a prefix argument, prompt for a directory and cvs FLAGS to use.
936A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]),
937 prevents reuse of an existing *cvs* buffer.
938Optional argument NOSHOW if non-nil means not to display the buffer."
939 (interactive (list (cvs-query-directory "CVS Status (directory): ")
940 (cvs-flags-query 'cvs-status-flags "cvs status flags")))
941 (when (eq flags t)
942 (setf flags (cvs-flags-query 'cvs-status-flags nil 'noquery)))
943 (cvs-cmd-do "status" directory flags nil
944 (> (prefix-numeric-value current-prefix-arg) 8)
945 :noshow noshow :dont-change-disc t))
946
947;;----------
948(defun cvs-update-filter (proc string)
949 "Filter function for pcl-cvs.
950This function gets the output that CVS sends to stdout. It inserts
951the STRING into (process-buffer PROC) but it also checks if CVS is waiting
952for a lock file. If so, it inserts a message cookie in the *cvs* buffer."
953 (save-match-data
954 (with-current-buffer (process-buffer proc)
955 (let ((inhibit-read-only t))
956 (save-excursion
957 ;; Insert the text, moving the process-marker.
958 (goto-char (process-mark proc))
959 (insert string)
960 (set-marker (process-mark proc) (point))
961 ;; FIXME: Delete any old lock message
962 ;;(if (tin-nth cookies 1)
963 ;; (tin-delete cookies
964 ;; (tin-nth cookies 1)))
965 ;; Check if CVS is waiting for a lock.
966 (beginning-of-line 0) ;Move to beginning of last complete line.
967 (when (looking-at "^[ a-z]+: \\(.*waiting for .*lock in \\(.*\\)\\)$")
968 (let ((msg (match-string 1))
969 (lock (match-string 2)))
970 (with-current-buffer cvs-buffer
971 (set (make-local-variable 'cvs-lock-file) lock)
972 ;; display the lock situation in the *cvs* buffer:
973 (ewoc-enter-last
974 cvs-cookies
975 (cvs-create-fileinfo
976 'MESSAGE "" " "
977 (concat msg
978 (substitute-command-keys
979 "\n\t(type \\[cvs-mode-delete-lock] to delete it)"))
980 :subtype 'TEMP))
981 (pop-to-buffer (current-buffer))
982 (goto-char (point-max))
983 (beep)))))))))
984
985
986;;;;
987;;;; The cvs-mode and its associated commands.
988;;;;
989
990(cvs-prefix-define cvs-force-command "" "" '("/F") cvs-qtypedesc-string1)
991(defun-cvs-mode cvs-mode-force-command (arg)
992 "Force the next cvs command to operate on all the selected files.
993By default, cvs commands only operate on files on which the command
994\"makes sense\". This overrides the safety feature on the next cvs command.
995It actually behaves as a toggle. If prefixed by \\[universal-argument] \\[universal-argument],
996the override will persist until the next toggle."
997 (interactive "P")
998 (cvs-prefix-set 'cvs-force-command arg))
999
1000;;----------
1001(put 'cvs-mode 'mode-class 'special)
1002(easy-mmode-define-derived-mode cvs-mode fundamental-mode "CVS"
1003 "Mode used for PCL-CVS, a frontend to CVS.
1004Full documentation is in the Texinfo file.
1005Pcl-cvs runs `pcl-cvs-load-hook' after being loaded."
1006 (setq mode-line-process
1007 '("" cvs-force-command cvs-ignore-marks-modif
1008 ":" (cvs-branch-prefix
1009 ("" cvs-branch-prefix (cvs-secondary-branch-prefix
1010 ("->" cvs-secondary-branch-prefix))))
1011 " " cvs-mode-line-process))
1012 (buffer-disable-undo (current-buffer))
1013 ;;(set (make-local-variable 'goal-column) cvs-cursor-column)
1014 (set (make-local-variable 'revert-buffer-function) 'cvs-mode-revert-buffer)
1015 (cvs-prefix-make-local 'cvs-branch-prefix)
1016 (cvs-prefix-make-local 'cvs-secondary-branch-prefix)
1017 (cvs-prefix-make-local 'cvs-force-command)
1018 (cvs-prefix-make-local 'cvs-ignore-marks-modif)
1019 (make-local-variable 'cvs-mode-line-process)
1020 (make-local-variable 'cvs-temp-buffers))
1021
1022
1023(defun cvs-buffer-p (&optional buffer)
1024 "Return whether the (by default current) BUFFER is a `cvs-mode' buffer."
1025 (save-excursion
1026 (if buffer (set-buffer buffer))
1027 (and (eq major-mode 'cvs-mode))))
1028
1029(defun cvs-buffer-check ()
1030 "Check that the current buffer follows cvs-buffer's conventions."
1031 (let ((buf (current-buffer))
1032 (check 'none))
1033 (or (and (setq check 'collection)
1034 (eq (ewoc-buffer cvs-cookies) buf)
1035 (setq check 'cvs-temp-buffer)
1036 (or (null cvs-temp-buffer)
1037 (null (buffer-name cvs-temp-buffer))
1038 (and (eq (with-current-buffer cvs-temp-buffer cvs-buffer) buf)
1039 (equal (with-current-buffer cvs-temp-buffer
1040 default-directory)
1041 default-directory)))
1042 t)
1043 (error "Inconsistent %s in buffer %s" check (buffer-name buf)))))
1044
1045
1046(defun-cvs-mode cvs-mode-quit ()
1047 "Quit PCL-CVS, killing the *cvs* buffer."
1048 (interactive)
1049 (and (y-or-n-p "Quit pcl-cvs? ") (kill-buffer (current-buffer))))
1050
1051;; Give help....
1052
1053(defun cvs-help ()
1054 "Display help for various PCL-CVS commands."
1055 (interactive)
1056 (if (eq last-command 'cvs-help)
1057 (describe-function 'cvs-mode) ; would need to use minor-mode for cvs-edit-mode
1058 (message
1059 (substitute-command-keys
1060 "`\\[cvs-help]':help `\\[cvs-mode-add]':add `\\[cvs-mode-commit]':commit \
1061`\\[cvs-mode-diff-map]':diff* `\\[cvs-mode-log]':log \
1062`\\[cvs-mode-remove]':remove `\\[cvs-mode-status]':status \
1063`\\[cvs-mode-undo]':undo"))))
1064
1065(defun cvs-mode-diff-help ()
1066 "Display help for various PCL-CVS diff commands."
1067 (interactive)
1068 (if (eq last-command 'cvs-mode-diff-help)
1069 (describe-function 'cvs-mode) ; no better docs for diff stuff?
1070 (message
1071 (substitute-command-keys
1072 "`\\[cvs-mode-diff]':diff `\\[cvs-mode-idiff]':idiff \
1073`\\[cvs-mode-diff-head]':head `\\[cvs-mode-diff-vendor]':vendor \
1074`\\[cvs-mode-diff-backup]':backup `\\[cvs-mode-idiff-other]':other \
1075`\\[cvs-mode-imerge]':imerge"))))
1076
1077;; Move around in the buffer
1078
1079(defun-cvs-mode cvs-mode-previous-line (arg)
1080 "Go to the previous line.
1081If a prefix argument is given, move by that many lines."
1082 (interactive "p")
1083 (ewoc-goto-prev cvs-cookies (point) arg))
1084
1085(defun-cvs-mode cvs-mode-next-line (arg)
1086 "Go to the next line.
1087If a prefix argument is given, move by that many lines."
1088 (interactive "p")
1089 (ewoc-goto-next cvs-cookies (point) arg))
1090
1091;;;;
1092;;;; Mark handling
1093;;;;
1094
1095(defun-cvs-mode cvs-mode-mark (&optional arg)
1096 "Mark the fileinfo on the current line.
1097If the fileinfo is a directory, all the contents of that directory are
1098marked instead. A directory can never be marked."
1099 (interactive)
1100 (let* ((tin (ewoc-locate cvs-cookies (point)))
1101 (fi (ewoc-data tin)))
1102 (if (eq (cvs-fileinfo->type fi) 'DIRCHANGE)
1103 ;; it's a directory: let's mark all files inside
1104 (ewoc-map
1105 (lambda (f dir)
1106 (when (cvs-dir-member-p f dir)
1107 (setf (cvs-fileinfo->marked f)
1108 (not (if (eq arg 'toggle) (cvs-fileinfo->marked f) arg)))
1109 t)) ;Tell cookie to redisplay this cookie.
1110 cvs-cookies
1111 (cvs-fileinfo->dir fi))
1112 ;; not a directory: just do the obvious
1113 (setf (cvs-fileinfo->marked fi)
1114 (not (if (eq arg 'toggle) (cvs-fileinfo->marked fi) arg)))
1115 (ewoc-invalidate cvs-cookies tin)
1116 (cvs-mode-next-line 1))))
1117
1118(defun cvs-mouse-toggle-mark (e)
1119 "Toggle the mark of the entry under the mouse."
1120 (interactive "e")
1121 (mouse-set-point e)
1122 (cvs-mode-mark 'toggle))
1123
1124(defun-cvs-mode cvs-mode-unmark ()
1125 "Unmark the fileinfo on the current line."
1126 (interactive)
1127 (cvs-mode-mark t))
1128
1129(defun-cvs-mode cvs-mode-mark-all-files ()
1130 "Mark all files."
1131 (interactive)
1132 (ewoc-map (lambda (cookie)
1133 (unless (eq (cvs-fileinfo->type cookie) 'DIRCHANGE)
1134 (setf (cvs-fileinfo->marked cookie) t)))
1135 cvs-cookies))
1136
1137(defun-cvs-mode cvs-mode-mark-matching-files (regex)
1138 "Mark all files matching REGEX."
1139 (interactive "sMark files matching: ")
1140 (ewoc-map (lambda (cookie)
1141 (when (and (not (eq (cvs-fileinfo->type cookie) 'DIRCHANGE))
1142 (string-match regex (cvs-fileinfo->file cookie)))
1143 (setf (cvs-fileinfo->marked cookie) t)))
1144 cvs-cookies))
1145
1146(defun-cvs-mode cvs-mode-unmark-all-files ()
1147 "Unmark all files.
1148Directories are also unmarked, but that doesn't matter, since
1149they should always be unmarked."
1150 (interactive)
1151 (ewoc-map (lambda (cookie)
1152 (setf (cvs-fileinfo->marked cookie) nil)
1153 t)
1154 cvs-cookies))
1155
1156(defun-cvs-mode cvs-mode-unmark-up ()
1157 "Unmark the file on the previous line."
1158 (interactive)
1159 (let ((tin (ewoc-goto-prev cvs-cookies (point) 1)))
1160 (when tin
1161 (setf (cvs-fileinfo->marked (ewoc-data tin)) nil)
1162 (ewoc-invalidate cvs-cookies tin))))
1163
1164(defconst cvs-ignore-marks-alternatives
1165 '(("toggle-marks" . "/TM")
1166 ("force-marks" . "/FM")
1167 ("ignore-marks" . "/IM")))
1168
1169(cvs-prefix-define cvs-ignore-marks-modif
1170 "Prefix to decide whether to ignore marks or not."
1171 "active"
1172 (mapcar 'cdr cvs-ignore-marks-alternatives)
1173 (cvs-qtypedesc-create
1174 (lambda (str) (cdr (assoc str cvs-ignore-marks-alternatives)))
1175 (lambda (obj) (caar (member* obj cvs-ignore-marks-alternatives :key 'cdr)))
1176 (lambda () cvs-ignore-marks-alternatives)
1177 nil t))
1178
1179(defun-cvs-mode cvs-mode-toggle-marks (arg)
1180 "Toggle whether the next CVS command uses marks.
1181See `cvs-prefix-set' for further description of the behavior.
1182\\[universal-argument] 1 selects `force-marks',
1183\\[universal-argument] 2 selects `ignore-marks',
1184\\[universal-argument] 3 selects `toggle-marks'."
1185 (interactive "P")
1186 (cvs-prefix-set 'cvs-ignore-marks-modif arg))
1187
1188(defun cvs-ignore-marks-p (cmd &optional read-only)
1189 (let ((default (if (member cmd cvs-invert-ignore-marks)
1190 (not cvs-default-ignore-marks)
1191 cvs-default-ignore-marks))
1192 (modif (cvs-prefix-get 'cvs-ignore-marks-modif read-only)))
1193 (cond
1194 ((equal modif "/IM") t)
1195 ((equal modif "/TM") (not default))
1196 ((equal modif "/FM") nil)
1197 (t default))))
1198
1199(defun cvs-mode-mark-get-modif (cmd)
1200 (if (cvs-ignore-marks-p cmd 'read-only) "/IM" "/FM"))
1201
1202(defvar cvs-minor-current-files)
1203(defun cvs-get-marked (&optional ignore-marks ignore-contents)
1204 "Return a list of all selected fileinfos.
1205If there are any marked tins, and IGNORE-MARKS is nil, return them.
1206Otherwise, if the cursor selects a directory, and IGNORE-CONTENTS is
1207nil, return all files in it, else return just the directory.
1208Otherwise return (a list containing) the file the cursor points to, or
1209an empty list if it doesn't point to a file at all.
1210
1211Args: &optional IGNORE-MARKS IGNORE-CONTENTS."
1212
1213 (let ((fis nil))
1214 (dolist (fi (if (boundp 'cvs-minor-current-files)
1215 (mapcar
1216 (lambda (f)
1217 (let ((f (file-relative-name f)))
1218 (if (file-directory-p f)
1219 (cvs-create-fileinfo
1220 'DIRCHANGE (file-name-as-directory f) "." "")
1221 (let ((dir (file-name-directory f))
1222 (file (file-name-nondirectory f)))
1223 (cvs-create-fileinfo
1224 'UNKNOWN (or dir "") file "")))))
1225 cvs-minor-current-files)
1226 (or (and (not ignore-marks)
1227 (ewoc-collect cvs-cookies
1228 'cvs-fileinfo->marked))
1229 (list (ewoc-data (ewoc-locate cvs-cookies (point)))))))
1230
1231 (if (or ignore-contents (not (eq (cvs-fileinfo->type fi) 'DIRCHANGE)))
1232 (push fi fis)
1233 ;; If a directory is selected, return members, if any.
1234 (setq fis
1235 (append (ewoc-collect cvs-cookies
1236 'cvs-dir-member-p
1237 (cvs-fileinfo->dir fi))
1238 fis))))
1239 (nreverse fis)))
1240
1241(defun* cvs-mode-marked (filter &optional (cmd (symbol-name filter))
1242 &key read-only one file)
1243 "Get the list of marked FIS.
1244CMD is used to determine whether to use the marks or not.
1245Only files for which FILTER is applicable are returned.
1246If READ-ONLY is non-nil, the current toggling is left intact.
1247If ONE is non-nil, marks are ignored and a single FI is returned.
1248If FILE is non-nil, directory entries won't be selected."
1249 (let* ((fis (cvs-get-marked (or one (cvs-ignore-marks-p cmd read-only))
1250 (and (not file)
1251 (cvs-applicable-p 'DIRCHANGE filter))))
1252 (force (cvs-prefix-get 'cvs-force-command))
1253 (fis (car (cvs-partition
1254 (lambda (fi) (cvs-applicable-p fi (and (not force) filter)))
1255 fis))))
1256 (cond
1257 ((null fis)
1258 (error "`%s' is not applicable to any of the selected files." filter))
1259 ((and one (cdr fis))
1260 (error "`%s' is only applicable to a single file." cmd))
1261 (one (car fis))
1262 (t fis))))
1263
1264(defun cvs-enabledp (filter)
1265 "Determine whether FILTER applies to at least one of the selected files."
1266 (ignore-errors (cvs-mode-marked filter nil :read-only t)))
1267
1268(defun cvs-mode-files (&rest -cvs-mode-files-args)
1269 (cvs-mode!
1270 (lambda ()
1271 (mapcar 'cvs-fileinfo->full-path
1272 (apply 'cvs-mode-marked -cvs-mode-files-args)))))
1273
1274;;;
1275;;; Interface between CVS-Edit and PCL-CVS
1276;;;
1277
1278(defun cvs-mode-commit-setup ()
1279 "Run `cvs-mode-commit' with setup."
1280 (interactive)
1281 (cvs-mode-commit 'force))
1282
1283(defun cvs-mode-commit (setup)
1284 "Check in all marked files, or the current file.
1285The user will be asked for a log message in a buffer.
1286The buffer's mode and name is determined by the \"message\" setting
1287 of `cvs-buffer-name-alist'.
1288The POSTPROC specified there (typically `cvs-edit') is then called,
1289 passing it the SETUP argument."
1290 (interactive "P")
1291 ;; It seems that the save-excursion that happens if I use the better
1292 ;; form of `(cvs-mode! (lambda ...))' screws up a couple things which
1293 ;; end up being rather annoying (like cvs-edit-mode's message being
1294 ;; displayed in the wrong minibuffer).
1295 (cvs-mode!)
1296 (pop-to-buffer (cvs-temp-buffer "message" 'normal 'nosetup))
1297 (set (make-local-variable 'cvs-minor-wrap-function) 'cvs-commit-minor-wrap)
1298 (let ((lbd list-buffers-directory)
1299 (setupfun (or (third (cdr (assoc "message" cvs-buffer-name-alist)))
1300 'cvs-edit)))
1301 (funcall setupfun 'cvs-do-commit setup 'cvs-commit-filelist)
1302 (set (make-local-variable 'list-buffers-directory) lbd)))
1303
1304(defun cvs-commit-minor-wrap (buf f)
1305 (let ((cvs-ignore-marks-modif (cvs-mode-mark-get-modif "commit")))
1306 (funcall f)))
1307
1308(defun cvs-commit-filelist () (cvs-mode-files 'commit nil :read-only t :file t))
1309
1310(defun cvs-do-commit (flags)
1311 "Do the actual commit, using the current buffer as the log message."
1312 (interactive (list (cvs-flags-query 'cvs-commit-flags "cvs commit flags")))
1313 (let ((msg (buffer-string)))
1314 (cvs-mode!)
1315 ;;(pop-to-buffer cvs-buffer)
1316 (cvs-mode-do "commit" (list* "-m" msg flags) 'commit)))
1317
1318
1319;;;;
1320;;;; CVS Mode commands
1321;;;;
1322
1323(defun-cvs-mode (cvs-mode-insert . NOARGS) (file)
1324 "Insert an entry for a specific file."
1325 (interactive
1326 (list (read-file-name "File to insert: " nil nil nil
1327 (ignore-errors
1328 (cvs-fileinfo->dir
1329 (car (cvs-mode-marked nil nil :read-only t)))))))
1330 (let ((file (file-relative-name (directory-file-name file))))
1331 (if (file-directory-p file)
1332 (let ((fi (cvs-create-fileinfo 'DIRCHANGE
1333 (file-name-as-directory file)
1334 "."
1335 "cvs-mode-insert")))
1336 (cvs-addto-collection cvs-cookies fi))
1337 (let ((fi (cvs-create-fileinfo 'UNKNOWN
1338 (or (file-name-directory file) "")
1339 (file-name-nondirectory file)
1340 "cvs-mode-insert")))
1341 (cvs-mode-run "status" (cvs-flags-query 'cvs-status-flags nil 'noquery)
1342 (list fi) :dont-change-disc t)))))
1343
1344(defun-cvs-mode (cvs-mode-add . SIMPLE) (flags)
1345 "Add marked files to the cvs repository.
1346With prefix argument, prompt for cvs flags."
1347 (interactive (list (cvs-flags-query 'cvs-add-flags "cvs add flags")))
1348 (let ((fis (cvs-mode-marked 'add))
1349 (needdesc nil) (dirs nil))
1350 ;; find directories and look for fis needing a description
1351 (dolist (fi fis)
1352 (cond
1353 ((file-directory-p (cvs-fileinfo->full-path fi)) (push fi dirs))
1354 ((eq (cvs-fileinfo->type fi) 'UNKNOWN) (setq needdesc t))))
1355 ;; prompt for description if necessary
1356 (let* ((msg (if (and needdesc
1357 (or current-prefix-arg (not cvs-add-default-message)))
1358 (read-from-minibuffer "Enter description: ")
1359 (or cvs-add-default-message "")))
1360 (flags (list* "-m" msg flags))
1361 (postproc
1362 ;; setup postprocessing for the directory entries
1363 (when dirs
1364 `((cvs-run-process (list "-n" "update")
1365 ',dirs
1366 '(cvs-parse-process t))
1367 (dolist (fi ',dirs) (setf (cvs-fileinfo->type fi) 'DEAD))))))
1368 (cvs-mode-run "add" flags fis :postproc postproc))))
1369
1370;;----------
1371(defun-cvs-mode (cvs-mode-diff . DOUBLE) (flags)
1372 "Diff the selected files against the repository.
1373This command compares the files in your working area against the
1374revision which they are based upon."
1375 (interactive
1376 (list (cvs-add-branch-prefix
1377 (cvs-add-secondary-branch-prefix
1378 (cvs-flags-query 'cvs-diff-flags "cvs diff flags")))))
1379 (cvs-mode-do "diff" flags 'diff
1380 :show t)) ;; :ignore-exit t
1381
1382;;----------
1383(defun-cvs-mode (cvs-mode-diff-head . SIMPLE) (flags)
1384 "Diff the selected files against the head of the current branch.
1385See ``cvs-mode-diff'' for more info."
1386 (interactive (list (cvs-flags-query 'cvs-diff-flags "cvs diff flags")))
1387 (cvs-mode-diff-1 (cons "-rHEAD" flags)))
1388
1389;;----------
1390(defun-cvs-mode (cvs-mode-diff-vendor . SIMPLE) (flags)
1391 "Diff the selected files against the head of the vendor branch.
1392See ``cvs-mode-diff'' for more info."
1393 (interactive (list (cvs-flags-query 'cvs-diff-flags "cvs diff flags")))
1394 (cvs-mode-diff-1 (cons (concat "-r" cvs-vendor-branch) flags)))
1395
1396;;----------
1397;; sadly, this is not provided by cvs, so we have to roll our own
1398(defun-cvs-mode (cvs-mode-diff-backup . SIMPLE) (flags)
1399 "Diff the files against the backup file.
1400This command can be used on files that are marked with \"Merged\"
1401or \"Conflict\" in the *cvs* buffer."
1402 (interactive (list (cvs-flags-query 'cvs-diff-flags "diff flags")))
1403 (unless (listp flags) (error "flags should be a list of strings."))
1404 (save-some-buffers)
1405 (let* ((filter 'diff)
1406 (marked (cvs-get-marked (cvs-ignore-marks-p "diff")))
1407 ;;(tins (cvs-filter-applicable filter marked))
1408 (fis (delete-if-not 'cvs-fileinfo->backup-file marked)))
1409 (unless (consp fis)
1410 (error "No files with a backup file selected!"))
1411 ;; let's extract some info into the environment for `buffer-name'
1412 (let* ((dir (cvs-fileinfo->dir (car fis)))
1413 (file (cvs-fileinfo->file (car fis))))
1414 (set-buffer (cvs-temp-buffer "diff")))
1415 (message "cvs diff backup...")
1416 (cvs-execute-single-file-list fis 'cvs-diff-backup-extractor
1417 cvs-diff-program flags))
1418 (message "cvs diff backup... Done."))
1419
1420;;----------
1421;; (defun cvs-backup-diffable-p (fi)
1422;; "Check if the TIN is backup-diffable.
1423;; It must have a backup file to be diffable."
1424;; (cvs-fileinfo->backup-file fi))
1425
1426;;----------
1427(defun cvs-diff-backup-extractor (fileinfo)
1428 "Return the filename and the name of the backup file as a list.
1429Signal an error if there is no backup file."
1430 (let ((backup-file (cvs-fileinfo->backup-file fileinfo)))
1431 (unless backup-file
1432 (error "%s has no backup file." (cvs-fileinfo->full-path fileinfo)))
1433 (list backup-file (cvs-fileinfo->file fileinfo))))
1434
1435;;
1436;; Emerge support
1437;;
1438(defun cvs-emerge-diff (b1 b2) (emerge-buffers b1 b2 b1))
1439(defun cvs-emerge-merge (b1 b2 base out)
1440 (emerge-buffers-with-ancestor b1 b2 base (find-file-noselect out)))
1441
1442;;
1443;; Ediff support
1444;;
1445
1446(defvar ediff-after-quit-destination-buffer)
1447(defvar cvs-transient-buffers)
1448(defun cvs-ediff-startup-hook ()
1449 (add-hook 'ediff-after-quit-hook-internal
1450 `(lambda ()
1451 (cvs-ediff-exit-hook
1452 ',ediff-after-quit-destination-buffer ',cvs-transient-buffers))
1453 nil 'local))
1454
1455(defun cvs-ediff-exit-hook (cvs-buf tmp-bufs)
1456 ;; kill the temp buffers (and their associated windows)
1457 (dolist (tb tmp-bufs)
1458 (when (and tb (buffer-live-p tb) (not (buffer-modified-p tb)))
1459 (let ((win (get-buffer-window tb t)))
1460 (when win (delete-window win))
1461 (kill-buffer tb))))
1462 ;; switch back to the *cvs* buffer
1463 (when (and cvs-buf (buffer-live-p cvs-buf)
1464 (not (get-buffer-window cvs-buf t)))
1465 (ignore-errors (switch-to-buffer cvs-buf))))
1466
1467(defun cvs-ediff-diff (b1 b2)
1468 (let ((ediff-after-quit-destination-buffer (current-buffer))
1469 (startup-hook '(cvs-ediff-startup-hook)))
1470 (ediff-buffers b1 b2 startup-hook 'ediff-revisions)))
1471
1472(defun cvs-ediff-merge (b1 b2 base out)
1473 (let ((ediff-after-quit-destination-buffer (current-buffer))
1474 (startup-hook '(cvs-ediff-startup-hook)))
1475 (ediff-merge-buffers-with-ancestor
1476 b1 b2 base startup-hook
1477 'ediff-merge-revisions-with-ancestor
1478 out)))
1479
1480;;
1481;; Interactive merge/diff support.
1482;;
1483
1484(defun cvs-retrieve-revision (fileinfo rev)
1485 "Retrieve the given REVision of the file in FILEINFO into a new buffer."
1486 (save-excursion
1487 (let* ((file (cvs-fileinfo->full-path fileinfo))
1488 (buf (create-file-buffer (concat file "." rev))))
1489 (set-buffer buf)
1490 (message "Retrieving revision %s..." rev)
1491 (let ((res (call-process cvs-program nil t nil
1492 "-q" "update" "-p" "-r" rev file)))
1493 (when (and res (not (and (equal 0 res))))
1494 (error "Something went wrong retrieving revision %s: %s" rev res))
1495 (set-buffer-modified-p nil)
1496 (let ((buffer-file-name (expand-file-name file)))
1497 (after-find-file))
1498 (toggle-read-only 1)
1499 (message "Retrieving revision %s... Done" rev)
1500 buf))))
1501
1502(eval-and-compile (autoload 'vc-resolve-conflicts "vc"))
1503
1504(defun-cvs-mode cvs-mode-imerge ()
1505 "Merge interactively appropriate revisions of the selected file."
1506 (interactive)
1507 (let ((fi (cvs-mode-marked 'merge nil :one t :file t)))
1508 (let ((merge (cvs-fileinfo->merge fi))
1509 (file (cvs-fileinfo->full-path fi))
1510 (backup-file (cvs-fileinfo->backup-file fi)))
1511 (if (not (and merge backup-file))
1512 (let ((buf (find-file-noselect file)))
1513 (message "Missing merge info or backup file, using VC.")
1514 (save-excursion
1515 (set-buffer buf)
1516 (vc-resolve-conflicts)))
1517 (let* ((ancestor-buf (cvs-retrieve-revision fi (car merge)))
1518 (head-buf (cvs-retrieve-revision fi (cdr merge)))
1519 (backup-buf (let ((auto-mode-alist nil))
1520 (find-file-noselect backup-file)))
1521 ;; this binding is used by cvs-ediff-startup-hook
1522 (cvs-transient-buffers (list ancestor-buf backup-buf head-buf)))
1523 (with-current-buffer backup-buf
1524 (let ((buffer-file-name (expand-file-name file)))
1525 (after-find-file)))
1526 (funcall (cdr cvs-idiff-imerge-handlers)
1527 backup-buf head-buf ancestor-buf file))))))
1528
1529(cvs-flags-define cvs-idiff-version
1530 (list "BASE" cvs-vendor-branch cvs-vendor-branch "BASE" "BASE")
1531 "version: " cvs-qtypedesc-tag)
1532
1533(defun-cvs-mode (cvs-mode-idiff . NOARGS) (&optional rev1 rev2)
1534 "Diff interactively current file to revisions."
1535 (interactive
1536 (let* ((rev1 (cvs-prefix-get 'cvs-branch-prefix))
1537 (rev2 (and rev1 (cvs-prefix-get 'cvs-secondary-branch-prefix))))
1538 (list (or rev1 (cvs-flags-query 'cvs-idiff-version))
1539 rev2)))
1540 (let ((fi (cvs-mode-marked 'diff "idiff" :one t :file t)))
1541 (let* ((file (cvs-fileinfo->full-path fi))
1542 (rev1-buf (cvs-retrieve-revision fi (or rev1 "BASE")))
1543 (rev2-buf (if rev2 (cvs-retrieve-revision fi rev2)))
1544 ;; this binding is used by cvs-ediff-startup-hook
1545 (cvs-transient-buffers (list rev1-buf rev2-buf)))
1546 (funcall (car cvs-idiff-imerge-handlers)
1547 rev1-buf (or rev2-buf (find-file-noselect file))))))
1548
1549(defun-cvs-mode (cvs-mode-idiff-other . NOARGS) ()
1550 "Diff interactively current file to revisions."
1551 (interactive)
1552 (let* ((rev1 (cvs-prefix-get 'cvs-branch-prefix))
1553 (rev2 (and rev1 (cvs-prefix-get 'cvs-secondary-branch-prefix)))
1554 (fis (cvs-mode-marked 'diff "idiff" :file t)))
1555 (when (> (length fis) 2)
1556 (error "idiff-other cannot be applied to more than 2 files at a time."))
1557 (let* ((fi1 (first fis))
1558 (rev1-buf (if rev1 (cvs-retrieve-revision fi1 rev1)
1559 (find-file-noselect (cvs-fileinfo->full-path fi1))))
1560 rev2-buf)
1561 (if (cdr fis)
1562 (let ((fi2 (second fis)))
1563 (setq rev2-buf
1564 (if rev2 (cvs-retrieve-revision fi2 rev2)
1565 (find-file-noselect (cvs-fileinfo->full-path fi2)))))
1566 (error "idiff-other doesn't know what other file/buffer to use."))
1567 (let* (;; this binding is used by cvs-ediff-startup-hook
1568 (cvs-transient-buffers (list rev1-buf rev2-buf)))
1569 (funcall (car cvs-idiff-imerge-handlers)
1570 rev1-buf rev2-buf)))))
1571
1572
1573(defun cvs-fileinfo-kill (c fi)
1574 "Mark a fileinfo xor its members (in case of a directory) as dead."
1575 (if (eq (cvs-fileinfo->type fi) 'DIRCHANGE)
1576 (dolist (fi (ewoc-collect c 'cvs-dir-member-p
1577 (cvs-fileinfo->dir fi)))
1578 (setf (cvs-fileinfo->type fi) 'DEAD))
1579 (setf (cvs-fileinfo->type fi) 'DEAD)))
1580
1581(defun* cvs-mode-run (cmd flags fis
1582 &key (buf (cvs-temp-buffer))
1583 dont-change-disc cvsargs postproc)
1584 "Generic cvs-mode-<foo> function.
1585Executes `cvs CVSARGS CMD FLAGS FIS'.
1586BUF is the buffer to be used for cvs' output.
1587DONT-CHANGE-DISC non-nil indicates that the command will not change the
1588 contents of files. This is only used by the parser.
1589POSTPROC is a list of expressions to be evaluated at the very end (after
1590 parsing if applicable). It will be prepended with `progn' is necessary."
1591 (save-some-buffers)
1592 (unless (listp flags) (error "flags should be a list of strings"))
1593 (let* ((cvs-buf (current-buffer))
1594 (single-dir (or (not (listp cvs-execute-single-dir))
1595 (member cmd cvs-execute-single-dir)))
1596 (parse (member cmd cvs-parse-known-commands))
1597 (args (append cvsargs (list cmd) flags))
1598 (after-mode (third (cdr (assoc cmd cvs-buffer-name-alist)))))
1599 (cvs-cleanup-collection cvs-cookies ;cleanup remaining messages
1600 (eq cvs-auto-remove-handled 'delayed) nil t)
1601 (when (fboundp after-mode)
1602 (setq postproc (append postproc `((,after-mode)))))
1603 (when parse (push `(cvs-parse-process ',dont-change-disc) postproc))
1604 (when (member cmd '("status" "update")) ;FIXME: Yuck!!
1605 ;; absence of `cvs update' output has a specific meaning.
1606 (push
1607 `(dolist (fi ',(or fis
1608 (list (cvs-create-fileinfo 'DIRCHANGE "" "." ""))))
1609 (cvs-fileinfo-kill ',cvs-cookies fi))
1610 postproc))
1611 (setq postproc (if (cdr postproc) (cons 'progn postproc) (car postproc)))
1612 (cvs-update-header args fis)
1613 (with-current-buffer buf
1614 ;;(set (make-local-variable 'cvs-buffer) cvs-buf)
1615 (let ((inhibit-read-only t)) (erase-buffer))
1616 (message "Running cvs %s ..." cmd)
1617 (cvs-run-process args fis postproc single-dir))))
1618
1619
1620(defun* cvs-mode-do (cmd flags filter
1621 &key show dont-change-disc parse cvsargs postproc)
1622 "Generic cvs-mode-<foo> function.
1623Executes `cvs CVSARGS CMD FLAGS' on the selected files.
1624FILTER is passed to `cvs-applicable-p' to only apply the command to
1625 files for which it makes sense.
1626SHOW indicates that CMD should be not be run in the default temp buffer and
1627 should be shown to the user. The buffer and mode to be used is determined
1628 by `cvs-buffer-name-alist'.
1629DONT-CHANGE-DISC non-nil indicates that the command will not change the
1630 contents of files. This is only used by the parser."
1631 (cvs-mode-run cmd flags (cvs-mode-marked filter cmd)
1632 :buf (cvs-temp-buffer (when show cmd))
1633 :dont-change-disc dont-change-disc
1634 :cvsargs cvsargs
1635 :postproc postproc))
1636
1637(defun-cvs-mode (cvs-mode-status . SIMPLE) (flags)
1638 "Show cvs status for all marked files.
1639With prefix argument, prompt for cvs flags."
1640 (interactive (list (cvs-flags-query 'cvs-status-flags "cvs status flags")))
1641 (cvs-mode-do "status" flags nil :dont-change-disc t :show t
1642 :postproc (when (eq cvs-auto-remove-handled 'status)
1643 '((with-current-buffer ,(current-buffer)
1644 (cvs-mode-remove-handled))))))
1645
1646(defun-cvs-mode (cvs-mode-tree . SIMPLE) (flags)
1647 "Call cvstree using the file under the point as a keyfile."
1648 (interactive (list (cvs-flags-query 'cvs-status-flags "cvs status flags")))
1649 (cvs-mode-run "status" (cons "-v" flags) (cvs-mode-marked nil "status")
1650 :buf (cvs-temp-buffer "tree")
1651 :dont-change-disc t
1652 :postproc '((cvs-status-trees))))
1653
1654;; cvs log
1655
1656(defun-cvs-mode (cvs-mode-log . NOARGS) (flags)
1657 "Display the cvs log of all selected files.
1658With prefix argument, prompt for cvs flags."
1659 (interactive (list (cvs-add-branch-prefix
1660 (cvs-flags-query 'cvs-log-flags "cvs log flags"))))
1661 (cvs-mode-do "log" flags nil :show t))
1662
1663
1664(defun-cvs-mode (cvs-mode-update . NOARGS) (flags)
1665 "Update all marked files.
1666With a prefix argument, prompt for cvs flags."
1667 (interactive
1668 (list (cvs-add-branch-prefix
1669 (cvs-add-secondary-branch-prefix
1670 (cvs-flags-query 'cvs-update-flags "cvs update flags")
1671 "-j") "-j")))
1672 (cvs-mode-do "update" flags 'update))
1673
1674
1675(defun-cvs-mode (cvs-mode-examine . NOARGS) (flags)
1676 "Re-examine all marked files.
1677With a prefix argument, prompt for cvs flags."
1678 (interactive
1679 (list (cvs-add-branch-prefix
1680 (cvs-add-secondary-branch-prefix
1681 (cvs-flags-query 'cvs-update-flags "cvs -n update flags")
1682 "-j") "-j")))
1683 (cvs-mode-do "update" flags nil :cvsargs '("-n") :dont-change-disc t))
1684
1685
1686(defun-cvs-mode cvs-mode-ignore (&optional pattern)
1687 "Arrange so that CVS ignores the selected files.
1688This command ignores files that are not flagged as `Unknown'."
1689 (interactive)
1690 (dolist (fi (cvs-mode-marked 'ignore))
1691 (cvs-append-to-ignore (cvs-fileinfo->dir fi) (cvs-fileinfo->file fi))
1692 (setf (cvs-fileinfo->type fi) 'DEAD))
1693 (cvs-cleanup-collection cvs-cookies nil nil nil))
1694
1695
1696(defun cvs-append-to-ignore (dir str)
1697 "Add STR to the .cvsignore file in DIR."
1698 (save-window-excursion
1699 (set-buffer (find-file-noselect (expand-file-name ".cvsignore" dir)))
1700 (when (ignore-errors
1701 (and buffer-read-only
1702 (eq 'CVS (vc-backend buffer-file-name))
1703 (not (vc-locking-user buffer-file-name))))
1704 ;; CVSREAD=on special case
1705 (vc-toggle-read-only))
1706 (goto-char (point-max))
1707 (unless (zerop (current-column)) (insert "\n"))
1708 (insert str "\n")
1709 (if cvs-sort-ignore-file (sort-lines nil (point-min) (point-max)))
1710 (save-buffer)))
1711
1712
1713(defun cvs-mode-find-file-other-window (e)
1714 "Select a buffer containing the file in another window."
1715 (interactive (list last-input-event))
1716 (cvs-mode-find-file e t))
1717
1718
1719(defun cvs-find-modif (fi)
1720 (with-temp-buffer
1721 (call-process cvs-program nil (current-buffer) nil
1722 "-f" "diff" (cvs-fileinfo->file fi))
1723 (goto-char (point-min))
1724 (if (re-search-forward "^\\([0-9]+\\)" nil t)
1725 (string-to-number (match-string 1))
1726 1)))
1727
1728
1729(defun cvs-mode-find-file (e &optional other)
1730 "Select a buffer containing the file.
1731With a prefix, opens the buffer in an OTHER window."
1732 (interactive (list last-input-event current-prefix-arg))
1733 (ignore-errors (mouse-set-point e)) ;for invocation via the mouse
1734 (cvs-mode!
1735 (lambda (&optional rev)
1736 (interactive (list (cvs-prefix-get 'cvs-branch-prefix)))
1737 (let* ((cvs-buf (current-buffer))
1738 (fi (cvs-mode-marked nil nil :one t)))
1739 (if (eq (cvs-fileinfo->type fi) 'DIRCHANGE)
1740 (let ((odir default-directory))
1741 (setq default-directory
1742 (cvs-expand-dir-name (cvs-fileinfo->dir fi)))
1743 (if other
1744 (dired-other-window default-directory)
1745 (dired default-directory))
1746 (set-buffer cvs-buf)
1747 (setq default-directory odir))
1748 (let ((buf (if rev (cvs-retrieve-revision fi rev)
1749 (find-file-noselect (cvs-fileinfo->full-path fi)))))
1750 (funcall (if other 'switch-to-buffer-other-window 'switch-to-buffer)
1751 buf)
1752 (when (and cvs-find-file-and-jump (cvs-applicable-p fi 'diff-base))
1753 (goto-line (cvs-find-modif fi)))
1754 buf))))))
1755
1756
1757(defun-cvs-mode (cvs-mode-undo . SIMPLE) (flags)
1758 "Undo local changes to all marked files.
1759The file is removed and `cvs update FILE' is run."
1760 ;;"With prefix argument, prompt for cvs FLAGS."
1761 (interactive (list nil));; (cvs-flags-query 'cvs-undo-flags "undo flags")
1762 (if current-prefix-arg (call-interactively 'cvs-mode-revert-to-rev)
1763 (let* ((fis (cvs-do-removal 'undo "update" 'all))
1764 (removedp (lambda (fi) (eq (cvs-fileinfo->type fi) 'REMOVED)))
1765 (fis-split (cvs-partition removedp fis))
1766 (fis-removed (car fis-split))
1767 (fis-other (cdr fis-split)))
1768 (if (null fis-other)
1769 (when fis-removed (cvs-mode-run "add" nil fis-removed))
1770 (cvs-mode-run "update" flags fis-other
1771 :postproc
1772 (when fis-removed
1773 `((with-current-buffer ,(current-buffer)
1774 (cvs-mode-run "add" nil ',fis-removed)))))))))
1775
1776
1777(defun-cvs-mode (cvs-mode-revert-to-rev . NOARGS) (rev)
1778 "Revert the selected files to an old revision."
1779 (interactive
1780 (list (or (cvs-prefix-get 'cvs-branch-prefix)
1781 (let ((current-prefix-arg '(4)))
1782 (cvs-flags-query 'cvs-idiff-version)))))
1783 (let* ((fis (cvs-mode-marked 'revert "revert" :file t))
1784 (tag (concat "tmp_pcl_tag_" (make-temp-name "")))
1785 (untag `((with-current-buffer ,(current-buffer)
1786 (cvs-mode-run "tag" (list "-d" ',tag) ',fis))))
1787 (update `((with-current-buffer ,(current-buffer)
1788 (cvs-mode-run "update" (list "-j" ',tag "-j" ',rev) ',fis
1789 :postproc ',untag)))))
1790 (cvs-mode-run "tag" (list tag) fis :postproc update)))
1791
1792
1793(defun-cvs-mode cvs-mode-delete-lock ()
1794 "Delete the lock file that CVS is waiting for.
1795Note that this can be dangerous. You should only do this
1796if you are convinced that the process that created the lock is dead."
1797 (interactive)
1798 (let* ((default-directory (cvs-expand-dir-name cvs-lock-file))
1799 (locks (directory-files default-directory nil cvs-lock-file-regexp)))
1800 (cond
1801 ((not locks) (error "No lock files found."))
1802 ((yes-or-no-p (concat "Really delete locks in " cvs-lock-file "? "))
1803 (dolist (lock locks)
1804 (cond ((file-directory-p lock) (delete-directory lock))
1805 ((file-exists-p lock) (delete-file lock))))))))
1806
1807
1808(defun-cvs-mode cvs-mode-remove-handled ()
1809 "Remove all lines that are handled.
1810Empty directories are removed."
1811 (interactive)
1812 (cvs-cleanup-collection cvs-cookies
1813 t (or cvs-auto-remove-directories 'handled) t))
1814
1815
1816(defun-cvs-mode cvs-mode-acknowledge ()
1817 "Remove all marked files from the buffer."
1818 (interactive)
1819 (dolist (fi (cvs-get-marked (cvs-ignore-marks-p "acknowledge") t))
1820 (setf (cvs-fileinfo->type fi) 'DEAD))
1821 (cvs-cleanup-collection cvs-cookies nil nil nil))
1822
1823;;----------
1824(defun cvs-insert-full-path (tin)
1825 "Insert full path to the file described in TIN in the current buffer."
1826 (insert (format "%s\n" (cvs-full-path tin))))
1827
1828(defun cvs-do-removal (filter &optional cmd all)
1829 "Remove files.
1830Returns a list of FIS that should be `cvs remove'd."
1831 (let* ((files (cvs-mode-marked filter cmd :file t :read-only t))
1832 (fis (delete-if (lambda (fi) (eq (cvs-fileinfo->type fi) 'UNKNOWN))
1833 (cvs-mode-marked filter cmd)))
1834 (silent (or (not cvs-confirm-removals)
1835 (cvs-every (lambda (fi)
1836 (or (not (file-exists-p
1837 (cvs-fileinfo->full-path fi)))
1838 (cvs-applicable-p fi 'safe-rm)))
1839 files))))
1840 (when (and (not silent) (equal cvs-confirm-removals 'list))
1841 (save-excursion
1842 (pop-to-buffer (cvs-temp-buffer))
1843 (dolist (fi fis)
1844 (insert (cvs-fileinfo->full-path fi) "\n"))))
1845 (if (not (or silent
1846 (yes-or-no-p (format "Delete %d files? " (length files)))))
1847 (progn (message "Aborting") nil)
1848 (dolist (fi files)
1849 (let* ((type (cvs-fileinfo->type fi))
1850 (file (cvs-fileinfo->full-path fi)))
1851 (when (or all (eq type 'UNKNOWN))
1852 (when (file-exists-p file) (delete-file file))
1853 (unless all (setf (cvs-fileinfo->type fi) 'DEAD) t))))
1854 fis)))
1855
1856(defun-cvs-mode (cvs-mode-remove . SIMPLE) (flags)
1857 "Remove all marked files.
1858With prefix argument, prompt for cvs flags."
1859 (interactive (list (cvs-flags-query 'cvs-remove-flags "cvs remove flags")))
1860 (let ((fis (cvs-do-removal 'remove)))
1861 (if fis (cvs-mode-run "remove" (cons "-f" flags) fis)
1862 (cvs-cleanup-collection cvs-cookies nil nil nil))))
1863
1864
1865(defvar cvs-tag-name "")
1866(defun-cvs-mode (cvs-mode-tag . SIMPLE) (tag &optional flags)
1867 "Run `cvs tag TAG' on all selected files.
1868With prefix argument, prompt for cvs flags."
1869 (interactive
1870 (list (setq cvs-tag-name
1871 (cvs-query-read cvs-tag-name "Tag name: " cvs-qtypedesc-tag))
1872 (cvs-flags-query 'cvs-tag-flags "tag flags")))
1873 (cvs-mode-do "tag" (append flags (list tag))
1874 (when cvs-force-dir-tag 'tag)))
1875
1876(defun-cvs-mode (cvs-mode-untag . SIMPLE) (tag &optional flags)
1877 "Run `cvs tag -d TAG' on all selected files.
1878With prefix argument, prompt for cvs flags."
1879 (interactive
1880 (list (setq cvs-tag-name
1881 (cvs-query-read cvs-tag-name "Tag to delete: " cvs-qtypedesc-tag))
1882 (cvs-flags-query 'cvs-tag-flags "tag flags")))
1883 (cvs-mode-do "tag" (append '("-d") flags (list tag))
1884 (when cvs-force-dir-tag 'tag)))
1885
1886
1887;; Byte compile files.
1888
1889(defun-cvs-mode cvs-mode-byte-compile-files ()
1890 "Run byte-compile-file on all selected files that end in '.el'."
1891 (interactive)
1892 (let ((marked (cvs-get-marked (cvs-ignore-marks-p "byte-compile"))))
1893 (dolist (fi marked)
1894 (let ((filename (cvs-fileinfo->full-path fi)))
1895 (when (string-match "\\.el\\'" filename)
1896 (byte-compile-file filename))))))
1897
1898;; ChangeLog support.
1899
1900;;----------
1901(defun-cvs-mode cvs-mode-add-change-log-entry-other-window ()
1902 "Add a ChangeLog entry in the ChangeLog of the current directory."
1903 (interactive)
1904 (let* ((fi (cvs-mode-marked nil nil :one t))
1905 (default-directory (cvs-expand-dir-name (cvs-fileinfo->dir fi)))
1906 (buffer-file-name (expand-file-name (cvs-fileinfo->file fi))))
1907 ;; This `save-excursion' is necessary because of interaction between
1908 ;; dynamic scoping and buffer-local variables:
1909 ;; the above binding of `buffer-file-name' has temporarily changed the
1910 ;; buffer-local variable (same thing for `default-directory'), so we
1911 ;; need to switch back to the original buffer before the unbinding
1912 ;; restores the old value.
1913 (save-excursion (add-change-log-entry-other-window))))
1914
1915;; interactive commands to set optional flags
1916
1917(defun cvs-mode-set-flags (flag)
1918 "Ask for new setting of cvs-FLAG-flags."
1919 (interactive
1920 (list (completing-read
1921 "Which flag: "
1922 (mapcar 'list '("cvs" "diff" "update" "status" "log" "tag" ;"rtag"
1923 "commit" "remove" "undo" "checkout"))
1924 nil t)))
1925 (let* ((sym (intern (concat "cvs-" flag "-flags"))))
1926 (let ((current-prefix-arg '(16)))
1927 (cvs-flags-query sym (concat flag " flags")))))
1928
1929
1930;;;;
1931;;;; Utilities for the *cvs* buffer
1932;;;;
1933
1934;;----------
1935(defun cvs-full-path (tin)
1936 "Return the full path for the file that is described in TIN."
1937 (cvs-fileinfo->full-path (ewoc-data tin)))
1938
1939;;----------
1940(defun cvs-dir-member-p (fileinfo dir)
1941 "Return true if FILEINFO represents a file in directory DIR."
1942 (and (not (eq (cvs-fileinfo->type fileinfo) 'DIRCHANGE))
1943 (cvs-string-prefix-p dir (cvs-fileinfo->dir fileinfo))
1944 (not (memq (cvs-fileinfo->subtype fileinfo) '(HEADER FOOTER)))))
1945
1946(defun cvs-execute-single-file (fi extractor program constant-args)
1947 "Internal function for `cvs-execute-single-file-list'."
1948 (let* ((cur-dir (cvs-fileinfo->dir fi))
1949 (default-directory (cvs-expand-dir-name cur-dir))
1950 (inhibit-read-only t)
1951 (arg-list (funcall extractor fi)))
1952
1953 ;; Execute the command unless extractor returned t.
1954 (when (listp arg-list)
1955 (let* ((args (append constant-args arg-list)))
1956
1957 (insert (format "=== cd %s\n=== %s %s\n\n"
1958 cur-dir program (cvs-strings->string args)))
1959
1960 ;; FIXME: return the exit status?
1961 (apply 'call-process program nil t t args)
1962 (goto-char (point-max))))))
1963
1964;; FIXME: make this run in the background ala cvs-run-process...
1965(defun cvs-execute-single-file-list (fis extractor program constant-args)
1966 "Run PROGRAM on all elements on FIS.
1967The PROGRAM will be called with pwd set to the directory the files
1968reside in. CONSTANT-ARGS is a list of strings to pass as arguments to
1969PROGRAM. The arguments given to the program will be CONSTANT-ARGS
1970followed by the list that EXTRACTOR returns.
1971
1972EXTRACTOR will be called once for each file on FIS. It is given
1973one argument, the cvs-fileinfo. It can return t, which means ignore
1974this file, or a list of arguments to send to the program."
1975 (dolist (fi fis)
1976 (cvs-execute-single-file fi extractor program constant-args)))
1977
1978
1979(defun cvs-revert-if-needed (fis)
1980 (dolist (fileinfo fis)
1981 (let* ((file (cvs-fileinfo->full-path fileinfo))
1982 (buffer (find-buffer-visiting file)))
1983 ;; For a revert to happen the user must be editing the file...
1984 (unless (or (null buffer)
1985 (eq (cvs-fileinfo->type fileinfo) 'MESSAGE)
1986 ;; FIXME: check whether revert is really needed.
1987 ;; `(verify-visited-file-modtime buffer)' doesn't cut it
1988 ;; because it only looks at the time stamp (it ignores
1989 ;; read-write changes) which is not changed by `commit'.
1990 (buffer-modified-p buffer))
1991 (with-current-buffer buffer
1992 (let ((cvs-buf-was-ro buffer-read-only))
1993 (ignore-errors
1994 ;; Ideally, we'd like to prevent changing the (minor) modes.
1995 ;; But we do want to reset the mode for some cases, most notably
1996 ;; VC. Maybe it'd better to reset VC explicitely ?
1997 (revert-buffer 'ignore-auto 'dont-ask)) ; 'preserve-modes
1998 ;; protect the buffer-read-only setting
1999 (if cvs-buf-was-ro (toggle-read-only 1))))))))
2000
2001
2002
2003(defun cvs-change-cvsroot (newroot)
2004 "Change the cvsroot."
2005 (interactive "DNew repository: ")
2006 (if (or (file-directory-p (expand-file-name "CVSROOT" newroot))
2007 (y-or-n-p (concat "Warning: no CVSROOT found inside repository."
2008 " Change cvs-cvsroot anyhow?")))
2009 (setq cvs-cvsroot newroot)))
2010
2011;;;;
2012;;;; useful global settings
2013;;;;
2014
2015;;;###autoload
2016(add-to-list 'completion-ignored-extensions "CVS/")
2017
2018;;
2019;; Hook to allow calling PCL-CVS by visiting the /CVS subdirectory
2020;;
2021
2022;;;###autoload
2023(defcustom cvs-dired-use-hook '(4)
2024 "Whether or not opening a CVS directory should run PCL-CVS.
2025NIL means never do it.
2026ALWAYS means to always do it unless a prefix argument is given to the
2027 command that prompted the opening of the directory.
2028Anything else means to do it only if the prefix arg is equal to this value."
2029 :group 'pcl-cvs
2030 :type '(choice (const :tag "Never" nil)
2031 (const :tag "Always" always)
2032 (const :tag "Prefix" (4))))
2033
2034;;;###autoload
2035(progn
2036(defun cvs-dired-noselect (dir)
2037 "Run `cvs-examine' if DIR is a CVS administrative directory.
2038The exact behavior is determined also by `cvs-dired-use-hook'."
2039 (when (stringp dir)
2040 (setq dir (directory-file-name dir))
2041 (when (and (string= "CVS" (file-name-nondirectory dir))
2042 (file-readable-p (expand-file-name "Entries" dir))
2043 cvs-dired-use-hook
2044 (if (eq cvs-dired-use-hook 'always)
2045 (not current-prefix-arg)
2046 (equal current-prefix-arg cvs-dired-use-hook)))
2047 (save-excursion
2048 (cvs-examine (file-name-directory dir) t t))))))
2049
2050;;
2051;; hook into VC
2052;;
2053
2054(defadvice vc-simple-command (after pcl-cvs-vc activate)
2055 (cvs-vc-command-advice "*vc-info*" (ad-get-arg 1) (ad-get-arg 3)))
2056
2057(defadvice vc-do-command (after pcl-cvs-vc activate)
2058 (cvs-vc-command-advice (or (ad-get-arg 0) "*vc*")
2059 (ad-get-arg 2) (ad-get-arg 5)))
2060
2061(defun cvs-vc-command-advice (buffer command cvscmd)
2062 (when (and (setq buffer (get-buffer buffer))
2063 (equal command "cvs")
2064 ;; don't parse output we don't understand.
2065 (member cvscmd cvs-parse-known-commands))
2066 (save-excursion
2067 (let ((dir (with-current-buffer buffer default-directory))
2068 (cvs-from-vc t))
2069 (dolist (cvs-buf (buffer-list))
2070 (set-buffer cvs-buf)
2071 ;; look for a corresponding pcl-cvs buffer
2072 (when (and (eq major-mode 'cvs-mode)
2073 (cvs-string-prefix-p default-directory dir))
2074 (let ((subdir (substring dir (length default-directory))))
2075 (set-buffer buffer)
2076 (set (make-local-variable 'cvs-buffer) cvs-buf)
2077 ;; VC never (?) does `cvs -n update' so dcd=nil
2078 ;; should probably always be the right choice.
2079 (cvs-parse-process nil subdir))))))))
2080
2081;;
2082;; Hook into write-buffer
2083;;
2084
2085(defun cvs-mark-buffer-changed ()
2086 (let* ((file (expand-file-name buffer-file-name))
2087 (version (and (fboundp 'vc-backend)
2088 (eq (vc-backend file) 'CVS)
2089 (vc-workfile-version file))))
2090 (when version
2091 (save-excursion
2092 (dolist (cvs-buf (buffer-list))
2093 (set-buffer cvs-buf)
2094 ;; look for a corresponding pcl-cvs buffer
2095 (when (and (eq major-mode 'cvs-mode)
2096 (cvs-string-prefix-p default-directory file))
2097 (let* ((file (substring file (length default-directory)))
2098 (fi (cvs-create-fileinfo
2099 (if (string= "0" version)
2100 'ADDED 'MODIFIED)
2101 (or (file-name-directory file) "")
2102 (file-name-nondirectory file)
2103 "cvs-mark-buffer-changed")))
2104 (cvs-addto-collection cvs-cookies fi))))))))
2105
2106(add-hook 'after-save-hook 'cvs-mark-buffer-changed)
2107
2108;;
2109;; hook into uniquify
2110;;
2111
2112(defadvice uniquify-buffer-file-name (after pcl-cvs-uniquify activate)
2113 (or ad-return-value
2114 (save-excursion
2115 (set-buffer (ad-get-arg 0))
2116 (when (eq major-mode 'cvs-mode)
2117 (setq ad-return-value list-buffers-directory)))))
2118
2119
2120(provide 'pcvs)
2121
2122;;; pcvs.el ends here