Sync to HEAD
[bpt/emacs.git] / lisp / vc-cvs.el
CommitLineData
c1b25099
GM
1;;; vc-cvs.el --- non-resident support for CVS version-control
2
e54faddb 3;; Copyright (C) 1995,98,99,2000,2001,02,2003 Free Software Foundation, Inc.
c1b25099
GM
4
5;; Author: FSF (see vc.el for full credits)
6;; Maintainer: Andre Spiegel <spiegel@gnu.org>
7
6b61353c 8;; $Id: vc-cvs.el,v 1.67 2004/01/20 17:41:18 uid65624 Exp $
c1b25099
GM
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;;; Code:
30
ed5d446e 31(eval-when-compile
54be33bc 32 (require 'vc))
89e65817 33
9b0630e5 34;;;
8f98485f
AS
35;;; Customization options
36;;;
37
bbce0417
AS
38(defcustom vc-cvs-global-switches nil
39 "*Global switches to pass to any CVS command."
40 :type '(choice (const :tag "None" nil)
41 (string :tag "Argument String")
42 (repeat :tag "Argument List"
43 :value ("")
44 string))
89e7ad59 45 :version "21.4"
bbce0417
AS
46 :group 'vc)
47
c1b25099
GM
48(defcustom vc-cvs-register-switches nil
49 "*Extra switches for registering a file into CVS.
50A string or list of strings passed to the checkin program by
51\\[vc-register]."
52 :type '(choice (const :tag "None" nil)
53 (string :tag "Argument String")
54 (repeat :tag "Argument List"
55 :value ("")
56 string))
0d685c4f 57 :version "21.1"
c1b25099
GM
58 :group 'vc)
59
633147f5
AS
60(defcustom vc-cvs-diff-switches nil
61 "*A string or list of strings specifying extra switches for cvs diff under VC."
62 :type '(choice (const :tag "None" nil)
63 (string :tag "Argument String")
64 (repeat :tag "Argument List"
65 :value ("")
66 string))
67 :version "21.1"
68 :group 'vc)
69
c1b25099
GM
70(defcustom vc-cvs-header (or (cdr (assoc 'CVS vc-header-alist)) '("\$Id\$"))
71 "*Header keywords to be inserted by `vc-insert-headers'."
0d685c4f 72 :version "21.1"
ee3275df 73 :type '(repeat string)
c1b25099
GM
74 :group 'vc)
75
76(defcustom vc-cvs-use-edit t
77 "*Non-nil means to use `cvs edit' to \"check out\" a file.
78This is only meaningful if you don't use the implicit checkout model
79\(i.e. if you have $CVSREAD set)."
80 :type 'boolean
0d685c4f 81 :version "21.1"
c1b25099
GM
82 :group 'vc)
83
f354c160 84(defcustom vc-cvs-stay-local t
0d685c4f 85 "*Non-nil means use local operations when possible for remote repositories.
c6a84d17
SM
86This avoids slow queries over the network and instead uses heuristics
87and past information to determine the current status of a file.
f354c160 88
d3ed06c6
AS
89The value can also be a regular expression or list of regular
90expressions to match against the host name of a repository; then VC
f354c160
AS
91only stays local for hosts that match it. Alternatively, the value
92can be a list of regular expressions where the first element is the
93symbol `except'; then VC always stays local except for hosts matched
94by these regular expressions."
c1b25099 95 :type '(choice (const :tag "Always stay local" t)
d3ed06c6
AS
96 (const :tag "Don't stay local" nil)
97 (list :format "\nExamine hostname and %v" :tag "Examine hostname ..."
98 (set :format "%v" :inline t (const :format "%t" :tag "don't" except))
99 (regexp :format " stay local,\n%t: %v" :tag "if it matches")
100 (repeat :format "%v%i\n" :inline t (regexp :tag "or"))))
0d685c4f 101 :version "21.1"
c1b25099
GM
102 :group 'vc)
103
51af12fc
AS
104(defcustom vc-cvs-sticky-date-format-string "%c"
105 "*Format string for mode-line display of sticky date.
106Format is according to `format-time-string'. Only used if
107`vc-cvs-sticky-tag-display' is t."
108 :type '(string)
89e7ad59 109 :version "21.4"
51af12fc
AS
110 :group 'vc)
111
112(defcustom vc-cvs-sticky-tag-display t
113 "*Specify the mode-line display of sticky tags.
114Value t means default display, nil means no display at all. If the
115value is a function or macro, it is called with the sticky tag and
116its' type as parameters, in that order. TYPE can have three different
117values: `symbolic-name' (TAG is a string), `revision-number' (TAG is a
118string) and `date' (TAG is a date as returned by `encode-time'). The
119return value of the function or macro will be displayed as a string.
120
121Here's an example that will display the formatted date for sticky
122dates and the word \"Sticky\" for sticky tag names and revisions.
123
124 (lambda (tag type)
968b980c 125 (cond ((eq type 'date) (format-time-string
51af12fc
AS
126 vc-cvs-sticky-date-format-string tag))
127 ((eq type 'revision-number) \"Sticky\")
128 ((eq type 'symbolic-name) \"Sticky\")))
129
130Here's an example that will abbreviate to the first character only,
aa4af071 131any text before the first occurrence of `-' for sticky symbolic tags.
51af12fc
AS
132If the sticky tag is a revision number, the word \"Sticky\" is
133displayed. Date and time is displayed for sticky dates.
134
135 (lambda (tag type)
136 (cond ((eq type 'date) (format-time-string \"%Y%m%d %H:%M\" tag))
137 ((eq type 'revision-number) \"Sticky\")
968b980c 138 ((eq type 'symbolic-name)
51af12fc
AS
139 (condition-case nil
140 (progn
141 (string-match \"\\\\([^-]*\\\\)\\\\(.*\\\\)\" tag)
968b980c 142 (concat (substring (match-string 1 tag) 0 1) \":\"
51af12fc
AS
143 (substring (match-string 2 tag) 1 nil)))
144 (error tag))))) ; Fall-back to given tag name.
145
146See also variable `vc-cvs-sticky-date-format-string'."
147 :type '(choice boolean function)
89e7ad59 148 :version "21.4"
51af12fc 149 :group 'vc)
9b0630e5 150
8f98485f
AS
151;;;
152;;; Internal variables
153;;;
154
155(defvar vc-cvs-local-month-numbers
156 '(("Jan" . 1) ("Feb" . 2) ("Mar" . 3) ("Apr" . 4)
157 ("May" . 5) ("Jun" . 6) ("Jul" . 7) ("Aug" . 8)
158 ("Sep" . 9) ("Oct" . 10) ("Nov" . 11) ("Dec" . 12))
159 "Local association list of month numbers.")
160
9b0630e5 161
8f98485f 162;;;
9b0630e5 163;;; State-querying functions
8f98485f
AS
164;;;
165
c1b25099
GM
166;;;###autoload (defun vc-cvs-registered (f)
167;;;###autoload (when (file-readable-p (expand-file-name
168;;;###autoload "CVS/Entries" (file-name-directory f)))
cfb0dbdc 169;;;###autoload (load "vc-cvs")
c1b25099
GM
170;;;###autoload (vc-cvs-registered f)))
171
172(defun vc-cvs-registered (file)
173 "Check if FILE is CVS registered."
174 (let ((dirname (or (file-name-directory file) ""))
175 (basename (file-name-nondirectory file))
176 ;; make sure that the file name is searched case-sensitively
177 (case-fold-search nil))
178 (if (file-readable-p (expand-file-name "CVS/Entries" dirname))
179 (with-temp-buffer
666721a6 180 (vc-cvs-get-entries dirname)
c1b25099 181 (goto-char (point-min))
0d685c4f
DL
182 (cond
183 ((re-search-forward
839dacca
SM
184 ;; CVS-removed files are not taken under VC control.
185 (concat "^/" (regexp-quote basename) "/[^/-]") nil t)
c1b25099
GM
186 (beginning-of-line)
187 (vc-cvs-parse-entry file)
188 t)
189 (t nil)))
190 nil)))
191
c1b25099
GM
192(defun vc-cvs-state (file)
193 "CVS-specific version of `vc-state'."
e54faddb 194 (if (vc-stay-local-p file)
c1b25099
GM
195 (let ((state (vc-file-getprop file 'vc-state)))
196 ;; If we should stay local, use the heuristic but only if
197 ;; we don't have a more precise state already available.
198 (if (memq state '(up-to-date edited))
199 (vc-cvs-state-heuristic file)
200 state))
201 (with-temp-buffer
202 (cd (file-name-directory file))
bbce0417 203 (vc-cvs-command t 0 file "status")
c1b25099
GM
204 (vc-cvs-parse-status t))))
205
206(defun vc-cvs-state-heuristic (file)
207 "CVS-specific state heuristic."
208 ;; If the file has not changed since checkout, consider it `up-to-date'.
209 ;; Otherwise consider it `edited'.
210 (let ((checkout-time (vc-file-getprop file 'vc-checkout-time))
211 (lastmod (nth 5 (file-attributes file))))
212 (if (equal checkout-time lastmod)
213 'up-to-date
214 'edited)))
215
8f98485f
AS
216(defun vc-cvs-dir-state (dir)
217 "Find the CVS state of all files in DIR."
d3ee404f
SM
218 ;; if DIR is not under CVS control, don't do anything.
219 (when (file-readable-p (expand-file-name "CVS/Entries" dir))
e54faddb 220 (if (vc-stay-local-p dir)
d3ee404f
SM
221 (vc-cvs-dir-state-heuristic dir)
222 (let ((default-directory dir))
223 ;; Don't specify DIR in this command, the default-directory is
224 ;; enough. Otherwise it might fail with remote repositories.
225 (with-temp-buffer
226 (vc-cvs-command t 0 nil "status" "-l")
227 (goto-char (point-min))
228 (while (re-search-forward "^=+\n\\([^=\n].*\n\\|\n\\)+" nil t)
229 (narrow-to-region (match-beginning 0) (match-end 0))
230 (vc-cvs-parse-status)
231 (goto-char (point-max))
232 (widen)))))))
8f98485f
AS
233
234(defun vc-cvs-workfile-version (file)
235 "CVS-specific version of `vc-workfile-version'."
236 ;; There is no need to consult RCS headers under CVS, because we
237 ;; get the workfile version for free when we recognize that a file
238 ;; is registered in CVS.
239 (vc-cvs-registered file)
240 (vc-file-getprop file 'vc-workfile-version))
241
8f98485f
AS
242(defun vc-cvs-checkout-model (file)
243 "CVS-specific version of `vc-checkout-model'."
244 (if (or (getenv "CVSREAD")
245 ;; If the file is not writable (despite CVSREAD being
246 ;; undefined), this is probably because the file is being
247 ;; "watched" by other developers.
248 ;; (If vc-mistrust-permissions was t, we actually shouldn't
249 ;; trust this, but there is no other way to learn this from CVS
250 ;; at the moment (version 1.9).)
251 (string-match "r-..-..-." (nth 8 (file-attributes file))))
252 'announce
253 'implicit))
254
099bd78a
SM
255(defun vc-cvs-mode-line-string (file)
256 "Return string for placement into the modeline for FILE.
51af12fc
AS
257Compared to the default implementation, this function does two things:
258Handle the special case of a CVS file that is added but not yet
259committed and support display of sticky tags."
a0688443
SM
260 (let ((sticky-tag (vc-file-getprop file 'vc-cvs-sticky-tag))
261 (string (if (string= (vc-workfile-version file) "0")
262 ;; A file that is added but not yet committed.
263 "CVS @@"
264 (vc-default-mode-line-string 'CVS file))))
265 (if (zerop (length sticky-tag))
266 string
267 (concat string "[" sticky-tag "]"))))
099bd78a 268
8f98485f
AS
269(defun vc-cvs-dired-state-info (file)
270 "CVS-specific version of `vc-dired-state-info'."
78376474
SM
271 (let ((cvs-state (vc-state file)))
272 (cond ((eq cvs-state 'edited)
cc47c6c1 273 (if (equal (vc-workfile-version file) "0")
78376474
SM
274 "(added)" "(modified)"))
275 ((eq cvs-state 'needs-patch) "(patch)")
276 ((eq cvs-state 'needs-merge) "(merge)"))))
c1b25099 277
9b0630e5 278
8f98485f
AS
279;;;
280;;; State-changing functions
281;;;
c1b25099 282
8f98485f
AS
283(defun vc-cvs-register (file &optional rev comment)
284 "Register FILE into the CVS version-control system.
285COMMENT can be used to provide an initial description of FILE.
c1b25099 286
8f98485f
AS
287`vc-register-switches' and `vc-cvs-register-switches' are passed to
288the CVS command (in that order)."
e54faddb
SM
289 (when (and (not (vc-cvs-responsible-p file))
290 (vc-cvs-could-register file))
291 ;; Register the directory if needed.
292 (vc-cvs-register (directory-file-name (file-name-directory file))))
f153cb52
SM
293 (apply 'vc-cvs-command nil 0 file
294 "add"
295 (and comment (string-match "[^\t\n ]" comment)
296 (concat "-m" comment))
297 (vc-switches 'CVS 'register)))
c1b25099 298
8f98485f
AS
299(defun vc-cvs-responsible-p (file)
300 "Return non-nil if CVS thinks it is responsible for FILE."
301 (file-directory-p (expand-file-name "CVS"
302 (if (file-directory-p file)
303 file
304 (file-name-directory file)))))
c1b25099 305
e54faddb 306(defun vc-cvs-could-register (file)
8f98485f 307 "Return non-nil if FILE could be registered in CVS.
e54faddb
SM
308This is only possible if CVS is managing FILE's directory or one of
309its parents."
310 (let ((dir file))
311 (while (and (stringp dir)
312 (not (equal dir (setq dir (file-name-directory dir))))
313 dir)
314 (setq dir (if (file-directory-p
315 (expand-file-name "CVS/Entries" dir))
316 t (directory-file-name dir))))
317 (eq dir t)))
c1b25099
GM
318
319(defun vc-cvs-checkin (file rev comment)
320 "CVS-specific version of `vc-backend-checkin'."
f153cb52
SM
321 (unless (or (not rev) (vc-cvs-valid-version-number-p rev))
322 (if (not (vc-cvs-valid-symbolic-tag-name-p rev))
323 (error "%s is not a valid symbolic tag name" rev)
324 ;; If the input revison is a valid symbolic tag name, we create it
325 ;; as a branch, commit and switch to it.
326 (apply 'vc-cvs-command nil 0 file "tag" "-b" (list rev))
327 (apply 'vc-cvs-command nil 0 file "update" "-r" (list rev))
328 (vc-file-setprop file 'vc-cvs-sticky-tag rev)))
329 (let ((status (apply 'vc-cvs-command nil 1 file
330 "ci" (if rev (concat "-r" rev))
331 (concat "-m" comment)
332 (vc-switches 'CVS 'checkin))))
c1b25099
GM
333 (set-buffer "*vc*")
334 (goto-char (point-min))
bee48f25
AS
335 (when (not (zerop status))
336 ;; Check checkin problem.
337 (cond
338 ((re-search-forward "Up-to-date check failed" nil t)
339 (vc-file-setprop file 'vc-state 'needs-merge)
340 (error (substitute-command-keys
341 (concat "Up-to-date check failed: "
342 "type \\[vc-next-action] to merge in changes"))))
343 (t
344 (pop-to-buffer (current-buffer))
345 (goto-char (point-min))
346 (shrink-window-if-larger-than-buffer)
347 (error "Check-in failed"))))
c1b25099
GM
348 ;; Update file properties
349 (vc-file-setprop
350 file 'vc-workfile-version
351 (vc-parse-buffer "^\\(new\\|initial\\) revision: \\([0-9.]+\\)" 2))
352 ;; Forget the checkout model of the file, because we might have
353 ;; guessed wrong when we found the file. After commit, we can
354 ;; tell it from the permissions of the file (see
355 ;; vc-cvs-checkout-model).
356 (vc-file-setprop file 'vc-checkout-model nil)
51af12fc
AS
357
358 ;; if this was an explicit check-in (does not include creation of
359 ;; a branch), remove the sticky tag.
360 (if (and rev (not (vc-cvs-valid-symbolic-tag-name-p rev)))
bbce0417 361 (vc-cvs-command nil 0 file "update" "-A"))))
c1b25099 362
ea05db01
SM
363(defun vc-cvs-find-version (file rev buffer)
364 (apply 'vc-cvs-command
365 buffer 0 file
366 "-Q" ; suppress diagnostic output
367 "update"
368 (and rev (not (string= rev ""))
369 (concat "-r" rev))
370 "-p"
f153cb52 371 (vc-switches 'CVS 'checkout)))
ea05db01 372
34abd98e 373(defun vc-cvs-checkout (file &optional editable rev workfile)
c1b25099 374 "Retrieve a revision of FILE into a WORKFILE.
34abd98e 375EDITABLE non-nil means that the file should be writable.
c1b25099
GM
376REV is the revision to check out into WORKFILE."
377 (let ((filename (or workfile file))
378 (file-buffer (get-file-buffer file))
379 switches)
380 (message "Checking out %s..." filename)
381 (save-excursion
382 ;; Change buffers to get local value of vc-checkout-switches.
383 (if file-buffer (set-buffer file-buffer))
f153cb52 384 (setq switches (vc-switches 'CVS 'checkout))
c1b25099
GM
385 ;; Save this buffer's default-directory
386 ;; and use save-excursion to make sure it is restored
387 ;; in the same buffer it was saved in.
388 (let ((default-directory default-directory))
389 (save-excursion
390 ;; Adjust the default-directory so that the check-out creates
391 ;; the file in the right place.
392 (setq default-directory (file-name-directory filename))
393 (if workfile
9b0630e5 394 (let ((failed t)
81d995bb 395 (backup-name (if (string= file workfile)
a8b6979f
AS
396 (car (find-backup-file-name filename)))))
397 (when backup-name
9b0630e5 398 (copy-file filename backup-name
a8b6979f
AS
399 'ok-if-already-exists 'keep-date)
400 (unless (file-writable-p filename)
401 (set-file-modes filename
402 (logior (file-modes filename) 128))))
c1b25099
GM
403 (unwind-protect
404 (progn
405 (let ((coding-system-for-read 'no-conversion)
406 (coding-system-for-write 'no-conversion))
407 (with-temp-file filename
bbce0417
AS
408 (apply 'vc-cvs-command
409 (current-buffer) 0 file
c1b25099 410 "-Q" ; suppress diagnostic output
044504d2 411 "update"
83220ab0
AS
412 (and (stringp rev)
413 (not (string= rev ""))
c1b25099
GM
414 (concat "-r" rev))
415 "-p"
416 switches)))
417 (setq failed nil))
9b0630e5 418 (if failed
81d995bb 419 (if backup-name
9b0630e5 420 (rename-file backup-name filename
81d995bb
AS
421 'ok-if-already-exists)
422 (if (file-exists-p filename)
423 (delete-file filename)))
a8b6979f
AS
424 (and backup-name
425 (not vc-make-backup-files)
426 (delete-file backup-name)))))
c1b25099
GM
427 (if (and (file-exists-p file) (not rev))
428 ;; If no revision was specified, just make the file writable
429 ;; if necessary (using `cvs-edit' if requested).
f153cb52 430 (and editable (not (eq (vc-cvs-checkout-model file) 'implicit))
83220ab0
AS
431 (if vc-cvs-use-edit
432 (vc-cvs-command nil 0 file "edit")
433 (set-file-modes file (logior (file-modes file) 128))
434 (if file-buffer (toggle-read-only -1))))
435 ;; Check out a particular version (or recreate the file).
436 (vc-file-setprop file 'vc-workfile-version nil)
437 (apply 'vc-cvs-command nil 0 file
438 (and editable
439 (or (not (file-exists-p file))
440 (not (eq (vc-cvs-checkout-model file)
441 'implicit)))
442 "-w")
443 "update"
63274db1
AS
444 (when rev
445 (unless (eq rev t)
446 ;; default for verbose checkout: clear the
447 ;; sticky tag so that the actual update will
448 ;; get the head of the trunk
449 (if (string= rev "")
450 "-A"
451 (concat "-r" rev))))
83220ab0 452 switches))))
c1b25099
GM
453 (vc-mode-line file)
454 (message "Checking out %s...done" filename)))))
455
a0688443
SM
456(defun vc-cvs-delete-file (file)
457 (vc-cvs-command nil 0 file "remove" "-f"))
458
99739bbf 459(defun vc-cvs-revert (file &optional contents-done)
8f98485f 460 "Revert FILE to the version it was based on."
99739bbf
AS
461 (unless contents-done
462 ;; Check out via standard output (caused by the final argument
463 ;; FILE below), so that no sticky tag is set.
f153cb52 464 (vc-cvs-checkout file nil (vc-workfile-version file) file))
99739bbf
AS
465 (unless (eq (vc-checkout-model file) 'implicit)
466 (if vc-cvs-use-edit
bbce0417 467 (vc-cvs-command nil 0 file "unedit")
99739bbf
AS
468 ;; Make the file read-only by switching off all w-bits
469 (set-file-modes file (logand (file-modes file) 3950)))))
8f98485f
AS
470
471(defun vc-cvs-merge (file first-version &optional second-version)
472 "Merge changes into current working copy of FILE.
473The changes are between FIRST-VERSION and SECOND-VERSION."
bbce0417 474 (vc-cvs-command nil 0 file
8f98485f
AS
475 "update" "-kk"
476 (concat "-j" first-version)
477 (concat "-j" second-version))
478 (vc-file-setprop file 'vc-state 'edited)
d3ee404f 479 (with-current-buffer (get-buffer "*vc*")
8f98485f
AS
480 (goto-char (point-min))
481 (if (re-search-forward "conflicts during merge" nil t)
482 1 ; signal error
483 0))) ; signal success
484
485(defun vc-cvs-merge-news (file)
486 "Merge in any new changes made to FILE."
487 (message "Merging changes into %s..." file)
d3ee404f
SM
488 ;; (vc-file-setprop file 'vc-workfile-version nil)
489 (vc-file-setprop file 'vc-checkout-time 0)
490 (vc-cvs-command nil 0 file "update")
491 ;; Analyze the merge result reported by CVS, and set
492 ;; file properties accordingly.
493 (with-current-buffer (get-buffer "*vc*")
8f98485f
AS
494 (goto-char (point-min))
495 ;; get new workfile version
d3ee404f
SM
496 (if (re-search-forward
497 "^Merging differences between [0-9.]* and \\([0-9.]*\\) into" nil t)
8f98485f
AS
498 (vc-file-setprop file 'vc-workfile-version (match-string 1))
499 (vc-file-setprop file 'vc-workfile-version nil))
500 ;; get file status
501 (prog1
502 (if (eq (buffer-size) 0)
503 0 ;; there were no news; indicate success
504 (if (re-search-forward
505 (concat "^\\([CMUP] \\)?"
506 (regexp-quote (file-name-nondirectory file))
507 "\\( already contains the differences between \\)?")
508 nil t)
509 (cond
510 ;; Merge successful, we are in sync with repository now
511 ((or (match-string 2)
512 (string= (match-string 1) "U ")
513 (string= (match-string 1) "P "))
514 (vc-file-setprop file 'vc-state 'up-to-date)
515 (vc-file-setprop file 'vc-checkout-time
516 (nth 5 (file-attributes file)))
517 0);; indicate success to the caller
518 ;; Merge successful, but our own changes are still in the file
519 ((string= (match-string 1) "M ")
520 (vc-file-setprop file 'vc-state 'edited)
521 0);; indicate success to the caller
522 ;; Conflicts detected!
523 (t
524 (vc-file-setprop file 'vc-state 'edited)
525 1);; signal the error to the caller
526 )
527 (pop-to-buffer "*vc*")
528 (error "Couldn't analyze cvs update result")))
529 (message "Merging changes into %s...done" file))))
530
9b0630e5 531
8f98485f
AS
532;;;
533;;; History functions
534;;;
535
6b61353c 536(defun vc-cvs-print-log (file &optional buffer)
8f98485f 537 "Get change log associated with FILE."
bbce0417 538 (vc-cvs-command
6b61353c 539 buffer
e54faddb 540 (if (and (vc-stay-local-p file) (fboundp 'start-process)) 'async 0)
bbce0417 541 file "log"))
8f98485f 542
6b61353c 543(defun vc-cvs-diff (file &optional oldvers newvers buffer)
8f98485f 544 "Get a difference report using CVS between two versions of FILE."
f153cb52
SM
545 (if (string= (vc-workfile-version file) "0")
546 ;; This file is added but not yet committed; there is no master file.
547 (if (or oldvers newvers)
548 (error "No revisions of %s exist" file)
549 ;; We regard this as "changed".
550 ;; Diff it against /dev/null.
551 ;; Note: this is NOT a "cvs diff".
6b61353c 552 (apply 'vc-do-command (or buffer "*vc-diff*")
f153cb52
SM
553 1 "diff" file
554 (append (vc-switches nil 'diff) '("/dev/null")))
555 ;; Even if it's empty, it's locally modified.
556 1)
e54faddb 557 (let* ((async (and (vc-stay-local-p file) (fboundp 'start-process)))
6b61353c 558 (status (apply 'vc-cvs-command (or buffer "*vc-diff*")
f153cb52
SM
559 (if async 'async 1)
560 file "diff"
561 (and oldvers (concat "-r" oldvers))
562 (and newvers (concat "-r" newvers))
563 (vc-switches 'CVS 'diff))))
564 (if async 1 status)))) ; async diff, pessimistic assumption
8f98485f 565
5e0fdc5a
AS
566(defun vc-cvs-diff-tree (dir &optional rev1 rev2)
567 "Diff all files at and below DIR."
568 (with-current-buffer "*vc-diff*"
569 (setq default-directory dir)
e54faddb 570 (if (vc-stay-local-p dir)
5e0fdc5a
AS
571 ;; local diff: do it filewise, and only for files that are modified
572 (vc-file-tree-walk
573 dir
574 (lambda (f)
575 (vc-exec-after
576 `(let ((coding-system-for-read (vc-coding-system-for-diff ',f)))
577 ;; possible optimization: fetch the state of all files
578 ;; in the tree via vc-cvs-dir-state-heuristic
579 (unless (vc-up-to-date-p ',f)
580 (message "Looking at %s" ',f)
044504d2 581 (vc-diff-internal ',f ',rev1 ',rev2))))))
5e0fdc5a
AS
582 ;; cvs diff: use a single call for the entire tree
583 (let ((coding-system-for-read
584 (or coding-system-for-read 'undecided)))
bbce0417 585 (apply 'vc-cvs-command "*vc-diff*" 1 nil "diff"
044504d2
AS
586 (and rev1 (concat "-r" rev1))
587 (and rev2 (concat "-r" rev2))
f153cb52 588 (vc-switches 'CVS 'diff))))))
5e0fdc5a 589
8f98485f
AS
590(defun vc-cvs-annotate-command (file buffer &optional version)
591 "Execute \"cvs annotate\" on FILE, inserting the contents in BUFFER.
592Optional arg VERSION is a version to annotate from."
f153cb52 593 (vc-cvs-command buffer 0 file "annotate" (if version (concat "-r" version))))
c1b25099 594
8ba2df32
AS
595(defun vc-cvs-annotate-current-time ()
596 "Return the current time, based at midnight of the current day, and
597encoded as fractional days."
598 (vc-annotate-convert-time
599 (apply 'encode-time 0 0 0 (nthcdr 3 (decode-time (current-time))))))
600
601(defun vc-cvs-annotate-time ()
602 "Return the time of the next annotation (as fraction of days)
f0529b5b 603systime, or nil if there is none."
968b980c 604 (let ((time-stamp
8ba2df32
AS
605 "^\\S-+\\s-+\\S-+\\s-+\\([0-9]+\\)-\\(\\sw+\\)-\\([0-9]+\\)): "))
606 (if (looking-at time-stamp)
c1b25099
GM
607 (progn
608 (let* ((day (string-to-number (match-string 1)))
968b980c 609 (month (cdr (assoc (match-string 2)
8ba2df32 610 vc-cvs-local-month-numbers)))
c1b25099
GM
611 (year-tmp (string-to-number (match-string 3)))
612 ;; Years 0..68 are 2000..2068.
613 ;; Years 69..99 are 1969..1999.
614 (year (+ (cond ((> 69 year-tmp) 2000)
615 ((> 100 year-tmp) 1900)
616 (t 0))
617 year-tmp)))
618 (goto-char (match-end 0)) ; Position at end makes for nicer overlay result
8ba2df32 619 (vc-annotate-convert-time (encode-time 0 0 0 day month year))))
c1b25099
GM
620 ;; If we did not look directly at an annotation, there might be
621 ;; some further down. This is the case if we are positioned at
622 ;; the very top of the buffer, for instance.
8ba2df32 623 (if (re-search-forward time-stamp nil t)
c1b25099
GM
624 (progn
625 (beginning-of-line nil)
8ba2df32 626 (vc-cvs-annotate-time))))))
9b0630e5 627
6b61353c
KH
628(defun vc-cvs-annotate-extract-revision-at-line ()
629 (save-excursion
630 (beginning-of-line)
631 (if (re-search-forward "^\\([0-9]+\\.[0-9]+\\(\\.[0-9]+\\)*\\) +("
632 (line-end-position) t)
633 (match-string-no-properties 1)
634 nil)))
635
8f98485f
AS
636;;;
637;;; Snapshot system
638;;;
639
640(defun vc-cvs-create-snapshot (dir name branchp)
641 "Assign to DIR's current version a given NAME.
642If BRANCHP is non-nil, the name is created as a branch (and the current
643workspace is immediately moved to that new branch)."
bbce0417
AS
644 (vc-cvs-command nil 0 dir "tag" "-c" (if branchp "-b") name)
645 (when branchp (vc-cvs-command nil 0 dir "update" "-r" name)))
8f98485f
AS
646
647(defun vc-cvs-retrieve-snapshot (dir name update)
648 "Retrieve a snapshot at and below DIR.
649NAME is the name of the snapshot; if it is empty, do a `cvs update'.
650If UPDATE is non-nil, then update (resynch) any affected buffers."
651 (with-current-buffer (get-buffer-create "*vc*")
51af12fc
AS
652 (let ((default-directory dir)
653 (sticky-tag))
8f98485f
AS
654 (erase-buffer)
655 (if (or (not name) (string= name ""))
bbce0417
AS
656 (vc-cvs-command t 0 nil "update")
657 (vc-cvs-command t 0 nil "update" "-r" name)
51af12fc 658 (setq sticky-tag name))
8f98485f
AS
659 (when update
660 (goto-char (point-min))
661 (while (not (eobp))
662 (if (looking-at "\\([CMUP]\\) \\(.*\\)")
663 (let* ((file (expand-file-name (match-string 2) dir))
664 (state (match-string 1))
665 (buffer (find-buffer-visiting file)))
666 (when buffer
667 (cond
668 ((or (string= state "U")
669 (string= state "P"))
670 (vc-file-setprop file 'vc-state 'up-to-date)
671 (vc-file-setprop file 'vc-workfile-version nil)
672 (vc-file-setprop file 'vc-checkout-time
673 (nth 5 (file-attributes file))))
674 ((or (string= state "M")
675 (string= state "C"))
676 (vc-file-setprop file 'vc-state 'edited)
677 (vc-file-setprop file 'vc-workfile-version nil)
678 (vc-file-setprop file 'vc-checkout-time 0)))
51af12fc 679 (vc-file-setprop file 'vc-cvs-sticky-tag sticky-tag)
8f98485f
AS
680 (vc-resynch-buffer file t t))))
681 (forward-line 1))))))
682
9b0630e5 683
8f98485f
AS
684;;;
685;;; Miscellaneous
686;;;
687
e54faddb 688(defalias 'vc-cvs-make-version-backups-p 'vc-stay-local-p
d3ee404f 689 "Return non-nil if version backups should be made for FILE.")
8f98485f
AS
690
691(defun vc-cvs-check-headers ()
692 "Check if the current file has any headers in it."
693 (save-excursion
694 (goto-char (point-min))
695 (re-search-forward "\\$[A-Za-z\300-\326\330-\366\370-\377]+\
696\\(: [\t -#%-\176\240-\377]*\\)?\\$" nil t)))
697
9b0630e5 698
8f98485f
AS
699;;;
700;;; Internal functions
701;;;
702
bbce0417
AS
703(defun vc-cvs-command (buffer okstatus file &rest flags)
704 "A wrapper around `vc-do-command' for use in vc-cvs.el.
705The difference to vc-do-command is that this function always invokes `cvs',
706and that it passes `vc-cvs-global-switches' to it before FLAGS."
707 (apply 'vc-do-command buffer okstatus "cvs" file
968b980c 708 (if (stringp vc-cvs-global-switches)
bbce0417
AS
709 (cons vc-cvs-global-switches flags)
710 (append vc-cvs-global-switches
711 flags))))
712
e54faddb
SM
713(defalias 'vc-cvs-stay-local-p 'vc-stay-local-p) ;Back-compatibility.
714
715(defun vc-cvs-repository-hostname (dirname)
716 "Hostname of the CVS server associated to workarea DIRNAME."
717 (let ((rootname (expand-file-name "CVS/Root" dirname)))
718 (when (file-readable-p rootname)
719 (with-temp-buffer
720 (let ((coding-system-for-read
721 (or file-name-coding-system
722 default-file-name-coding-system)))
723 (vc-insert-file rootname))
724 (goto-char (point-min))
725 (nth 2 (vc-cvs-parse-root
726 (buffer-substring (point)
727 (line-end-position))))))))
78376474
SM
728
729(defun vc-cvs-parse-root (root)
d3ed06c6
AS
730 "Split CVS ROOT specification string into a list of fields.
731A CVS root specification of the form
732 [:METHOD:][[USER@]HOSTNAME:]/path/to/repository
733is converted to a normalized record with the following structure:
734 \(METHOD USER HOSTNAME CVS-ROOT).
735The default METHOD for a CVS root of the form
736 /path/to/repository
737is `local'.
738The default METHOD for a CVS root of the form
739 [USER@]HOSTNAME:/path/to/repository
740is `ext'.
741For an empty string, nil is returned (illegal CVS root)."
742 ;; Split CVS root into colon separated fields (0-4).
743 ;; The `x:' makes sure, that leading colons are not lost;
744 ;; `HOST:/PATH' is then different from `:METHOD:/PATH'.
745 (let* ((root-list (cdr (split-string (concat "x:" root) ":")))
746 (len (length root-list))
747 ;; All syntactic varieties will get a proper METHOD.
748 (root-list
749 (cond
750 ((= len 0)
751 ;; Invalid CVS root
752 nil)
753 ((= len 1)
754 ;; Simple PATH => method `local'
755 (cons "local"
756 (cons nil root-list)))
757 ((= len 2)
758 ;; [USER@]HOST:PATH => method `ext'
759 (and (not (equal (car root-list) ""))
760 (cons "ext" root-list)))
761 ((= len 3)
762 ;; :METHOD:PATH
763 (cons (cadr root-list)
764 (cons nil (cddr root-list))))
765 (t
766 ;; :METHOD:[USER@]HOST:PATH
767 (cdr root-list)))))
768 (if root-list
769 (let ((method (car root-list))
770 (uhost (or (cadr root-list) ""))
771 (root (nth 2 root-list))
772 user host)
773 ;; Split USER@HOST
774 (if (string-match "\\(.*\\)@\\(.*\\)" uhost)
775 (setq user (match-string 1 uhost)
776 host (match-string 2 uhost))
777 (setq host uhost))
778 ;; Remove empty HOST
779 (and (equal host "")
780 (setq host))
781 ;; Fix windows style CVS root `:local:C:\\project\\cvs\\some\\dir'
782 (and host
783 (equal method "local")
784 (setq root (concat host ":" root) host))
785 ;; Normalize CVS root record
786 (list method user host root)))))
8f98485f
AS
787
788(defun vc-cvs-parse-status (&optional full)
789 "Parse output of \"cvs status\" command in the current buffer.
790Set file properties accordingly. Unless FULL is t, parse only
791essential information."
792 (let (file status)
793 (goto-char (point-min))
794 (if (re-search-forward "^File: " nil t)
795 (cond
796 ((looking-at "no file") nil)
797 ((re-search-forward "\\=\\([^ \t]+\\)" nil t)
798 (setq file (expand-file-name (match-string 1)))
799 (vc-file-setprop file 'vc-backend 'CVS)
800 (if (not (re-search-forward "\\=[ \t]+Status: \\(.*\\)" nil t))
801 (setq status "Unknown")
802 (setq status (match-string 1)))
803 (if (and full
804 (re-search-forward
44f663a4 805 "\\(RCS Version\\|RCS Revision\\|Repository revision\\):\
8f98485f
AS
806\[\t ]+\\([0-9.]+\\)"
807 nil t))
808 (vc-file-setprop file 'vc-latest-version (match-string 2)))
968b980c 809 (vc-file-setprop
44f663a4
AS
810 file 'vc-state
811 (cond
812 ((string-match "Up-to-date" status)
813 (vc-file-setprop file 'vc-checkout-time
814 (nth 5 (file-attributes file)))
815 'up-to-date)
816 ((string-match "Locally Modified" status) 'edited)
817 ((string-match "Needs Merge" status) 'needs-merge)
818 ((string-match "Needs \\(Checkout\\|Patch\\)" status) 'needs-patch)
819 (t 'edited))))))))
8f98485f
AS
820
821(defun vc-cvs-dir-state-heuristic (dir)
822 "Find the CVS state of all files in DIR, using only local information."
823 (with-temp-buffer
666721a6 824 (vc-cvs-get-entries dir)
8f98485f
AS
825 (goto-char (point-min))
826 (while (not (eobp))
839dacca
SM
827 ;; CVS-removed files are not taken under VC control.
828 (when (looking-at "/\\([^/]*\\)/[^/-]")
8f98485f
AS
829 (let ((file (expand-file-name (match-string 1) dir)))
830 (unless (vc-file-getprop file 'vc-state)
831 (vc-cvs-parse-entry file t))))
832 (forward-line 1))))
833
666721a6
AS
834(defun vc-cvs-get-entries (dir)
835 "Insert the CVS/Entries file from below DIR into the current buffer.
836This function ensures that the correct coding system is used for that,
837which may not be the one that is used for the files' contents.
838CVS/Entries should only be accessed through this function."
839 (let ((coding-system-for-read (or file-name-coding-system
840 default-file-name-coding-system)))
841 (vc-insert-file (expand-file-name "CVS/Entries" dir))))
842
51af12fc
AS
843(defun vc-cvs-valid-symbolic-tag-name-p (tag)
844 "Return non-nil if TAG is a valid symbolic tag name."
845 ;; According to the CVS manual, a valid symbolic tag must start with
846 ;; an uppercase or lowercase letter and can contain uppercase and
847 ;; lowercase letters, digits, `-', and `_'.
848 (and (string-match "^[a-zA-Z]" tag)
849 (not (string-match "[^a-z0-9A-Z-_]" tag))))
968b980c 850
93bcb353
SS
851(defun vc-cvs-valid-version-number-p (tag)
852 "Return non-nil if TAG is a valid version number."
853 (and (string-match "^[0-9]" tag)
854 (not (string-match "[^0-9.]" tag))))
51af12fc
AS
855
856(defun vc-cvs-parse-sticky-tag (match-type match-tag)
968b980c 857 "Parse and return the sticky tag as a string.
51af12fc
AS
858`match-data' is protected."
859 (let ((data (match-data))
860 (tag)
861 (type (cond ((string= match-type "D") 'date)
862 ((string= match-type "T")
863 (if (vc-cvs-valid-symbolic-tag-name-p match-tag)
864 'symbolic-name
865 'revision-number))
866 (t nil))))
867 (unwind-protect
868 (progn
968b980c 869 (cond
e6608c12 870 ;; Sticky Date tag. Convert to a proper date value (`encode-time')
51af12fc 871 ((eq type 'date)
968b980c
SS
872 (string-match
873 "\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)"
51af12fc
AS
874 match-tag)
875 (let* ((year-tmp (string-to-number (match-string 1 match-tag)))
876 (month (string-to-number (match-string 2 match-tag)))
877 (day (string-to-number (match-string 3 match-tag)))
878 (hour (string-to-number (match-string 4 match-tag)))
879 (min (string-to-number (match-string 5 match-tag)))
880 (sec (string-to-number (match-string 6 match-tag)))
881 ;; Years 0..68 are 2000..2068.
882 ;; Years 69..99 are 1969..1999.
883 (year (+ (cond ((> 69 year-tmp) 2000)
884 ((> 100 year-tmp) 1900)
885 (t 0))
886 year-tmp)))
887 (setq tag (encode-time sec min hour day month year))))
888 ;; Sticky Tag name or revision number
889 ((eq type 'symbolic-name) (setq tag match-tag))
890 ((eq type 'revision-number) (setq tag match-tag))
891 ;; Default is no sticky tag at all
892 (t nil))
893 (cond ((eq vc-cvs-sticky-tag-display nil) nil)
894 ((eq vc-cvs-sticky-tag-display t)
968b980c 895 (cond ((eq type 'date) (format-time-string
51af12fc
AS
896 vc-cvs-sticky-date-format-string
897 tag))
898 ((eq type 'symbolic-name) tag)
899 ((eq type 'revision-number) tag)
900 (t nil)))
968b980c 901 ((functionp vc-cvs-sticky-tag-display)
51af12fc
AS
902 (funcall vc-cvs-sticky-tag-display tag type))
903 (t nil)))
904
905 (set-match-data data))))
906
8f98485f
AS
907(defun vc-cvs-parse-entry (file &optional set-state)
908 "Parse a line from CVS/Entries.
909Compare modification time to that of the FILE, set file properties
910accordingly. However, `vc-state' is set only if optional arg SET-STATE
911is non-nil."
912 (cond
913 ;; entry for a "locally added" file (not yet committed)
914 ((looking-at "/[^/]+/0/")
915 (vc-file-setprop file 'vc-checkout-time 0)
916 (vc-file-setprop file 'vc-workfile-version "0")
917 (if set-state (vc-file-setprop file 'vc-state 'edited)))
918 ;; normal entry
919 ((looking-at
920 (concat "/[^/]+"
921 ;; revision
922 "/\\([^/]*\\)"
92788b3b
AS
923 ;; timestamp and optional conflict field
924 "/\\([^/]*\\)/"
51af12fc
AS
925 ;; options
926 "\\([^/]*\\)/"
927 ;; sticky tag
928 "\\(.\\|\\)" ;Sticky tag type (date or tag name, could be empty)
929 "\\(.*\\)")) ;Sticky tag
8f98485f 930 (vc-file-setprop file 'vc-workfile-version (match-string 1))
839dacca 931 (vc-file-setprop file 'vc-cvs-sticky-tag
6b61353c
KH
932 (vc-cvs-parse-sticky-tag (match-string 4)
933 (match-string 5)))
934 ;; Compare checkout time and modification time.
935 ;; This is intentionally different from the algorithm that CVS uses
936 ;; (which is based on textual comparison), because there can be problems
937 ;; generating a time string that looks exactly like the one from CVS.
938 (let ((mtime (nth 5 (file-attributes file))))
939 (require 'parse-time)
940 (let ((parsed-time
941 (parse-time-string (concat (match-string 2) " +0000"))))
942 (cond ((and (not (string-match "\\+" (match-string 2)))
943 (car parsed-time)
944 (equal mtime (apply 'encode-time parsed-time)))
945 (vc-file-setprop file 'vc-checkout-time mtime)
946 (if set-state (vc-file-setprop file 'vc-state 'up-to-date)))
947 (t
948 (vc-file-setprop file 'vc-checkout-time 0)
949 (if set-state (vc-file-setprop file 'vc-state 'edited)))))))))
968b980c 950
c1b25099
GM
951(provide 'vc-cvs)
952
6b61353c 953;;; arch-tag: 60e1402a-aa53-4607-927a-cf74f144b432
c1b25099 954;;; vc-cvs.el ends here