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