(ewoc-goto-prev, ewoc-goto-next):
[bpt/emacs.git] / lisp / pcvs.el
CommitLineData
5b467bf4
SM
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
a7996e05
SM
11;; (Stefan Monnier) monnier@cs.yale.edu
12;; (Greg Klanderman) greg@alphatech.com
13;; (Jari Aalto+mail.emacs) jari.aalto@poboxes.com
5b467bf4
SM
14;; Maintainer: (Stefan Monnier) monnier+lists/cvs/pcl@flint.cs.yale.edu
15;; Keywords: CVS, version control, release management
16;; Version: $Name: $
d6cc3d17 17;; Revision: $Id: pcvs.el,v 1.13 2000/10/15 05:18:33 monnier Exp $
5b467bf4
SM
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
cb3430a1
SM
38;; PCL-CVS is a front-end to the CVS version control system. For people
39;; familiar with VC, it is somewhat like VC-dired: it presents the status of
40;; all the files in your working area and allows you to commit/update several
41;; of them at a time. Compared to VC-dired, it is considerably better and
42;; faster (but only for CVS).
43
44;; PCL-CVS was originally written by Per Cederqvist many years ago. This
45;; version derives from the XEmacs-21 version, itself based on the 2.0b2
46;; version (last release from Per). It is a thorough rework.
47
48;; Contrary to what you'd expect, PCL-CVS is not a replacement for VC but only
49;; for VC-dired. As such, I've tried to make PCL-CVS and VC interoperate
50;; seamlessly (I also use VC).
51
52;; To use PCL-CVS just use `M-x cvs-examine RET <dir> RET'.
96190aa1 53;; There is a TeXinfo manual, which can be helpful to get started.
cb3430a1 54
5b467bf4
SM
55;;; Todo:
56
cb3430a1 57;; ******** FIX THE DOCUMENTATION *********
96190aa1 58;;
eed914af 59;; - use UP-TO-DATE rather than DEAD when cleaning before `examine'.
d6cc3d17
SM
60;; - Allow to flush messages only
61;; - Allow to protect files like ChangeLog from flushing
62;; - Automatically cvs-mode-insert files from find-file-hook
63;; (and don't flush them as long as they are visited)
eed914af 64;;
96190aa1 65;; - hide fileinfos without getting rid of them (will require ewok work).
cb3430a1
SM
66;; - add toolbar entries
67;; - marking
68;; marking directories should jump to just after the dir.
69;; allow (un)marking directories at a time with the mouse.
cb3430a1
SM
70;; - liveness indicator
71;; - indicate in docstring if the cmd understands the `b' prefix(es).
72;; - call smerge-mode when opening CONFLICT files.
cb3430a1
SM
73;; - have vc-checkin delegate to cvs-mode-commit when applicable
74;; - higher-level CVS operations
75;; cvs-mode-rename
76;; cvs-mode-branch
77;; - module-level commands
78;; add support for parsing 'modules' file ("cvs co -c")
79;; cvs-mode-rcs2log
80;; cvs-rdiff
81;; cvs-release
82;; cvs-import
83;; C-u M-x cvs-checkout should ask for a cvsroot
84;; cvs-mode-handle-new-vendor-version
5b467bf4
SM
85;; - checks out module, or alternately does update join
86;; - does "cvs -n tag LAST_VENDOR" to find old files into *cvs*
cb3430a1 87;; cvs-export
44946a4c 88;; (with completion on tag names and hooks to help generate full releases)
cb3430a1 89;; - allow cvs-cmd-do to either clear the marks or not.
cb3430a1
SM
90;; - display stickiness information. And current CVS/Tag as well.
91;; - write 'cvs-mode-admin' to do arbitrary 'cvs admin' commands
6dc7d3d5
SM
92;; Most interesting would be version removal and log message replacement.
93;; The last one would be neat when called from log-view-mode.
cb3430a1 94;; - cvs-mode-incorporate
6dc7d3d5 95;; It would merge in the status from one *cvs* buffer into another.
5b467bf4
SM
96;; This would be used to populate such a buffer that had been created with
97;; a `cvs {update,status,checkout} -l'.
cb3430a1
SM
98;; - cvs-mode-(i)diff-other-{file,buffer,cvs-buffer}
99;; - offer the choice to kill the process when the user kills the cvs buffer.
5b467bf4 100;; right now, it's killed without further ado.
cb3430a1 101;; - make `cvs-mode-ignore' allow manually entering a pattern.
5b467bf4 102;; to which dir should it apply ?
cb3430a1
SM
103;; - cvs-mode-ignore should try to remove duplicate entries.
104;; - maybe poll/check CVS/Entries files to react to external `cvs' commands ?
105;; - some kind of `cvs annotate' support ?
5b467bf4 106;; but vc-annotate can be used instead.
44946a4c
SM
107;; - proper `g' that passes safe args and uses either cvs-status or cvs-examine
108;; maybe also use cvs-update depending on I-don't-know-what.
cb3430a1 109;; - add message-levels so that we can hide some levels of messages
5b467bf4
SM
110
111;;; Code:
112
3db3a13f 113(eval-when-compile (require 'cl))
5b467bf4
SM
114(require 'ewoc) ;Ewoc was once cookie
115(require 'pcvs-defs)
116(require 'pcvs-util)
117(require 'pcvs-parse)
118(require 'pcvs-info)
119
120\f
121;;;;
122;;;; global vars
123;;;;
124
125(defvar cvs-cookies) ;;nil
126 ;;"Handle for the cookie structure that is displayed in the *cvs* buffer.")
127;;(make-variable-buffer-local 'cvs-cookies)
128
129;;;;
130;;;; Dynamically scoped variables
131;;;;
132
133(defvar cvs-from-vc nil "Bound to t inside VC advice.")
134
027b73ac 135;;;;
5b467bf4 136;;;; flags variables
027b73ac 137;;;;
5b467bf4
SM
138
139(defun cvs-defaults (&rest defs)
140 (let ((defs (cvs-first defs cvs-shared-start)))
141 (append defs
44946a4c 142 (make-list (- cvs-shared-start (length defs)) (car defs))
5b467bf4
SM
143 cvs-shared-flags)))
144
145;; For cvs flags, we need to add "-f" to override the cvsrc settings
146;; we also want to evict the annoying -q and -Q options that hide useful
147;; information from pcl-cvs.
148(cvs-flags-define cvs-cvs-flags '(("-f")))
149
150(cvs-flags-define cvs-checkout-flags (cvs-defaults '("-P")))
151(cvs-flags-define cvs-status-flags (cvs-defaults '("-v") nil))
152(cvs-flags-define cvs-log-flags (cvs-defaults nil))
6dc7d3d5 153(cvs-flags-define cvs-diff-flags (cvs-defaults '("-u" "-N") '("-c" "-N") '("-u" "-b")))
5b467bf4
SM
154(cvs-flags-define cvs-tag-flags (cvs-defaults nil))
155(cvs-flags-define cvs-add-flags (cvs-defaults nil))
156(cvs-flags-define cvs-commit-flags (cvs-defaults nil))
157(cvs-flags-define cvs-remove-flags (cvs-defaults nil))
158;;(cvs-flags-define cvs-undo-flags (cvs-defaults nil))
159(cvs-flags-define cvs-update-flags (cvs-defaults '("-d" "-P")))
160
161(defun cvs-reread-cvsrc ()
162 "Reset the default arguments to those in the `cvs-cvsrc-file'."
163 (interactive)
164 (let ((cvsrc (cvs-file-to-string cvs-cvsrc-file)))
165 (when (stringp cvsrc)
166 ;; fetch the values
167 (dolist (cmd '("cvs" "checkout" "status" "log" "diff" "tag"
168 "add" "commit" "remove" "update"))
169 (let* ((sym (intern (concat "cvs-" cmd "-flags")))
170 (val (when (string-match (concat "^" cmd "\\s-\\(.*\\)$") cvsrc)
171 (cvs-string->strings (match-string 1 cvsrc)))))
172 (cvs-flags-set sym 0 val)))
173 ;; ensure that cvs doesn't have -q or -Q
174 (cvs-flags-set 'cvs-cvs-flags 0
175 (cons "-f"
176 (cdr (cvs-partition
177 (lambda (x) (member x '("-q" "-Q")))
178 (cvs-flags-query 'cvs-cvs-flags
179 nil 'noquery))))))))
180
181;; initialize to cvsrc's default values
182(cvs-reread-cvsrc)
183
184\f
185;;;;
186;;;; Mouse bindings and mode motion
187;;;;
188
189(defun cvs-menu (e)
190 "Popup the CVS menu."
191 (interactive "e")
96190aa1
SM
192 (let ((cvs-minor-current-files
193 (list (ewoc-data (ewoc-locate
194 cvs-cookies (posn-point (event-end e)))))))
195 (popup-menu cvs-menu-map e)))
5b467bf4
SM
196
197(defvar cvs-mode-line-process nil
198 "Mode-line control for displaying info on cvs process status.")
199
200
027b73ac 201;;;;
5b467bf4 202;;;; Query-Type-Descriptor for Tags
027b73ac 203;;;;
5b467bf4
SM
204
205(autoload 'cvs-status-get-tags "cvs-status")
206(defun cvs-tags-list ()
207 "Return a list of acceptable tags, ready for completions."
208 (assert (cvs-buffer-p))
209 (let ((marked (cvs-get-marked)))
210 (list* '("BASE") '("HEAD")
211 (when marked
212 (with-temp-buffer
213 (call-process cvs-program
214 nil ;no input
215 t ;output to current-buffer
216 nil ;don't update display while running
217 "status"
218 "-v"
219 (cvs-fileinfo->full-path (car marked)))
220 (goto-char (point-min))
221 (let ((tags (cvs-status-get-tags)))
222 (when (listp tags) tags)))))))
223
224(defvar cvs-tag-history nil)
225(defconst cvs-qtypedesc-tag
226 (cvs-qtypedesc-create 'identity 'identity 'cvs-tags-list 'cvs-tag-history))
227
027b73ac 228;;;;
5b467bf4
SM
229
230(defun cvs-mode! (&optional -cvs-mode!-fun -cvs-mode!-noerror)
231 "Switch to the *cvs* buffer.
232If -CVS-MODE!-FUN is provided, it is executed *cvs* being the current buffer
233 and with its window selected. Else, the *cvs* buffer is simply selected.
234If -CVS-MODE!-NOERROR is non-nil, then failure to find a *cvs* buffer does
235 not generate an error and the current buffer is kept selected.
236-CVS-MODE!-FUN is called interactively if applicable and else with no argument."
237 (let* ((-cvs-mode!-buf (current-buffer))
238 (cvsbuf (cond ((cvs-buffer-p) (current-buffer))
239 ((and cvs-buffer (cvs-buffer-p cvs-buffer)) cvs-buffer)
240 (-cvs-mode!-noerror (current-buffer))
241 (t (error "can't find the *cvs* buffer."))))
242 (-cvs-mode!-wrapper cvs-minor-wrap-function)
243 (-cvs-mode!-cont (lambda ()
244 (save-current-buffer
245 (if (commandp -cvs-mode!-fun)
246 (call-interactively -cvs-mode!-fun)
247 (funcall -cvs-mode!-fun))))))
248 (if (not -cvs-mode!-fun) (set-buffer cvsbuf)
249 (let ((cvs-mode!-buf (current-buffer))
250 (cvs-mode!-owin (selected-window))
251 (cvs-mode!-nwin (get-buffer-window cvsbuf 'visible)))
252 (unwind-protect
253 (progn
254 (set-buffer cvsbuf)
255 (when cvs-mode!-nwin (select-window cvs-mode!-nwin))
256 (if -cvs-mode!-wrapper
257 (funcall -cvs-mode!-wrapper -cvs-mode!-buf -cvs-mode!-cont)
258 (funcall -cvs-mode!-cont)))
259 (set-buffer cvs-mode!-buf)
260 (when (and cvs-mode!-nwin (eq cvs-mode!-nwin (selected-window)))
261 ;; the selected window has not been changed by FUN
262 (select-window cvs-mode!-owin)))))))
263
027b73ac 264;;;;
5b467bf4 265;;;; Prefixes
027b73ac 266;;;;
5b467bf4
SM
267
268(defvar cvs-branches (list cvs-vendor-branch "HEAD" "HEAD"))
269(cvs-prefix-define cvs-branch-prefix
270 "Current selected branch."
271 "version"
272 (cons cvs-vendor-branch cvs-branches)
273 cvs-qtypedesc-tag)
274
275(defun cvs-set-branch-prefix (arg)
276 "Set the branch prefix to take action at the next command.
277See `cvs-prefix-set' for a further the description of the behavior.
278\\[universal-argument] 1 selects the vendor branch
279and \\[universal-argument] 2 selects the HEAD."
280 (interactive "P")
281 (cvs-mode!)
282 (cvs-prefix-set 'cvs-branch-prefix arg))
283
284(defun cvs-add-branch-prefix (flags &optional arg)
285 "Add branch selection argument if the branch prefix was set.
286The argument is added (or not) to the list of FLAGS and is constructed
287by appending the branch to ARG which defaults to \"-r\"."
288 (let ((branch (cvs-prefix-get 'cvs-branch-prefix)))
289 ;; deactivate the secondary prefix, even if not used.
290 (cvs-prefix-get 'cvs-secondary-branch-prefix)
291 (if branch (cons (concat (or arg "-r") branch) flags) flags)))
292
293(cvs-prefix-define cvs-secondary-branch-prefix
294 "Current secondary selected branch."
295 "version"
296 (cons cvs-vendor-branch cvs-branches)
297 cvs-qtypedesc-tag)
298
299(defun cvs-set-secondary-branch-prefix (arg)
300 "Set the branch prefix to take action at the next command.
301See `cvs-prefix-set' for a further the description of the behavior.
302\\[universal-argument] 1 selects the vendor branch
303and \\[universal-argument] 2 selects the HEAD."
304 (interactive "P")
305 (cvs-mode!)
306 (cvs-prefix-set 'cvs-secondary-branch-prefix arg))
307
308(defun cvs-add-secondary-branch-prefix (flags &optional arg)
309 "Add branch selection argument if the secondary branch prefix was set.
310The argument is added (or not) to the list of FLAGS and is constructed
311by appending the branch to ARG which defaults to \"-r\".
312Since the `cvs-secondary-branch-prefix' is only active if the primary
313prefix is active, it is important to read the secondary prefix before
314the primay since reading the primary can deactivate it."
315 (let ((branch (and (cvs-prefix-get 'cvs-branch-prefix 'read-only)
316 (cvs-prefix-get 'cvs-secondary-branch-prefix))))
317 (if branch (cons (concat (or arg "-r") branch) flags) flags)))
318
027b73ac 319;;;;
5b467bf4
SM
320
321(define-minor-mode
322 cvs-minor-mode
323 "
324This mode is used for buffers related to a main *cvs* buffer.
325All the `cvs-mode' buffer operations are simply rebound under
326the \\[cvs-mode-map] prefix.
327"
328 nil " CVS")
329(put 'cvs-minor-mode 'permanent-local t)
330
331
332(defvar cvs-temp-buffers nil)
333(defun cvs-temp-buffer (&optional cmd normal nosetup)
334 "Create a temporary buffer to run CMD in.
335If CMD is a string, use it to lookup `cvs-buffer-name-alist' to find
336the buffer name to be used and its `major-mode'.
337
338The selected window will not be changed. The new buffer will not maintain undo
339information and will be read-only unless NORMAL is non-nil. It will be emptied
340\(unless NOSETUP is non-nil\) and its `default-directory' will be inherited
341from the current buffer."
342 (let* ((cvs-buf (current-buffer))
343 (info (cdr (assoc cmd cvs-buffer-name-alist)))
dedffa6a
GM
344 (name (eval (nth 0 info)))
345 (mode (nth 1 info))
5b467bf4
SM
346 (dir default-directory)
347 (buf (cond
348 (name (cvs-get-buffer-create name))
349 ((and (bufferp cvs-temp-buffer) (buffer-name cvs-temp-buffer))
350 cvs-temp-buffer)
351 (t
352 (set (make-local-variable 'cvs-temp-buffer)
353 (cvs-get-buffer-create
354 (eval cvs-temp-buffer-name) 'noreuse))))))
027b73ac 355
5b467bf4
SM
356 ;; handle the potential pre-existing process
357 (let ((proc (get-buffer-process buf)))
358 (when (and (not normal) (processp proc)
359 (memq (process-status proc) '(run stop)))
360 (error "Can not run two cvs processes simultaneously")))
361
362 (if (not name) (kill-local-variable 'other-window-scroll-buffer)
363 ;; Strangely, if no window is created, `display-buffer' ends up
364 ;; doing a `switch-to-buffer' which does a `set-buffer', hence
365 ;; the need for `save-excursion'.
366 (unless nosetup (save-excursion (display-buffer buf)))
367 ;; FIXME: this doesn't do the right thing if the user later on
368 ;; does a `find-file-other-window' and `scroll-other-window'
369 (set (make-local-variable 'other-window-scroll-buffer) buf))
370
371 (add-to-list 'cvs-temp-buffers buf)
372
373 (with-current-buffer buf
374 (setq buffer-read-only nil)
375 (setq default-directory dir)
376 (unless nosetup (erase-buffer))
377 (set (make-local-variable 'cvs-buffer) cvs-buf)
378 ;;(cvs-minor-mode 1)
379 (let ((lbd list-buffers-directory))
380 (if (fboundp mode) (funcall mode) (fundamental-mode))
381 (when lbd (set (make-local-variable 'list-buffers-directory) lbd)))
382 (cvs-minor-mode 1)
383 ;;(set (make-local-variable 'cvs-buffer) cvs-buf)
384 (unless normal
385 (setq buffer-read-only t)
386 (buffer-disable-undo))
387 buf)))
388
389(defun cvs-mode-kill-buffers ()
390 "Kill all the \"temporary\" buffers created by the *cvs* buffer."
391 (interactive)
392 (dolist (buf cvs-temp-buffers) (ignore-errors (kill-buffer buf))))
393
394(defun cvs-make-cvs-buffer (dir &optional new)
395 "Create the *cvs* buffer for directory DIR.
396If non-nil, NEW means to create a new buffer no matter what."
397 ;; the real cvs-buffer creation
398 (setq dir (cvs-expand-dir-name dir))
399 (let* ((buffer-name (eval cvs-buffer-name))
400 (buffer
401 (or (and (not new)
402 (eq cvs-reuse-cvs-buffer 'current)
403 (cvs-buffer-p) ;reuse the current buffer if possible
404 (current-buffer))
405 ;; look for another cvs buffer visiting the same directory
406 (save-excursion
407 (unless new
408 (dolist (buffer (cons (current-buffer) (buffer-list)))
409 (set-buffer buffer)
410 (and (cvs-buffer-p)
411 (case cvs-reuse-cvs-buffer
412 (always t)
413 (subdir
414 (or (cvs-string-prefix-p default-directory dir)
415 (cvs-string-prefix-p dir default-directory)))
416 (samedir (string= default-directory dir)))
417 (return buffer)))))
418 ;; we really have to create a new buffer:
419 ;; we temporarily bind cwd to "" to prevent
420 ;; create-file-buffer from using directory info
421 ;; unless it is explicitly in the cvs-buffer-name.
422 (cvs-get-buffer-create buffer-name new))))
423 (with-current-buffer buffer
424 (or
425 (and (string= dir default-directory) (cvs-buffer-p)
426 ;; just a refresh
427 (ignore-errors
428 (cvs-cleanup-collection cvs-cookies nil nil t)
429 (current-buffer)))
430 ;; setup from scratch
431 (progn
432 (setq default-directory dir)
433 (setq buffer-read-only nil)
434 (erase-buffer)
cb3430a1
SM
435 (insert "\
436Repository : " (directory-file-name (cvs-get-cvsroot)) "
437Module : " (cvs-get-module) "
438Working dir: " (abbreviate-file-name dir) "
439
440")
5b467bf4
SM
441 (setq buffer-read-only t)
442 (cvs-mode)
443 (set (make-local-variable 'list-buffers-directory) buffer-name)
444 ;;(set (make-local-variable 'cvs-temp-buffer) (cvs-temp-buffer))
cb3430a1 445 (let ((cookies (ewoc-create 'cvs-fileinfo-pp "\n" "")))
5b467bf4 446 (set (make-local-variable 'cvs-cookies) cookies)
5b467bf4
SM
447 (add-hook 'kill-buffer-hook
448 (lambda ()
449 (ignore-errors (kill-buffer cvs-temp-buffer)))
450 nil t)
451 ;;(set-buffer buf)
452 buffer))))))
453
454(defun* cvs-cmd-do (cmd dir flags fis new
455 &key cvsargs noexist dont-change-disc noshow)
456 (let* ((dir (file-name-as-directory
457 (abbreviate-file-name (expand-file-name dir))))
458 (cvsbuf (cvs-make-cvs-buffer dir new)))
459 ;; Check that dir is under CVS control.
460 (unless (file-directory-p dir)
6dc7d3d5 461 (error "%s is not a directory" dir))
5b467bf4 462 (unless (or noexist (file-directory-p (expand-file-name "CVS" dir)))
6dc7d3d5 463 (error "%s does not contain CVS controlled files" dir))
5b467bf4
SM
464
465 (set-buffer cvsbuf)
466 (cvs-mode-run cmd flags fis
467 :cvsargs cvsargs :dont-change-disc dont-change-disc)
468
469 (if noshow cvsbuf
470 (let ((pop-up-windows nil)) (pop-to-buffer cvsbuf)))))
471;; (funcall (if (and (boundp 'pop-up-frames) pop-up-frames)
472;; 'pop-to-buffer 'switch-to-buffer)
473;; cvsbuf))))
474
5b467bf4
SM
475(defun cvs-run-process (args fis postprocess &optional single-dir)
476 (assert (cvs-buffer-p cvs-buffer))
477 (save-current-buffer
478 (let ((procbuf (current-buffer))
479 (cvsbuf cvs-buffer)
480 (single-dir (or single-dir (eq cvs-execute-single-dir t))))
027b73ac 481
5b467bf4
SM
482 (set-buffer procbuf)
483 (goto-char (point-max))
484 (unless (bolp) (let ((inhibit-read-only t)) (insert "\n")))
485 ;; find the set of files we'll process in this round
486 (let* ((dir+files+rest
487 (if (or (null fis) (not single-dir))
488 ;; not single-dir mode: just process the whole thing
489 (list "" (mapcar 'cvs-fileinfo->full-path fis) nil)
490 ;; single-dir mode: extract the same-dir-elements
491 (let ((dir (cvs-fileinfo->dir (car fis))))
492 ;; output the concerned dir so the parser can translate paths
493 (let ((inhibit-read-only t))
494 (insert "pcl-cvs: descending directory " dir "\n"))
495 ;; loop to find the same-dir-elems
496 (do* ((files () (cons (cvs-fileinfo->file fi) files))
497 (fis fis (cdr fis))
498 (fi (car fis) (car fis)))
499 ((not (and fis (string= dir (cvs-fileinfo->dir fi))))
500 (list dir files fis))))))
dedffa6a
GM
501 (dir (nth 0 dir+files+rest))
502 (files (nth 1 dir+files+rest))
503 (rest (nth 2 dir+files+rest)))
027b73ac 504
5b467bf4
SM
505 ;; setup the (current) process buffer
506 (set (make-local-variable 'cvs-postprocess)
507 (if (null rest)
508 ;; this is the last invocation
509 postprocess
510 ;; else, we have to register ourselves to be rerun on the rest
511 `(cvs-run-process ',args ',rest ',postprocess ',single-dir)))
5b467bf4
SM
512 (add-hook 'kill-buffer-hook
513 (lambda ()
514 (let ((proc (get-buffer-process (current-buffer))))
515 (when (processp proc)
516 (set-process-filter proc nil)
517 (set-process-sentinel proc nil)
518 (delete-process proc))))
519 nil t)
520
521 ;; create the new process and setup the procbuffer correspondingly
522 (let* ((args (append (cvs-flags-query 'cvs-cvs-flags nil 'noquery)
523 (if cvs-cvsroot (list "-d" cvs-cvsroot))
524 args
525 files))
526 (process-connection-type nil) ; Use a pipe, not a pty.
527 (process
528 ;; the process will be run in the selected dir
529 (let ((default-directory (cvs-expand-dir-name dir)))
530 (apply 'start-process "cvs" procbuf cvs-program args))))
531 (set-process-sentinel process 'cvs-sentinel)
532 (set-process-filter process 'cvs-update-filter)
533 (set-marker (process-mark process) (point-max))
534 (ignore-errors (process-send-eof process)) ;close its stdin to avoid hangs
027b73ac 535
5b467bf4
SM
536 ;; now finish setting up the cvs-buffer
537 (set-buffer cvsbuf)
538 (setq cvs-mode-line-process (symbol-name (process-status process)))
539 (force-mode-line-update)))))
540
541 ;; The following line is said to improve display updates on some
542 ;; emacsen. It shouldn't be needed, but it does no harm.
543 (sit-for 0))
544
545(defun cvs-update-header (args fis) ; inline
546 (let* ((lastarg nil)
547 ;; filter out the largish commit message
548 (args (mapcar (lambda (arg)
549 (cond
550 ((and (eq lastarg nil) (string= arg "commit"))
551 (setq lastarg 'commit) arg)
552 ((and (eq lastarg 'commit) (string= arg "-m"))
553 (setq lastarg '-m) arg)
554 ((eq lastarg '-m)
555 (setq lastarg 'done) "<log message>")
556 (t arg)))
557 args))
558 ;; turn them into a string
559 (arg (cvs-strings->string
560 (append (cvs-flags-query 'cvs-cvs-flags nil 'noquery)
561 (if cvs-cvsroot (list "-d" cvs-cvsroot))
562 args
563 (mapcar 'cvs-fileinfo->full-path fis))))
564 (str (if args (concat "-- Running " cvs-program " " arg " ...\n")
565 "\n")))
566 (if nil (insert str) ;inline
567 ;;(with-current-buffer cvs-buffer
cb3430a1
SM
568 (let* ((prev-msg (car (ewoc-get-hf cvs-cookies)))
569 (tin (ewoc-nth cvs-cookies 0)))
5b467bf4
SM
570 ;; look for the first *real* fileinfo (to determine emptyness)
571 (while
572 (and tin
573 (memq (cvs-fileinfo->type (ewoc-data tin))
574 '(MESSAGE DIRCHANGE)))
575 (setq tin (ewoc-next cvs-cookies tin)))
576 ;; cleanup the prev-msg
577 (when (string-match "Running \\(.*\\) ...\n" prev-msg)
578 (setq prev-msg
579 (concat
580 "-- last cmd: "
581 (match-string 1 prev-msg)
582 " --")))
583 ;; set the new header and footer
cb3430a1
SM
584 (ewoc-set-hf cvs-cookies
585 str (concat "\n--------------------- "
586 (if tin "End" "Empty")
587 " ---------------------\n"
588 prev-msg))))))
5b467bf4
SM
589
590
5b467bf4
SM
591(defun cvs-sentinel (proc msg)
592 "Sentinel for the cvs update process.
593This is responsible for parsing the output from the cvs update when
594it is finished."
595 (when (memq (process-status proc) '(signal exit))
596 (if (null (buffer-name (process-buffer proc)))
597 ;;(set-process-buffer proc nil)
598 (error "cvs' process buffer was killed")
599 (let* ((obuf (current-buffer))
600 (procbuffer (process-buffer proc)))
601 (set-buffer (with-current-buffer procbuffer cvs-buffer))
602 (setq cvs-mode-line-process (symbol-name (process-status proc)))
603 (force-mode-line-update)
604 (set-buffer procbuffer)
605 (let ((cvs-postproc cvs-postprocess))
606 ;; Since the buffer and mode line will show that the
607 ;; process is dead, we can delete it now. Otherwise it
608 ;; will stay around until M-x list-processes.
609 (delete-process proc)
610 (setq cvs-postprocess nil)
611 ;; do the postprocessing like parsing and such
612 (save-excursion (eval cvs-postproc))
613 ;; check whether something is left
614 (unless cvs-postprocess
615 (buffer-enable-undo)
616 (with-current-buffer cvs-buffer
617 (cvs-update-header nil nil) ;FIXME: might need to be inline
618 (message "CVS process has completed"))))
619 ;; This might not even be necessary
620 (set-buffer obuf)))))
621
5b467bf4
SM
622(defun cvs-parse-process (dcd &optional subdir)
623 "FIXME: bad name, no doc"
624 (let* ((from-buf (current-buffer))
625 (fileinfos (cvs-parse-buffer 'cvs-parse-table dcd subdir))
626 (_ (set-buffer cvs-buffer))
627 last
628 (from-pt (point)))
629 ;; add the new fileinfos
630 (dolist (fi fileinfos)
631 (setq last (cvs-addto-collection cvs-cookies fi last)))
632 (cvs-cleanup-collection cvs-cookies
633 (eq cvs-auto-remove-handled t)
634 cvs-auto-remove-directories
635 nil)
636 ;; update the display (might be unnecessary)
6dc7d3d5 637 ;;(ewoc-refresh cvs-cookies)
5b467bf4
SM
638 ;; revert buffers if necessary
639 (when (and cvs-auto-revert (not dcd) (not cvs-from-vc))
640 (cvs-revert-if-needed fileinfos))
641 ;; get back to where we were. `save-excursion' doesn't seem to
642 ;; work in this case, probably because the buffer is reconstructed
643 ;; by the cookie code.
644 (goto-char from-pt)
645 (set-buffer from-buf)))
646
647(defmacro defun-cvs-mode (fun args docstring interact &rest body)
648 "Define a function to be used in a *cvs* buffer.
649This will look for a *cvs* buffer and execute BODY in it.
650Since the interactive arguments might need to be queried after
651switching to the *cvs* buffer, the generic code is rather ugly,
652but luckily we can often use simpler alternatives.
653
654FUN can be either a symbol (i.e. STYLE is nil) or a cons (FUN . STYLE).
655ARGS and DOCSTRING are the normal argument list.
656INTERACT is the interactive specification or nil for non-commands.
657
658STYLE can be either SIMPLE, NOARGS or DOUBLE. It's an error for it
659to have any other value, unless other details of the function make it
660clear what alternative to use.
661- SIMPLE will get all the interactive arguments from the original buffer.
662- NOARGS will get all the arguments from the *cvs* buffer and will
663 always behave as if called interactively.
664- DOUBLE is the generic case."
665 (let ((style (cvs-cdr fun))
666 (fun (cvs-car fun)))
667 (cond
668 ;; a trivial interaction, no need to move it
669 ((or (eq style 'SIMPLE)
dedffa6a
GM
670 (null (nth 1 interact))
671 (stringp (nth 1 interact)))
5b467bf4
SM
672 `(defun ,fun ,args ,docstring ,interact
673 (cvs-mode! (lambda () ,@body))))
674
675 ;; fun is only called interactively: move all the args to the inner fun
676 ((eq style 'NOARGS)
677 `(defun ,fun () ,docstring (interactive)
678 (cvs-mode! (lambda ,args ,interact ,@body))))
679
680 ;; bad case
681 ((eq style 'DOUBLE)
682 (string-match ".*" docstring)
683 (let ((line1 (match-string 0 docstring))
684 (restdoc (substring docstring (match-end 0)))
685 (fun-1 (intern (concat (symbol-name fun) "-1"))))
686 `(progn
687 (defun ,fun-1 ,args
688 ,(concat docstring "\nThis function only works within a *cvs* buffer.
689For interactive use, use `" (symbol-name fun) "' instead.")
690 ,interact
691 ,@body)
692 (defun ,fun ()
693 ,(concat line1 "\nWrapper function that switches to a *cvs* buffer
694before calling the real function `" (symbol-name fun-1) "'.\n")
695 (interactive)
696 (cvs-mode! ',fun-1)))))
697
698 (t (error "unknown style %s in `defun-cvs-mode'" style)))))
699(def-edebug-spec defun-cvs-mode (&define sexp lambda-list stringp ("interactive" interactive) def-body))
700
701(defun-cvs-mode cvs-mode-kill-process ()
702 "Kill the temporary buffer and associated process."
703 (interactive)
704 (when (and (bufferp cvs-temp-buffer) (buffer-name cvs-temp-buffer))
705 (let ((proc (get-buffer-process cvs-temp-buffer)))
706 (when proc (delete-process proc)))))
707
708;;;
709;;; Maintaining the collection in the face of updates
710;;;
711
712(defun cvs-addto-collection (c fi &optional tin)
3c7fafc7 713 "Add FI to C and return FI's corresponding tin.
5b467bf4
SM
714FI is inserted in its proper place or maybe even merged with a preexisting
715 fileinfo if applicable.
716TIN specifies an optional starting point."
717 (unless tin (setq tin (ewoc-nth c 0)))
718 (while (and tin (cvs-fileinfo< fi (ewoc-data tin)))
719 (setq tin (ewoc-prev c tin)))
3c7fafc7 720 (if (null tin) (ewoc-enter-first c fi) ;empty collection
5b467bf4
SM
721 (assert (not (cvs-fileinfo< fi (ewoc-data tin))))
722 (let ((next-tin (ewoc-next c tin)))
723 (while (not (or (null next-tin)
724 (cvs-fileinfo< fi (ewoc-data next-tin))))
725 (setq tin next-tin next-tin (ewoc-next c next-tin)))
726 (if (cvs-fileinfo< (ewoc-data tin) fi)
727 ;; tin < fi < next-tin
728 (ewoc-enter-after c tin fi)
729 ;; fi == tin
730 (cvs-fileinfo-update (ewoc-data tin) fi)
3c7fafc7
SM
731 (ewoc-invalidate c tin)
732 tin))))
5b467bf4 733
6dc7d3d5
SM
734(defcustom cvs-cleanup-functions nil
735 "Functions to tweak the cleanup process.
736The functions are called with a single argument (a FILEINFO) and should
737return a non-nil value if that fileinfo should be removed."
738 :group 'pcl-cvs
739 :type '(hook :options (cvs-cleanup-removed)))
740
741(defun cvs-cleanup-removed (fi)
742 "Non-nil if FI has been cvs-removed but still exists.
743This is intended for use on `cvs-cleanup-functions' when you have cvs-removed
744automatically generated files (which should hence not be under CVS control)
745but can't commit the removal because the repository's owner doesn't understand
746the problem."
747 (and (or (eq (cvs-fileinfo->type fi) 'REMOVED)
748 (and (eq (cvs-fileinfo->type fi) 'CONFLICT)
749 (eq (cvs-fileinfo->subtype fi) 'REMOVED)))
750 (file-exists-p (cvs-fileinfo->full-path fi))))
751
5b467bf4
SM
752;; called at the following times:
753;; - postparse ((eq cvs-auto-remove-handled t) cvs-auto-remove-directories nil)
754;; - pre-run ((eq cvs-auto-remove-handled 'delayed) nil t)
755;; - remove-handled (t (or cvs-auto-remove-directories 'handled) t)
756;; - cvs-cmd-do (nil nil t)
757;; - post-ignore (nil nil nil)
758;; - acknowledge (nil nil nil)
759;; - remove (nil nil nil)
760(defun cvs-cleanup-collection (c rm-handled rm-dirs rm-msgs)
761 "Remove undesired entries.
762C is the collection
763RM-HANDLED if non-nil means remove handled entries.
764RM-DIRS behaves like `cvs-auto-remove-directories'.
765RM-MSGS if non-nil means remove messages."
766 (let (last-fi first-dir (rerun t))
767 (while rerun
768 (setq rerun nil)
769 (setq first-dir t)
770 (setq last-fi (cvs-create-fileinfo 'DEAD "../" "" "")) ;place-holder
771 (ewoc-filter
772 c (lambda (fi)
773 (let* ((type (cvs-fileinfo->type fi))
774 (subtype (cvs-fileinfo->subtype fi))
775 (keep
776 (case type
777 ;; remove temp messages and keep the others
b15b5618 778 (MESSAGE (not (or rm-msgs (eq subtype 'TEMP))))
5b467bf4
SM
779 ;; remove entries
780 (DEAD nil)
781 ;; handled also?
782 (UP-TO-DATE (not rm-handled))
783 ;; keep the rest
6dc7d3d5
SM
784 (t (not (run-hook-with-args-until-success
785 'cvs-cleanup-functions fi))))))
027b73ac 786
5b467bf4
SM
787 ;; mark dirs for removal
788 (when (and keep rm-dirs
789 (eq (cvs-fileinfo->type last-fi) 'DIRCHANGE)
790 (not (when first-dir (setq first-dir nil) t))
791 (or (eq rm-dirs 'all)
792 (not (cvs-string-prefix-p
793 (cvs-fileinfo->dir last-fi)
794 (cvs-fileinfo->dir fi)))
795 (and (eq type 'DIRCHANGE) (eq rm-dirs 'empty))
796 (eq subtype 'FOOTER)))
797 (setf (cvs-fileinfo->type last-fi) 'DEAD)
798 (setq rerun t))
b15b5618
SM
799 (when keep (setq last-fi fi)))))
800 ;; remove empty last dir
801 (when (and rm-dirs
802 (not first-dir)
803 (eq (cvs-fileinfo->type last-fi) 'DIRCHANGE))
804 (setf (cvs-fileinfo->type last-fi) 'DEAD)
805 (setq rerun t)))))
5b467bf4
SM
806
807(defun cvs-get-cvsroot ()
808 "Gets the CVSROOT for DIR."
809 (let ((cvs-cvsroot-file (expand-file-name "Root" "CVS")))
810 (or (cvs-file-to-string cvs-cvsroot-file t)
811 cvs-cvsroot
812 (getenv "CVSROOT")
813 "?????")))
814
815(defun cvs-get-module ()
816 "Return the current CVS module.
817This usually doesn't really work but is a handy initval in a prompt."
818 (let* ((repfile (expand-file-name "Repository" "CVS"))
819 (rep (cvs-file-to-string repfile t)))
820 (cond
821 ((null rep) "")
822 ((not (file-name-absolute-p rep)) rep)
823 (t
824 (let* ((root (cvs-get-cvsroot))
825 (str (concat (file-name-as-directory (or root "/")) " || " rep)))
826 (if (and root (string-match "\\(.*\\) || \\1\\(.*\\)\\'" str))
827 (match-string 2 str)
828 (file-name-nondirectory rep)))))))
829
830
831\f
832;;;;
833;;;; running a "cvs checkout".
834;;;;
835
836;;;###autoload
837(defun cvs-checkout (modules dir flags)
838 "Run a 'cvs checkout MODULES' in DIR.
839Feed the output to a *cvs* buffer, display it in the current window,
840and run `cvs-mode' on it.
841
842With a prefix argument, prompt for cvs FLAGS to use."
843 (interactive
844 (list (cvs-string->strings (read-string "Module(s): " (cvs-get-module)))
845 (read-file-name "CVS Checkout Directory: "
846 nil default-directory nil)
847 (cvs-add-branch-prefix
848 (cvs-flags-query 'cvs-checkout-flags "cvs checkout flags"))))
849 (when (eq flags t)
850 (setf flags (cvs-flags-query 'cvs-checkout-flags nil 'noquery)))
851 (cvs-cmd-do "checkout" (or dir default-directory)
852 (append flags modules) nil 'new
853 :noexist t))
854
855\f
027b73ac 856;;;;
5b467bf4 857;;;; The code for running a "cvs update" and friends in various ways.
027b73ac 858;;;;
5b467bf4
SM
859
860(defun-cvs-mode (cvs-mode-revert-buffer . SIMPLE)
861 (&optional ignore-auto noconfirm)
44946a4c 862 "Rerun `cvs-examine' on the current directory with the defauls flags."
5b467bf4
SM
863 (interactive)
864 (cvs-examine default-directory t))
865
866(defun cvs-query-directory (msg)
867 ;; last-command-char = ?\r hints that the command was run via M-x
868 (if (and (cvs-buffer-p)
869 (not current-prefix-arg)
870 (not (eq last-command-char ?\r)))
871 default-directory
872 (read-file-name msg nil default-directory nil)))
873
6dc7d3d5
SM
874;;;###autoload
875(defun cvs-quickdir (dir &optional flags noshow)
876 "Open a *cvs* buffer on DIR without running cvs.
877With a prefix argument, prompt for a directory to use.
878A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]),
879 prevents reuse of an existing *cvs* buffer.
880Optional argument NOSHOW if non-nil means not to display the buffer.
881FLAGS is ignored."
882 (interactive (list (cvs-query-directory "CVS quickdir (directory): ")))
883 ;; FIXME: code duplication with cvs-cmd-do and cvs-parse-process
884 (let* ((dir (file-name-as-directory
885 (abbreviate-file-name (expand-file-name dir))))
886 (new (> (prefix-numeric-value current-prefix-arg) 8))
887 (cvsbuf (cvs-make-cvs-buffer dir new))
888 last)
889 ;; Check that dir is under CVS control.
890 (unless (file-directory-p dir)
891 (error "%s is not a directory" dir))
892 (unless (file-directory-p (expand-file-name "CVS" dir))
893 (error "%s does not contain CVS controlled files" dir))
894 (set-buffer cvsbuf)
895 (dolist (fi (cvs-fileinfo-from-entries ""))
896 (setq last (cvs-addto-collection cvs-cookies fi last)))
897 (cvs-cleanup-collection cvs-cookies
898 (eq cvs-auto-remove-handled t)
899 cvs-auto-remove-directories
900 nil)
901 (if noshow cvsbuf
902 (let ((pop-up-windows nil)) (pop-to-buffer cvsbuf)))))
5b467bf4
SM
903
904;;;###autoload
905(defun cvs-examine (directory flags &optional noshow)
906 "Run a `cvs -n update' in the specified DIRECTORY.
907That is, check what needs to be done, but don't change the disc.
908Feed the output to a *cvs* buffer and run `cvs-mode' on it.
909With a prefix argument, prompt for a directory and cvs FLAGS to use.
910A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]),
911 prevents reuse of an existing *cvs* buffer.
912Optional argument NOSHOW if non-nil means not to display the buffer."
913 (interactive (list (cvs-query-directory "CVS Examine (directory): ")
914 (cvs-flags-query 'cvs-update-flags "cvs -n update flags")))
915 (when (eq flags t)
916 (setf flags (cvs-flags-query 'cvs-update-flags nil 'noquery)))
917 (cvs-cmd-do "update" directory flags nil
918 (> (prefix-numeric-value current-prefix-arg) 8)
919 :cvsargs '("-n")
920 :noshow noshow
921 :dont-change-disc t))
922
923
924;;;###autoload
925(defun cvs-update (directory flags)
926 "Run a `cvs update' in the current working DIRECTORY.
927Feed the output to a *cvs* buffer and run `cvs-mode' on it.
928With a prefix argument, prompt for a directory and cvs FLAGS to use.
929A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]),
930 prevents reuse of an existing *cvs* buffer."
931 (interactive (list (cvs-query-directory "CVS Update (directory): ")
932 (cvs-flags-query 'cvs-update-flags "cvs update flags")))
933 (when (eq flags t)
934 (setf flags (cvs-flags-query 'cvs-update-flags nil 'noquery)))
935 (cvs-cmd-do "update" directory flags nil
936 (> (prefix-numeric-value current-prefix-arg) 8)))
937
938
939;;;###autoload
940(defun cvs-status (directory flags &optional noshow)
941 "Run a `cvs status' in the current working DIRECTORY.
942Feed the output to a *cvs* buffer and run `cvs-mode' on it.
943With a prefix argument, prompt for a directory and cvs FLAGS to use.
944A prefix arg >8 (ex: \\[universal-argument] \\[universal-argument]),
945 prevents reuse of an existing *cvs* buffer.
946Optional argument NOSHOW if non-nil means not to display the buffer."
947 (interactive (list (cvs-query-directory "CVS Status (directory): ")
948 (cvs-flags-query 'cvs-status-flags "cvs status flags")))
949 (when (eq flags t)
950 (setf flags (cvs-flags-query 'cvs-status-flags nil 'noquery)))
951 (cvs-cmd-do "status" directory flags nil
952 (> (prefix-numeric-value current-prefix-arg) 8)
953 :noshow noshow :dont-change-disc t))
954
5b467bf4
SM
955(defun cvs-update-filter (proc string)
956 "Filter function for pcl-cvs.
957This function gets the output that CVS sends to stdout. It inserts
958the STRING into (process-buffer PROC) but it also checks if CVS is waiting
959for a lock file. If so, it inserts a message cookie in the *cvs* buffer."
960 (save-match-data
961 (with-current-buffer (process-buffer proc)
962 (let ((inhibit-read-only t))
963 (save-excursion
964 ;; Insert the text, moving the process-marker.
965 (goto-char (process-mark proc))
966 (insert string)
967 (set-marker (process-mark proc) (point))
968 ;; FIXME: Delete any old lock message
969 ;;(if (tin-nth cookies 1)
970 ;; (tin-delete cookies
971 ;; (tin-nth cookies 1)))
972 ;; Check if CVS is waiting for a lock.
973 (beginning-of-line 0) ;Move to beginning of last complete line.
974 (when (looking-at "^[ a-z]+: \\(.*waiting for .*lock in \\(.*\\)\\)$")
975 (let ((msg (match-string 1))
976 (lock (match-string 2)))
977 (with-current-buffer cvs-buffer
978 (set (make-local-variable 'cvs-lock-file) lock)
979 ;; display the lock situation in the *cvs* buffer:
980 (ewoc-enter-last
981 cvs-cookies
982 (cvs-create-fileinfo
983 'MESSAGE "" " "
984 (concat msg
027b73ac 985 (substitute-command-keys
5b467bf4
SM
986 "\n\t(type \\[cvs-mode-delete-lock] to delete it)"))
987 :subtype 'TEMP))
988 (pop-to-buffer (current-buffer))
989 (goto-char (point-max))
990 (beep)))))))))
991
992\f
993;;;;
994;;;; The cvs-mode and its associated commands.
995;;;;
996
997(cvs-prefix-define cvs-force-command "" "" '("/F") cvs-qtypedesc-string1)
998(defun-cvs-mode cvs-mode-force-command (arg)
999 "Force the next cvs command to operate on all the selected files.
1000By default, cvs commands only operate on files on which the command
1001\"makes sense\". This overrides the safety feature on the next cvs command.
1002It actually behaves as a toggle. If prefixed by \\[universal-argument] \\[universal-argument],
1003the override will persist until the next toggle."
1004 (interactive "P")
1005 (cvs-prefix-set 'cvs-force-command arg))
1006
5b467bf4 1007(put 'cvs-mode 'mode-class 'special)
cb3430a1 1008(define-derived-mode cvs-mode fundamental-mode "CVS"
5b467bf4 1009 "Mode used for PCL-CVS, a frontend to CVS.
cb3430a1 1010Full documentation is in the Texinfo file."
5b467bf4
SM
1011 (setq mode-line-process
1012 '("" cvs-force-command cvs-ignore-marks-modif
1013 ":" (cvs-branch-prefix
1014 ("" cvs-branch-prefix (cvs-secondary-branch-prefix
1015 ("->" cvs-secondary-branch-prefix))))
1016 " " cvs-mode-line-process))
1017 (buffer-disable-undo (current-buffer))
1018 ;;(set (make-local-variable 'goal-column) cvs-cursor-column)
1019 (set (make-local-variable 'revert-buffer-function) 'cvs-mode-revert-buffer)
cb3430a1 1020 (setq truncate-lines t)
5b467bf4
SM
1021 (cvs-prefix-make-local 'cvs-branch-prefix)
1022 (cvs-prefix-make-local 'cvs-secondary-branch-prefix)
1023 (cvs-prefix-make-local 'cvs-force-command)
1024 (cvs-prefix-make-local 'cvs-ignore-marks-modif)
1025 (make-local-variable 'cvs-mode-line-process)
1026 (make-local-variable 'cvs-temp-buffers))
1027
1028
1029(defun cvs-buffer-p (&optional buffer)
1030 "Return whether the (by default current) BUFFER is a `cvs-mode' buffer."
1031 (save-excursion
1032 (if buffer (set-buffer buffer))
1033 (and (eq major-mode 'cvs-mode))))
1034
1035(defun cvs-buffer-check ()
1036 "Check that the current buffer follows cvs-buffer's conventions."
1037 (let ((buf (current-buffer))
1038 (check 'none))
1039 (or (and (setq check 'collection)
1040 (eq (ewoc-buffer cvs-cookies) buf)
1041 (setq check 'cvs-temp-buffer)
1042 (or (null cvs-temp-buffer)
1043 (null (buffer-name cvs-temp-buffer))
1044 (and (eq (with-current-buffer cvs-temp-buffer cvs-buffer) buf)
1045 (equal (with-current-buffer cvs-temp-buffer
1046 default-directory)
1047 default-directory)))
1048 t)
1049 (error "Inconsistent %s in buffer %s" check (buffer-name buf)))))
1050
1051
1052(defun-cvs-mode cvs-mode-quit ()
1053 "Quit PCL-CVS, killing the *cvs* buffer."
1054 (interactive)
1055 (and (y-or-n-p "Quit pcl-cvs? ") (kill-buffer (current-buffer))))
1056
1057;; Give help....
1058
1059(defun cvs-help ()
1060 "Display help for various PCL-CVS commands."
1061 (interactive)
1062 (if (eq last-command 'cvs-help)
1063 (describe-function 'cvs-mode) ; would need to use minor-mode for cvs-edit-mode
1064 (message
1065 (substitute-command-keys
1066 "`\\[cvs-help]':help `\\[cvs-mode-add]':add `\\[cvs-mode-commit]':commit \
1067`\\[cvs-mode-diff-map]':diff* `\\[cvs-mode-log]':log \
1068`\\[cvs-mode-remove]':remove `\\[cvs-mode-status]':status \
1069`\\[cvs-mode-undo]':undo"))))
1070
1071(defun cvs-mode-diff-help ()
1072 "Display help for various PCL-CVS diff commands."
1073 (interactive)
1074 (if (eq last-command 'cvs-mode-diff-help)
1075 (describe-function 'cvs-mode) ; no better docs for diff stuff?
1076 (message
1077 (substitute-command-keys
1078 "`\\[cvs-mode-diff]':diff `\\[cvs-mode-idiff]':idiff \
1079`\\[cvs-mode-diff-head]':head `\\[cvs-mode-diff-vendor]':vendor \
1080`\\[cvs-mode-diff-backup]':backup `\\[cvs-mode-idiff-other]':other \
1081`\\[cvs-mode-imerge]':imerge"))))
1082
1083;; Move around in the buffer
1084
1085(defun-cvs-mode cvs-mode-previous-line (arg)
1086 "Go to the previous line.
1087If a prefix argument is given, move by that many lines."
1088 (interactive "p")
44946a4c 1089 (ewoc-goto-prev cvs-cookies arg))
5b467bf4
SM
1090
1091(defun-cvs-mode cvs-mode-next-line (arg)
1092 "Go to the next line.
1093If a prefix argument is given, move by that many lines."
1094 (interactive "p")
44946a4c 1095 (ewoc-goto-next cvs-cookies arg))
5b467bf4 1096
027b73ac 1097;;;;
5b467bf4 1098;;;; Mark handling
027b73ac 1099;;;;
5b467bf4
SM
1100
1101(defun-cvs-mode cvs-mode-mark (&optional arg)
1102 "Mark the fileinfo on the current line.
1103If the fileinfo is a directory, all the contents of that directory are
1104marked instead. A directory can never be marked."
1105 (interactive)
44946a4c 1106 (let* ((tin (ewoc-locate cvs-cookies))
5b467bf4
SM
1107 (fi (ewoc-data tin)))
1108 (if (eq (cvs-fileinfo->type fi) 'DIRCHANGE)
1109 ;; it's a directory: let's mark all files inside
1110 (ewoc-map
1111 (lambda (f dir)
1112 (when (cvs-dir-member-p f dir)
1113 (setf (cvs-fileinfo->marked f)
1114 (not (if (eq arg 'toggle) (cvs-fileinfo->marked f) arg)))
1115 t)) ;Tell cookie to redisplay this cookie.
1116 cvs-cookies
1117 (cvs-fileinfo->dir fi))
1118 ;; not a directory: just do the obvious
1119 (setf (cvs-fileinfo->marked fi)
1120 (not (if (eq arg 'toggle) (cvs-fileinfo->marked fi) arg)))
1121 (ewoc-invalidate cvs-cookies tin)
1122 (cvs-mode-next-line 1))))
1123
1124(defun cvs-mouse-toggle-mark (e)
1125 "Toggle the mark of the entry under the mouse."
1126 (interactive "e")
44946a4c
SM
1127 (save-excursion
1128 (mouse-set-point e)
1129 (cvs-mode-mark 'toggle)))
5b467bf4
SM
1130
1131(defun-cvs-mode cvs-mode-unmark ()
1132 "Unmark the fileinfo on the current line."
1133 (interactive)
1134 (cvs-mode-mark t))
1135
1136(defun-cvs-mode cvs-mode-mark-all-files ()
1137 "Mark all files."
1138 (interactive)
1139 (ewoc-map (lambda (cookie)
1140 (unless (eq (cvs-fileinfo->type cookie) 'DIRCHANGE)
1141 (setf (cvs-fileinfo->marked cookie) t)))
1142 cvs-cookies))
1143
1144(defun-cvs-mode cvs-mode-mark-matching-files (regex)
1145 "Mark all files matching REGEX."
1146 (interactive "sMark files matching: ")
1147 (ewoc-map (lambda (cookie)
1148 (when (and (not (eq (cvs-fileinfo->type cookie) 'DIRCHANGE))
1149 (string-match regex (cvs-fileinfo->file cookie)))
1150 (setf (cvs-fileinfo->marked cookie) t)))
1151 cvs-cookies))
1152
1153(defun-cvs-mode cvs-mode-unmark-all-files ()
1154 "Unmark all files.
1155Directories are also unmarked, but that doesn't matter, since
1156they should always be unmarked."
1157 (interactive)
1158 (ewoc-map (lambda (cookie)
1159 (setf (cvs-fileinfo->marked cookie) nil)
1160 t)
1161 cvs-cookies))
1162
1163(defun-cvs-mode cvs-mode-unmark-up ()
1164 "Unmark the file on the previous line."
1165 (interactive)
44946a4c 1166 (let ((tin (ewoc-goto-prev cvs-cookies 1)))
5b467bf4
SM
1167 (when tin
1168 (setf (cvs-fileinfo->marked (ewoc-data tin)) nil)
1169 (ewoc-invalidate cvs-cookies tin))))
1170
1171(defconst cvs-ignore-marks-alternatives
1172 '(("toggle-marks" . "/TM")
1173 ("force-marks" . "/FM")
1174 ("ignore-marks" . "/IM")))
1175
1176(cvs-prefix-define cvs-ignore-marks-modif
1177 "Prefix to decide whether to ignore marks or not."
1178 "active"
1179 (mapcar 'cdr cvs-ignore-marks-alternatives)
1180 (cvs-qtypedesc-create
1181 (lambda (str) (cdr (assoc str cvs-ignore-marks-alternatives)))
1182 (lambda (obj) (caar (member* obj cvs-ignore-marks-alternatives :key 'cdr)))
1183 (lambda () cvs-ignore-marks-alternatives)
1184 nil t))
027b73ac 1185
5b467bf4
SM
1186(defun-cvs-mode cvs-mode-toggle-marks (arg)
1187 "Toggle whether the next CVS command uses marks.
1188See `cvs-prefix-set' for further description of the behavior.
1189\\[universal-argument] 1 selects `force-marks',
1190\\[universal-argument] 2 selects `ignore-marks',
1191\\[universal-argument] 3 selects `toggle-marks'."
1192 (interactive "P")
1193 (cvs-prefix-set 'cvs-ignore-marks-modif arg))
027b73ac 1194
5b467bf4
SM
1195(defun cvs-ignore-marks-p (cmd &optional read-only)
1196 (let ((default (if (member cmd cvs-invert-ignore-marks)
1197 (not cvs-default-ignore-marks)
1198 cvs-default-ignore-marks))
1199 (modif (cvs-prefix-get 'cvs-ignore-marks-modif read-only)))
1200 (cond
1201 ((equal modif "/IM") t)
1202 ((equal modif "/TM") (not default))
1203 ((equal modif "/FM") nil)
1204 (t default))))
1205
1206(defun cvs-mode-mark-get-modif (cmd)
1207 (if (cvs-ignore-marks-p cmd 'read-only) "/IM" "/FM"))
1208
1209(defvar cvs-minor-current-files)
1210(defun cvs-get-marked (&optional ignore-marks ignore-contents)
1211 "Return a list of all selected fileinfos.
1212If there are any marked tins, and IGNORE-MARKS is nil, return them.
1213Otherwise, if the cursor selects a directory, and IGNORE-CONTENTS is
1214nil, return all files in it, else return just the directory.
1215Otherwise return (a list containing) the file the cursor points to, or
1216an empty list if it doesn't point to a file at all.
1217
1218Args: &optional IGNORE-MARKS IGNORE-CONTENTS."
1219
1220 (let ((fis nil))
96190aa1
SM
1221 (dolist (fi (if (and (boundp 'cvs-minor-current-files)
1222 (consp cvs-minor-current-files))
5b467bf4
SM
1223 (mapcar
1224 (lambda (f)
96190aa1
SM
1225 (if (cvs-fileinfo-p f) f
1226 (let ((f (file-relative-name f)))
1227 (if (file-directory-p f)
1228 (cvs-create-fileinfo
1229 'DIRCHANGE (file-name-as-directory f) "." "")
1230 (let ((dir (file-name-directory f))
1231 (file (file-name-nondirectory f)))
1232 (cvs-create-fileinfo
1233 'UNKNOWN (or dir "") file ""))))))
5b467bf4
SM
1234 cvs-minor-current-files)
1235 (or (and (not ignore-marks)
1236 (ewoc-collect cvs-cookies
1237 'cvs-fileinfo->marked))
44946a4c 1238 (list (ewoc-data (ewoc-locate cvs-cookies))))))
027b73ac 1239
5b467bf4
SM
1240 (if (or ignore-contents (not (eq (cvs-fileinfo->type fi) 'DIRCHANGE)))
1241 (push fi fis)
1242 ;; If a directory is selected, return members, if any.
1243 (setq fis
1244 (append (ewoc-collect cvs-cookies
1245 'cvs-dir-member-p
1246 (cvs-fileinfo->dir fi))
1247 fis))))
1248 (nreverse fis)))
1249
1250(defun* cvs-mode-marked (filter &optional (cmd (symbol-name filter))
1251 &key read-only one file)
1252 "Get the list of marked FIS.
1253CMD is used to determine whether to use the marks or not.
1254Only files for which FILTER is applicable are returned.
1255If READ-ONLY is non-nil, the current toggling is left intact.
1256If ONE is non-nil, marks are ignored and a single FI is returned.
1257If FILE is non-nil, directory entries won't be selected."
1258 (let* ((fis (cvs-get-marked (or one (cvs-ignore-marks-p cmd read-only))
1259 (and (not file)
1260 (cvs-applicable-p 'DIRCHANGE filter))))
1261 (force (cvs-prefix-get 'cvs-force-command))
1262 (fis (car (cvs-partition
1263 (lambda (fi) (cvs-applicable-p fi (and (not force) filter)))
1264 fis))))
1265 (cond
1266 ((null fis)
1267 (error "`%s' is not applicable to any of the selected files." filter))
1268 ((and one (cdr fis))
1269 (error "`%s' is only applicable to a single file." cmd))
1270 (one (car fis))
1271 (t fis))))
1272
1273(defun cvs-enabledp (filter)
1274 "Determine whether FILTER applies to at least one of the selected files."
1275 (ignore-errors (cvs-mode-marked filter nil :read-only t)))
1276
1277(defun cvs-mode-files (&rest -cvs-mode-files-args)
1278 (cvs-mode!
1279 (lambda ()
1280 (mapcar 'cvs-fileinfo->full-path
1281 (apply 'cvs-mode-marked -cvs-mode-files-args)))))
1282
1283;;;
1284;;; Interface between CVS-Edit and PCL-CVS
1285;;;
1286
1287(defun cvs-mode-commit-setup ()
1288 "Run `cvs-mode-commit' with setup."
1289 (interactive)
1290 (cvs-mode-commit 'force))
1291
1292(defun cvs-mode-commit (setup)
1293 "Check in all marked files, or the current file.
1294The user will be asked for a log message in a buffer.
1295The buffer's mode and name is determined by the \"message\" setting
1296 of `cvs-buffer-name-alist'.
1297The POSTPROC specified there (typically `cvs-edit') is then called,
1298 passing it the SETUP argument."
1299 (interactive "P")
1300 ;; It seems that the save-excursion that happens if I use the better
1301 ;; form of `(cvs-mode! (lambda ...))' screws up a couple things which
1302 ;; end up being rather annoying (like cvs-edit-mode's message being
1303 ;; displayed in the wrong minibuffer).
1304 (cvs-mode!)
1305 (pop-to-buffer (cvs-temp-buffer "message" 'normal 'nosetup))
1306 (set (make-local-variable 'cvs-minor-wrap-function) 'cvs-commit-minor-wrap)
1307 (let ((lbd list-buffers-directory)
dedffa6a 1308 (setupfun (or (nth 2 (cdr (assoc "message" cvs-buffer-name-alist)))
5b467bf4
SM
1309 'cvs-edit)))
1310 (funcall setupfun 'cvs-do-commit setup 'cvs-commit-filelist)
1311 (set (make-local-variable 'list-buffers-directory) lbd)))
1312
1313(defun cvs-commit-minor-wrap (buf f)
1314 (let ((cvs-ignore-marks-modif (cvs-mode-mark-get-modif "commit")))
1315 (funcall f)))
1316
1317(defun cvs-commit-filelist () (cvs-mode-files 'commit nil :read-only t :file t))
1318
1319(defun cvs-do-commit (flags)
1320 "Do the actual commit, using the current buffer as the log message."
1321 (interactive (list (cvs-flags-query 'cvs-commit-flags "cvs commit flags")))
027b73ac 1322 (let ((msg (buffer-substring-no-properties (point-min) (point-max))))
5b467bf4
SM
1323 (cvs-mode!)
1324 ;;(pop-to-buffer cvs-buffer)
1325 (cvs-mode-do "commit" (list* "-m" msg flags) 'commit)))
1326
1327
027b73ac 1328;;;;
5b467bf4 1329;;;; CVS Mode commands
027b73ac 1330;;;;
5b467bf4
SM
1331
1332(defun-cvs-mode (cvs-mode-insert . NOARGS) (file)
1333 "Insert an entry for a specific file."
1334 (interactive
1335 (list (read-file-name "File to insert: " nil nil nil
96190aa1
SM
1336 ;; Can't use ignore-errors here because interactive
1337 ;; specs aren't byte-compiled.
1338 (condition-case nil
1339 (cvs-fileinfo->dir
1340 (car (cvs-mode-marked nil nil :read-only t)))
1341 (error nil)))))
6dc7d3d5
SM
1342 (let ((file (file-relative-name (directory-file-name file))) last)
1343 (dolist (fi (cvs-fileinfo-from-entries file))
3c7fafc7
SM
1344 (setq last (cvs-addto-collection cvs-cookies fi last)))
1345 ;; There should have been at least one entry.
1346 (goto-char (ewoc-location last))))
5b467bf4
SM
1347
1348(defun-cvs-mode (cvs-mode-add . SIMPLE) (flags)
1349 "Add marked files to the cvs repository.
1350With prefix argument, prompt for cvs flags."
1351 (interactive (list (cvs-flags-query 'cvs-add-flags "cvs add flags")))
1352 (let ((fis (cvs-mode-marked 'add))
1353 (needdesc nil) (dirs nil))
1354 ;; find directories and look for fis needing a description
1355 (dolist (fi fis)
1356 (cond
1357 ((file-directory-p (cvs-fileinfo->full-path fi)) (push fi dirs))
1358 ((eq (cvs-fileinfo->type fi) 'UNKNOWN) (setq needdesc t))))
1359 ;; prompt for description if necessary
1360 (let* ((msg (if (and needdesc
1361 (or current-prefix-arg (not cvs-add-default-message)))
1362 (read-from-minibuffer "Enter description: ")
1363 (or cvs-add-default-message "")))
1364 (flags (list* "-m" msg flags))
1365 (postproc
1366 ;; setup postprocessing for the directory entries
1367 (when dirs
1368 `((cvs-run-process (list "-n" "update")
1369 ',dirs
1370 '(cvs-parse-process t))
1371 (dolist (fi ',dirs) (setf (cvs-fileinfo->type fi) 'DEAD))))))
1372 (cvs-mode-run "add" flags fis :postproc postproc))))
1373
5b467bf4
SM
1374(defun-cvs-mode (cvs-mode-diff . DOUBLE) (flags)
1375 "Diff the selected files against the repository.
1376This command compares the files in your working area against the
1377revision which they are based upon."
1378 (interactive
1379 (list (cvs-add-branch-prefix
1380 (cvs-add-secondary-branch-prefix
1381 (cvs-flags-query 'cvs-diff-flags "cvs diff flags")))))
1382 (cvs-mode-do "diff" flags 'diff
1383 :show t)) ;; :ignore-exit t
1384
5b467bf4
SM
1385(defun-cvs-mode (cvs-mode-diff-head . SIMPLE) (flags)
1386 "Diff the selected files against the head of the current branch.
1387See ``cvs-mode-diff'' for more info."
1388 (interactive (list (cvs-flags-query 'cvs-diff-flags "cvs diff flags")))
1389 (cvs-mode-diff-1 (cons "-rHEAD" flags)))
1390
5b467bf4
SM
1391(defun-cvs-mode (cvs-mode-diff-vendor . SIMPLE) (flags)
1392 "Diff the selected files against the head of the vendor branch.
1393See ``cvs-mode-diff'' for more info."
1394 (interactive (list (cvs-flags-query 'cvs-diff-flags "cvs diff flags")))
1395 (cvs-mode-diff-1 (cons (concat "-r" cvs-vendor-branch) flags)))
1396
5b467bf4
SM
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
5b467bf4
SM
1420(defun cvs-diff-backup-extractor (fileinfo)
1421 "Return the filename and the name of the backup file as a list.
1422Signal an error if there is no backup file."
1423 (let ((backup-file (cvs-fileinfo->backup-file fileinfo)))
1424 (unless backup-file
1425 (error "%s has no backup file." (cvs-fileinfo->full-path fileinfo)))
1426 (list backup-file (cvs-fileinfo->file fileinfo))))
1427
1428;;
1429;; Emerge support
1430;;
1431(defun cvs-emerge-diff (b1 b2) (emerge-buffers b1 b2 b1))
1432(defun cvs-emerge-merge (b1 b2 base out)
1433 (emerge-buffers-with-ancestor b1 b2 base (find-file-noselect out)))
1434
1435;;
1436;; Ediff support
027b73ac 1437;;
5b467bf4
SM
1438
1439(defvar ediff-after-quit-destination-buffer)
1440(defvar cvs-transient-buffers)
1441(defun cvs-ediff-startup-hook ()
1442 (add-hook 'ediff-after-quit-hook-internal
1443 `(lambda ()
1444 (cvs-ediff-exit-hook
1445 ',ediff-after-quit-destination-buffer ',cvs-transient-buffers))
1446 nil 'local))
1447
1448(defun cvs-ediff-exit-hook (cvs-buf tmp-bufs)
1449 ;; kill the temp buffers (and their associated windows)
1450 (dolist (tb tmp-bufs)
1451 (when (and tb (buffer-live-p tb) (not (buffer-modified-p tb)))
1452 (let ((win (get-buffer-window tb t)))
a7996e05 1453 (kill-buffer tb)
eed914af 1454 (when (window-live-p win) (ignore-errors (delete-window win))))))
5b467bf4
SM
1455 ;; switch back to the *cvs* buffer
1456 (when (and cvs-buf (buffer-live-p cvs-buf)
1457 (not (get-buffer-window cvs-buf t)))
1458 (ignore-errors (switch-to-buffer cvs-buf))))
1459
1460(defun cvs-ediff-diff (b1 b2)
1461 (let ((ediff-after-quit-destination-buffer (current-buffer))
1462 (startup-hook '(cvs-ediff-startup-hook)))
a7996e05 1463 (ediff-buffers b1 b2 startup-hook 'ediff-revision)))
5b467bf4
SM
1464
1465(defun cvs-ediff-merge (b1 b2 base out)
1466 (let ((ediff-after-quit-destination-buffer (current-buffer))
1467 (startup-hook '(cvs-ediff-startup-hook)))
1468 (ediff-merge-buffers-with-ancestor
1469 b1 b2 base startup-hook
1470 'ediff-merge-revisions-with-ancestor
1471 out)))
1472
1473;;
1474;; Interactive merge/diff support.
1475;;
1476
1477(defun cvs-retrieve-revision (fileinfo rev)
1478 "Retrieve the given REVision of the file in FILEINFO into a new buffer."
eed914af
SM
1479 (let* ((file (cvs-fileinfo->full-path fileinfo))
1480 (buffile (concat file "." rev)))
1481 (or (find-buffer-visiting buffile)
1482 (with-current-buffer (create-file-buffer buffile)
1483 (message "Retrieving revision %s..." rev)
1484 (let ((res (call-process cvs-program nil t nil
1485 "-q" "update" "-p" "-r" rev file)))
1486 (when (and res (not (and (equal 0 res))))
1487 (error "Something went wrong retrieving revision %s: %s" rev res))
1488 (set-buffer-modified-p nil)
1489 (let ((buffer-file-name (expand-file-name file)))
1490 (after-find-file))
1491 (toggle-read-only 1)
1492 (message "Retrieving revision %s... Done" rev)
1493 (current-buffer))))))
5b467bf4 1494
6dc7d3d5 1495(eval-and-compile (autoload 'smerge-ediff "smerge-mode"))
5b467bf4 1496
6dc7d3d5
SM
1497;; FIXME: The user should be able to specify ancestor/head/backup and we should
1498;; provide sensible defaults when merge info is unavailable (rather than rely
1499;; on smerge-ediff). Also provide sane defaults for need-merge files.
5b467bf4
SM
1500(defun-cvs-mode cvs-mode-imerge ()
1501 "Merge interactively appropriate revisions of the selected file."
1502 (interactive)
1503 (let ((fi (cvs-mode-marked 'merge nil :one t :file t)))
1504 (let ((merge (cvs-fileinfo->merge fi))
1505 (file (cvs-fileinfo->full-path fi))
1506 (backup-file (cvs-fileinfo->backup-file fi)))
1507 (if (not (and merge backup-file))
1508 (let ((buf (find-file-noselect file)))
1509 (message "Missing merge info or backup file, using VC.")
6dc7d3d5
SM
1510 (with-current-buffer buf
1511 (smerge-ediff)))
5b467bf4
SM
1512 (let* ((ancestor-buf (cvs-retrieve-revision fi (car merge)))
1513 (head-buf (cvs-retrieve-revision fi (cdr merge)))
1514 (backup-buf (let ((auto-mode-alist nil))
1515 (find-file-noselect backup-file)))
1516 ;; this binding is used by cvs-ediff-startup-hook
1517 (cvs-transient-buffers (list ancestor-buf backup-buf head-buf)))
1518 (with-current-buffer backup-buf
1519 (let ((buffer-file-name (expand-file-name file)))
1520 (after-find-file)))
1521 (funcall (cdr cvs-idiff-imerge-handlers)
1522 backup-buf head-buf ancestor-buf file))))))
1523
1524(cvs-flags-define cvs-idiff-version
1525 (list "BASE" cvs-vendor-branch cvs-vendor-branch "BASE" "BASE")
1526 "version: " cvs-qtypedesc-tag)
1527
1528(defun-cvs-mode (cvs-mode-idiff . NOARGS) (&optional rev1 rev2)
1529 "Diff interactively current file to revisions."
1530 (interactive
1531 (let* ((rev1 (cvs-prefix-get 'cvs-branch-prefix))
1532 (rev2 (and rev1 (cvs-prefix-get 'cvs-secondary-branch-prefix))))
1533 (list (or rev1 (cvs-flags-query 'cvs-idiff-version))
1534 rev2)))
1535 (let ((fi (cvs-mode-marked 'diff "idiff" :one t :file t)))
1536 (let* ((file (cvs-fileinfo->full-path fi))
1537 (rev1-buf (cvs-retrieve-revision fi (or rev1 "BASE")))
1538 (rev2-buf (if rev2 (cvs-retrieve-revision fi rev2)))
1539 ;; this binding is used by cvs-ediff-startup-hook
1540 (cvs-transient-buffers (list rev1-buf rev2-buf)))
1541 (funcall (car cvs-idiff-imerge-handlers)
1542 rev1-buf (or rev2-buf (find-file-noselect file))))))
1543
1544(defun-cvs-mode (cvs-mode-idiff-other . NOARGS) ()
1545 "Diff interactively current file to revisions."
1546 (interactive)
1547 (let* ((rev1 (cvs-prefix-get 'cvs-branch-prefix))
1548 (rev2 (and rev1 (cvs-prefix-get 'cvs-secondary-branch-prefix)))
1549 (fis (cvs-mode-marked 'diff "idiff" :file t)))
1550 (when (> (length fis) 2)
1551 (error "idiff-other cannot be applied to more than 2 files at a time."))
44946a4c 1552 (let* ((fi1 (car fis))
5b467bf4
SM
1553 (rev1-buf (if rev1 (cvs-retrieve-revision fi1 rev1)
1554 (find-file-noselect (cvs-fileinfo->full-path fi1))))
1555 rev2-buf)
1556 (if (cdr fis)
dedffa6a 1557 (let ((fi2 (nth 1 fis)))
5b467bf4
SM
1558 (setq rev2-buf
1559 (if rev2 (cvs-retrieve-revision fi2 rev2)
1560 (find-file-noselect (cvs-fileinfo->full-path fi2)))))
1561 (error "idiff-other doesn't know what other file/buffer to use."))
1562 (let* (;; this binding is used by cvs-ediff-startup-hook
1563 (cvs-transient-buffers (list rev1-buf rev2-buf)))
1564 (funcall (car cvs-idiff-imerge-handlers)
1565 rev1-buf rev2-buf)))))
1566
1567
1568(defun cvs-fileinfo-kill (c fi)
1569 "Mark a fileinfo xor its members (in case of a directory) as dead."
1570 (if (eq (cvs-fileinfo->type fi) 'DIRCHANGE)
1571 (dolist (fi (ewoc-collect c 'cvs-dir-member-p
1572 (cvs-fileinfo->dir fi)))
1573 (setf (cvs-fileinfo->type fi) 'DEAD))
1574 (setf (cvs-fileinfo->type fi) 'DEAD)))
1575
cb3430a1
SM
1576(defun cvs-is-within-p (fis dir)
1577 "Non-nil is buffer is inside one of FIS (in DIR)."
1578 (when (stringp buffer-file-name)
1579 (setq buffer-file-name (expand-file-name buffer-file-name))
1580 (let (ret)
1581 (dolist (fi (or fis (list (cvs-create-fileinfo 'DIRCHANGE "" "." ""))))
1582 (when (cvs-string-prefix-p
1583 (expand-file-name (cvs-fileinfo->full-path fi) dir)
1584 buffer-file-name)
1585 (setq ret t)))
1586 ret)))
1587
5b467bf4
SM
1588(defun* cvs-mode-run (cmd flags fis
1589 &key (buf (cvs-temp-buffer))
1590 dont-change-disc cvsargs postproc)
1591 "Generic cvs-mode-<foo> function.
1592Executes `cvs CVSARGS CMD FLAGS FIS'.
1593BUF is the buffer to be used for cvs' output.
1594DONT-CHANGE-DISC non-nil indicates that the command will not change the
1595 contents of files. This is only used by the parser.
1596POSTPROC is a list of expressions to be evaluated at the very end (after
1597 parsing if applicable). It will be prepended with `progn' is necessary."
cb3430a1
SM
1598 (let ((def-dir default-directory))
1599 ;; Save the relevant buffers
1600 (save-some-buffers nil (lambda () (cvs-is-within-p fis def-dir))))
5b467bf4
SM
1601 (unless (listp flags) (error "flags should be a list of strings"))
1602 (let* ((cvs-buf (current-buffer))
1603 (single-dir (or (not (listp cvs-execute-single-dir))
1604 (member cmd cvs-execute-single-dir)))
1605 (parse (member cmd cvs-parse-known-commands))
1606 (args (append cvsargs (list cmd) flags))
dedffa6a 1607 (after-mode (nth 2 (cdr (assoc cmd cvs-buffer-name-alist)))))
5b467bf4
SM
1608 (cvs-cleanup-collection cvs-cookies ;cleanup remaining messages
1609 (eq cvs-auto-remove-handled 'delayed) nil t)
1610 (when (fboundp after-mode)
1611 (setq postproc (append postproc `((,after-mode)))))
1612 (when parse (push `(cvs-parse-process ',dont-change-disc) postproc))
1613 (when (member cmd '("status" "update")) ;FIXME: Yuck!!
1614 ;; absence of `cvs update' output has a specific meaning.
1615 (push
1616 `(dolist (fi ',(or fis
1617 (list (cvs-create-fileinfo 'DIRCHANGE "" "." ""))))
1618 (cvs-fileinfo-kill ',cvs-cookies fi))
1619 postproc))
1620 (setq postproc (if (cdr postproc) (cons 'progn postproc) (car postproc)))
1621 (cvs-update-header args fis)
1622 (with-current-buffer buf
1623 ;;(set (make-local-variable 'cvs-buffer) cvs-buf)
1624 (let ((inhibit-read-only t)) (erase-buffer))
1625 (message "Running cvs %s ..." cmd)
1626 (cvs-run-process args fis postproc single-dir))))
1627
1628
1629(defun* cvs-mode-do (cmd flags filter
1630 &key show dont-change-disc parse cvsargs postproc)
1631 "Generic cvs-mode-<foo> function.
1632Executes `cvs CVSARGS CMD FLAGS' on the selected files.
1633FILTER is passed to `cvs-applicable-p' to only apply the command to
1634 files for which it makes sense.
1635SHOW indicates that CMD should be not be run in the default temp buffer and
1636 should be shown to the user. The buffer and mode to be used is determined
1637 by `cvs-buffer-name-alist'.
1638DONT-CHANGE-DISC non-nil indicates that the command will not change the
1639 contents of files. This is only used by the parser."
1640 (cvs-mode-run cmd flags (cvs-mode-marked filter cmd)
1641 :buf (cvs-temp-buffer (when show cmd))
1642 :dont-change-disc dont-change-disc
1643 :cvsargs cvsargs
1644 :postproc postproc))
1645
1646(defun-cvs-mode (cvs-mode-status . SIMPLE) (flags)
1647 "Show cvs status for all marked files.
1648With prefix argument, prompt for cvs flags."
1649 (interactive (list (cvs-flags-query 'cvs-status-flags "cvs status flags")))
1650 (cvs-mode-do "status" flags nil :dont-change-disc t :show t
1651 :postproc (when (eq cvs-auto-remove-handled 'status)
1652 '((with-current-buffer ,(current-buffer)
1653 (cvs-mode-remove-handled))))))
1654
1655(defun-cvs-mode (cvs-mode-tree . SIMPLE) (flags)
1656 "Call cvstree using the file under the point as a keyfile."
1657 (interactive (list (cvs-flags-query 'cvs-status-flags "cvs status flags")))
1658 (cvs-mode-run "status" (cons "-v" flags) (cvs-mode-marked nil "status")
1659 :buf (cvs-temp-buffer "tree")
1660 :dont-change-disc t
1661 :postproc '((cvs-status-trees))))
1662
1663;; cvs log
1664
1665(defun-cvs-mode (cvs-mode-log . NOARGS) (flags)
1666 "Display the cvs log of all selected files.
1667With prefix argument, prompt for cvs flags."
1668 (interactive (list (cvs-add-branch-prefix
1669 (cvs-flags-query 'cvs-log-flags "cvs log flags"))))
1670 (cvs-mode-do "log" flags nil :show t))
1671
1672
1673(defun-cvs-mode (cvs-mode-update . NOARGS) (flags)
1674 "Update all marked files.
1675With a prefix argument, prompt for cvs flags."
1676 (interactive
1677 (list (cvs-add-branch-prefix
1678 (cvs-add-secondary-branch-prefix
1679 (cvs-flags-query 'cvs-update-flags "cvs update flags")
1680 "-j") "-j")))
1681 (cvs-mode-do "update" flags 'update))
1682
1683
1684(defun-cvs-mode (cvs-mode-examine . NOARGS) (flags)
1685 "Re-examine all marked files.
1686With a prefix argument, prompt for cvs flags."
1687 (interactive
1688 (list (cvs-add-branch-prefix
1689 (cvs-add-secondary-branch-prefix
1690 (cvs-flags-query 'cvs-update-flags "cvs -n update flags")
1691 "-j") "-j")))
1692 (cvs-mode-do "update" flags nil :cvsargs '("-n") :dont-change-disc t))
1693
1694
1695(defun-cvs-mode cvs-mode-ignore (&optional pattern)
1696 "Arrange so that CVS ignores the selected files.
1697This command ignores files that are not flagged as `Unknown'."
1698 (interactive)
1699 (dolist (fi (cvs-mode-marked 'ignore))
1700 (cvs-append-to-ignore (cvs-fileinfo->dir fi) (cvs-fileinfo->file fi))
1701 (setf (cvs-fileinfo->type fi) 'DEAD))
1702 (cvs-cleanup-collection cvs-cookies nil nil nil))
1703
1704
1705(defun cvs-append-to-ignore (dir str)
1706 "Add STR to the .cvsignore file in DIR."
1707 (save-window-excursion
1708 (set-buffer (find-file-noselect (expand-file-name ".cvsignore" dir)))
1709 (when (ignore-errors
1710 (and buffer-read-only
1711 (eq 'CVS (vc-backend buffer-file-name))
d15c2aaa 1712 (not (vc-editable-p buffer-file-name))))
5b467bf4
SM
1713 ;; CVSREAD=on special case
1714 (vc-toggle-read-only))
1715 (goto-char (point-max))
1716 (unless (zerop (current-column)) (insert "\n"))
1717 (insert str "\n")
1718 (if cvs-sort-ignore-file (sort-lines nil (point-min) (point-max)))
1719 (save-buffer)))
1720
1721
1722(defun cvs-mode-find-file-other-window (e)
1723 "Select a buffer containing the file in another window."
1724 (interactive (list last-input-event))
1725 (cvs-mode-find-file e t))
1726
1727
1728(defun cvs-find-modif (fi)
1729 (with-temp-buffer
1730 (call-process cvs-program nil (current-buffer) nil
1731 "-f" "diff" (cvs-fileinfo->file fi))
1732 (goto-char (point-min))
1733 (if (re-search-forward "^\\([0-9]+\\)" nil t)
1734 (string-to-number (match-string 1))
1735 1)))
1736
1737
1738(defun cvs-mode-find-file (e &optional other)
1739 "Select a buffer containing the file.
1740With a prefix, opens the buffer in an OTHER window."
1741 (interactive (list last-input-event current-prefix-arg))
6dc7d3d5
SM
1742 (when (ignore-errors (mouse-set-point e) t) ;for invocation via the mouse
1743 (unless (memq (get-text-property (point) 'face)
1744 '(cvs-dirname-face cvs-filename-face))
1745 (error "Not a file name")))
5b467bf4
SM
1746 (cvs-mode!
1747 (lambda (&optional rev)
1748 (interactive (list (cvs-prefix-get 'cvs-branch-prefix)))
1749 (let* ((cvs-buf (current-buffer))
1750 (fi (cvs-mode-marked nil nil :one t)))
1751 (if (eq (cvs-fileinfo->type fi) 'DIRCHANGE)
1752 (let ((odir default-directory))
1753 (setq default-directory
1754 (cvs-expand-dir-name (cvs-fileinfo->dir fi)))
1755 (if other
1756 (dired-other-window default-directory)
1757 (dired default-directory))
1758 (set-buffer cvs-buf)
1759 (setq default-directory odir))
1760 (let ((buf (if rev (cvs-retrieve-revision fi rev)
1761 (find-file-noselect (cvs-fileinfo->full-path fi)))))
1762 (funcall (if other 'switch-to-buffer-other-window 'switch-to-buffer)
1763 buf)
1764 (when (and cvs-find-file-and-jump (cvs-applicable-p fi 'diff-base))
1765 (goto-line (cvs-find-modif fi)))
1766 buf))))))
1767
1768
1769(defun-cvs-mode (cvs-mode-undo . SIMPLE) (flags)
1770 "Undo local changes to all marked files.
1771The file is removed and `cvs update FILE' is run."
1772 ;;"With prefix argument, prompt for cvs FLAGS."
1773 (interactive (list nil));; (cvs-flags-query 'cvs-undo-flags "undo flags")
1774 (if current-prefix-arg (call-interactively 'cvs-mode-revert-to-rev)
1775 (let* ((fis (cvs-do-removal 'undo "update" 'all))
1776 (removedp (lambda (fi) (eq (cvs-fileinfo->type fi) 'REMOVED)))
1777 (fis-split (cvs-partition removedp fis))
1778 (fis-removed (car fis-split))
1779 (fis-other (cdr fis-split)))
1780 (if (null fis-other)
1781 (when fis-removed (cvs-mode-run "add" nil fis-removed))
1782 (cvs-mode-run "update" flags fis-other
1783 :postproc
1784 (when fis-removed
1785 `((with-current-buffer ,(current-buffer)
1786 (cvs-mode-run "add" nil ',fis-removed)))))))))
1787
1788
1789(defun-cvs-mode (cvs-mode-revert-to-rev . NOARGS) (rev)
1790 "Revert the selected files to an old revision."
1791 (interactive
1792 (list (or (cvs-prefix-get 'cvs-branch-prefix)
1793 (let ((current-prefix-arg '(4)))
1794 (cvs-flags-query 'cvs-idiff-version)))))
1795 (let* ((fis (cvs-mode-marked 'revert "revert" :file t))
1796 (tag (concat "tmp_pcl_tag_" (make-temp-name "")))
1797 (untag `((with-current-buffer ,(current-buffer)
1798 (cvs-mode-run "tag" (list "-d" ',tag) ',fis))))
1799 (update `((with-current-buffer ,(current-buffer)
1800 (cvs-mode-run "update" (list "-j" ',tag "-j" ',rev) ',fis
1801 :postproc ',untag)))))
1802 (cvs-mode-run "tag" (list tag) fis :postproc update)))
1803
1804
1805(defun-cvs-mode cvs-mode-delete-lock ()
1806 "Delete the lock file that CVS is waiting for.
1807Note that this can be dangerous. You should only do this
1808if you are convinced that the process that created the lock is dead."
1809 (interactive)
1810 (let* ((default-directory (cvs-expand-dir-name cvs-lock-file))
1811 (locks (directory-files default-directory nil cvs-lock-file-regexp)))
1812 (cond
1813 ((not locks) (error "No lock files found."))
1814 ((yes-or-no-p (concat "Really delete locks in " cvs-lock-file "? "))
1815 (dolist (lock locks)
1816 (cond ((file-directory-p lock) (delete-directory lock))
1817 ((file-exists-p lock) (delete-file lock))))))))
1818
1819
1820(defun-cvs-mode cvs-mode-remove-handled ()
1821 "Remove all lines that are handled.
1822Empty directories are removed."
1823 (interactive)
1824 (cvs-cleanup-collection cvs-cookies
1825 t (or cvs-auto-remove-directories 'handled) t))
1826
1827
1828(defun-cvs-mode cvs-mode-acknowledge ()
1829 "Remove all marked files from the buffer."
1830 (interactive)
1831 (dolist (fi (cvs-get-marked (cvs-ignore-marks-p "acknowledge") t))
1832 (setf (cvs-fileinfo->type fi) 'DEAD))
1833 (cvs-cleanup-collection cvs-cookies nil nil nil))
1834
5b467bf4
SM
1835(defun cvs-do-removal (filter &optional cmd all)
1836 "Remove files.
1837Returns a list of FIS that should be `cvs remove'd."
1838 (let* ((files (cvs-mode-marked filter cmd :file t :read-only t))
1839 (fis (delete-if (lambda (fi) (eq (cvs-fileinfo->type fi) 'UNKNOWN))
1840 (cvs-mode-marked filter cmd)))
1841 (silent (or (not cvs-confirm-removals)
1842 (cvs-every (lambda (fi)
1843 (or (not (file-exists-p
1844 (cvs-fileinfo->full-path fi)))
1845 (cvs-applicable-p fi 'safe-rm)))
1846 files))))
1847 (when (and (not silent) (equal cvs-confirm-removals 'list))
1848 (save-excursion
1849 (pop-to-buffer (cvs-temp-buffer))
1850 (dolist (fi fis)
1851 (insert (cvs-fileinfo->full-path fi) "\n"))))
1852 (if (not (or silent
1853 (yes-or-no-p (format "Delete %d files? " (length files)))))
1854 (progn (message "Aborting") nil)
1855 (dolist (fi files)
1856 (let* ((type (cvs-fileinfo->type fi))
1857 (file (cvs-fileinfo->full-path fi)))
1858 (when (or all (eq type 'UNKNOWN))
1859 (when (file-exists-p file) (delete-file file))
1860 (unless all (setf (cvs-fileinfo->type fi) 'DEAD) t))))
1861 fis)))
1862
1863(defun-cvs-mode (cvs-mode-remove . SIMPLE) (flags)
1864 "Remove all marked files.
1865With prefix argument, prompt for cvs flags."
1866 (interactive (list (cvs-flags-query 'cvs-remove-flags "cvs remove flags")))
1867 (let ((fis (cvs-do-removal 'remove)))
1868 (if fis (cvs-mode-run "remove" (cons "-f" flags) fis)
1869 (cvs-cleanup-collection cvs-cookies nil nil nil))))
1870
1871
1872(defvar cvs-tag-name "")
1873(defun-cvs-mode (cvs-mode-tag . SIMPLE) (tag &optional flags)
1874 "Run `cvs tag TAG' on all selected files.
1875With prefix argument, prompt for cvs flags."
1876 (interactive
1877 (list (setq cvs-tag-name
1878 (cvs-query-read cvs-tag-name "Tag name: " cvs-qtypedesc-tag))
1879 (cvs-flags-query 'cvs-tag-flags "tag flags")))
1880 (cvs-mode-do "tag" (append flags (list tag))
1881 (when cvs-force-dir-tag 'tag)))
1882
1883(defun-cvs-mode (cvs-mode-untag . SIMPLE) (tag &optional flags)
1884 "Run `cvs tag -d TAG' on all selected files.
1885With prefix argument, prompt for cvs flags."
1886 (interactive
1887 (list (setq cvs-tag-name
1888 (cvs-query-read cvs-tag-name "Tag to delete: " cvs-qtypedesc-tag))
1889 (cvs-flags-query 'cvs-tag-flags "tag flags")))
1890 (cvs-mode-do "tag" (append '("-d") flags (list tag))
1891 (when cvs-force-dir-tag 'tag)))
027b73ac 1892
5b467bf4
SM
1893
1894;; Byte compile files.
1895
1896(defun-cvs-mode cvs-mode-byte-compile-files ()
1897 "Run byte-compile-file on all selected files that end in '.el'."
1898 (interactive)
1899 (let ((marked (cvs-get-marked (cvs-ignore-marks-p "byte-compile"))))
1900 (dolist (fi marked)
1901 (let ((filename (cvs-fileinfo->full-path fi)))
1902 (when (string-match "\\.el\\'" filename)
1903 (byte-compile-file filename))))))
1904
1905;; ChangeLog support.
1906
5b467bf4
SM
1907(defun-cvs-mode cvs-mode-add-change-log-entry-other-window ()
1908 "Add a ChangeLog entry in the ChangeLog of the current directory."
1909 (interactive)
1910 (let* ((fi (cvs-mode-marked nil nil :one t))
1911 (default-directory (cvs-expand-dir-name (cvs-fileinfo->dir fi)))
d6cc3d17
SM
1912 (buffer-file-name (expand-file-name (cvs-fileinfo->file fi)))
1913 change-log-default-name)
1914 (add-change-log-entry-other-window)))
5b467bf4
SM
1915
1916;; interactive commands to set optional flags
1917
1918(defun cvs-mode-set-flags (flag)
1919 "Ask for new setting of cvs-FLAG-flags."
1920 (interactive
1921 (list (completing-read
1922 "Which flag: "
1923 (mapcar 'list '("cvs" "diff" "update" "status" "log" "tag" ;"rtag"
1924 "commit" "remove" "undo" "checkout"))
1925 nil t)))
1926 (let* ((sym (intern (concat "cvs-" flag "-flags"))))
1927 (let ((current-prefix-arg '(16)))
1928 (cvs-flags-query sym (concat flag " flags")))))
1929
1930\f
1931;;;;
1932;;;; Utilities for the *cvs* buffer
1933;;;;
1934
5b467bf4
SM
1935(defun cvs-dir-member-p (fileinfo dir)
1936 "Return true if FILEINFO represents a file in directory DIR."
1937 (and (not (eq (cvs-fileinfo->type fileinfo) 'DIRCHANGE))
b15b5618 1938 (cvs-string-prefix-p dir (cvs-fileinfo->dir fileinfo))))
5b467bf4
SM
1939
1940(defun cvs-execute-single-file (fi extractor program constant-args)
1941 "Internal function for `cvs-execute-single-file-list'."
1942 (let* ((cur-dir (cvs-fileinfo->dir fi))
1943 (default-directory (cvs-expand-dir-name cur-dir))
1944 (inhibit-read-only t)
1945 (arg-list (funcall extractor fi)))
027b73ac 1946
5b467bf4
SM
1947 ;; Execute the command unless extractor returned t.
1948 (when (listp arg-list)
1949 (let* ((args (append constant-args arg-list)))
027b73ac 1950
5b467bf4
SM
1951 (insert (format "=== cd %s\n=== %s %s\n\n"
1952 cur-dir program (cvs-strings->string args)))
027b73ac 1953
5b467bf4
SM
1954 ;; FIXME: return the exit status?
1955 (apply 'call-process program nil t t args)
1956 (goto-char (point-max))))))
1957
1958;; FIXME: make this run in the background ala cvs-run-process...
1959(defun cvs-execute-single-file-list (fis extractor program constant-args)
1960 "Run PROGRAM on all elements on FIS.
1961The PROGRAM will be called with pwd set to the directory the files
1962reside in. CONSTANT-ARGS is a list of strings to pass as arguments to
1963PROGRAM. The arguments given to the program will be CONSTANT-ARGS
1964followed by the list that EXTRACTOR returns.
1965
1966EXTRACTOR will be called once for each file on FIS. It is given
1967one argument, the cvs-fileinfo. It can return t, which means ignore
1968this file, or a list of arguments to send to the program."
1969 (dolist (fi fis)
1970 (cvs-execute-single-file fi extractor program constant-args)))
1971
1972\f
1973(defun cvs-revert-if-needed (fis)
1974 (dolist (fileinfo fis)
1975 (let* ((file (cvs-fileinfo->full-path fileinfo))
1976 (buffer (find-buffer-visiting file)))
1977 ;; For a revert to happen the user must be editing the file...
1978 (unless (or (null buffer)
1979 (eq (cvs-fileinfo->type fileinfo) 'MESSAGE)
1980 ;; FIXME: check whether revert is really needed.
1981 ;; `(verify-visited-file-modtime buffer)' doesn't cut it
1982 ;; because it only looks at the time stamp (it ignores
1983 ;; read-write changes) which is not changed by `commit'.
1984 (buffer-modified-p buffer))
1985 (with-current-buffer buffer
a7996e05
SM
1986 (ignore-errors
1987 (revert-buffer 'ignore-auto 'dont-ask 'preserve-modes)
1988 ;; `preserve-modes' avoids changing the (minor) modes. But we
1989 ;; do want to reset the mode for VC, so we do it explicitly.
1990 (vc-find-file-hook)
1991 (when (eq (cvs-fileinfo->type fileinfo) 'CONFLICT)
1992 (smerge-mode 1))))))))
5b467bf4
SM
1993
1994\f
1995(defun cvs-change-cvsroot (newroot)
1996 "Change the cvsroot."
1997 (interactive "DNew repository: ")
1998 (if (or (file-directory-p (expand-file-name "CVSROOT" newroot))
1999 (y-or-n-p (concat "Warning: no CVSROOT found inside repository."
2000 " Change cvs-cvsroot anyhow?")))
2001 (setq cvs-cvsroot newroot)))
2002
2003;;;;
2004;;;; useful global settings
2005;;;;
2006
2007;;;###autoload
2008(add-to-list 'completion-ignored-extensions "CVS/")
2009
2010;;
2011;; Hook to allow calling PCL-CVS by visiting the /CVS subdirectory
2012;;
2013
6dc7d3d5 2014;;;###autoload
eed914af 2015(defcustom cvs-dired-action 'cvs-quickdir
6dc7d3d5
SM
2016 "The action to be performed when opening a CVS directory.
2017Sensible values are `cvs-examine', `cvs-status' and `cvs-quickdir'."
2018 :group 'pcl-cvs
2019 :type '(choice (const cvs-examine) (const cvs-status) (const cvs-quickdir)))
2020
5b467bf4
SM
2021;;;###autoload
2022(defcustom cvs-dired-use-hook '(4)
2023 "Whether or not opening a CVS directory should run PCL-CVS.
2024NIL means never do it.
2025ALWAYS means to always do it unless a prefix argument is given to the
2026 command that prompted the opening of the directory.
2027Anything else means to do it only if the prefix arg is equal to this value."
2028 :group 'pcl-cvs
2029 :type '(choice (const :tag "Never" nil)
2030 (const :tag "Always" always)
2031 (const :tag "Prefix" (4))))
2032
2033;;;###autoload
b15b5618 2034(progn (defun cvs-dired-noselect (dir)
5b467bf4
SM
2035 "Run `cvs-examine' if DIR is a CVS administrative directory.
2036The exact behavior is determined also by `cvs-dired-use-hook'."
2037 (when (stringp dir)
2038 (setq dir (directory-file-name dir))
2039 (when (and (string= "CVS" (file-name-nondirectory dir))
2040 (file-readable-p (expand-file-name "Entries" dir))
2041 cvs-dired-use-hook
2042 (if (eq cvs-dired-use-hook 'always)
2043 (not current-prefix-arg)
2044 (equal current-prefix-arg cvs-dired-use-hook)))
2045 (save-excursion
6dc7d3d5 2046 (funcall cvs-dired-action (file-name-directory dir) t t))))))
5b467bf4
SM
2047
2048;;
2049;; hook into VC
2050;;
2051
d15c2aaa
SM
2052(add-hook 'vc-post-command-functions 'cvs-vc-command-advice)
2053
2054(defun cvs-vc-command-advice (command file flags)
2055 (when (and (equal command "cvs")
5b467bf4 2056 ;; don't parse output we don't understand.
d15c2aaa 2057 (member (car flags) cvs-parse-known-commands))
5b467bf4 2058 (save-excursion
d15c2aaa
SM
2059 (let ((buffer (current-buffer))
2060 (dir default-directory)
5b467bf4
SM
2061 (cvs-from-vc t))
2062 (dolist (cvs-buf (buffer-list))
2063 (set-buffer cvs-buf)
2064 ;; look for a corresponding pcl-cvs buffer
2065 (when (and (eq major-mode 'cvs-mode)
2066 (cvs-string-prefix-p default-directory dir))
2067 (let ((subdir (substring dir (length default-directory))))
2068 (set-buffer buffer)
2069 (set (make-local-variable 'cvs-buffer) cvs-buf)
2070 ;; VC never (?) does `cvs -n update' so dcd=nil
2071 ;; should probably always be the right choice.
2072 (cvs-parse-process nil subdir))))))))
2073
2074;;
2075;; Hook into write-buffer
2076;;
2077
2078(defun cvs-mark-buffer-changed ()
2079 (let* ((file (expand-file-name buffer-file-name))
2080 (version (and (fboundp 'vc-backend)
2081 (eq (vc-backend file) 'CVS)
2082 (vc-workfile-version file))))
2083 (when version
2084 (save-excursion
2085 (dolist (cvs-buf (buffer-list))
2086 (set-buffer cvs-buf)
2087 ;; look for a corresponding pcl-cvs buffer
2088 (when (and (eq major-mode 'cvs-mode)
2089 (cvs-string-prefix-p default-directory file))
2090 (let* ((file (substring file (length default-directory)))
2091 (fi (cvs-create-fileinfo
2092 (if (string= "0" version)
2093 'ADDED 'MODIFIED)
2094 (or (file-name-directory file) "")
2095 (file-name-nondirectory file)
2096 "cvs-mark-buffer-changed")))
2097 (cvs-addto-collection cvs-cookies fi))))))))
2098
2099(add-hook 'after-save-hook 'cvs-mark-buffer-changed)
2100
2101;;
2102;; hook into uniquify
2103;;
2104
2105(defadvice uniquify-buffer-file-name (after pcl-cvs-uniquify activate)
2106 (or ad-return-value
2107 (save-excursion
2108 (set-buffer (ad-get-arg 0))
2109 (when (eq major-mode 'cvs-mode)
2110 (setq ad-return-value list-buffers-directory)))))
2111
2112\f
2113(provide 'pcvs)
2114
2115;;; pcvs.el ends here