(vc-fetch-master-properties): For RCS file,
[bpt/emacs.git] / lisp / vc-hooks.el
... / ...
CommitLineData
1;;; vc-hooks.el --- resident support for version-control
2
3;; Copyright (C) 1992, 1993, 1994, 1995 Free Software Foundation, Inc.
4
5;; Author: Eric S. Raymond <esr@snark.thyrsus.com>
6;; Modified by:
7;; Per Cederqvist <ceder@lysator.liu.se>
8;; Andre Spiegel <spiegel@berlin.informatik.uni-stuttgart.de>
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
24;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
25
26;;; Commentary:
27
28;; This is the always-loaded portion of VC.
29;; It takes care VC-related activities that are done when you visit a file,
30;; so that vc.el itself is loaded only when you use a VC command.
31;; See the commentary of vc.el.
32
33;;; Code:
34
35;; Customization Variables (the rest is in vc.el)
36
37(defvar vc-default-back-end nil
38 "*Back-end actually used by this interface; may be SCCS or RCS.
39The value is only computed when needed to avoid an expensive search.")
40
41(defvar vc-path
42 (if (file-directory-p "/usr/sccs")
43 '("/usr/sccs")
44 nil)
45 "*List of extra directories to search for version control commands.")
46
47(defvar vc-master-templates
48 '(("%sRCS/%s,v" . RCS) ("%s%s,v" . RCS) ("%sRCS/%s" . RCS)
49 ("%sSCCS/s.%s" . SCCS) ("%ss.%s". SCCS)
50 vc-find-cvs-master)
51 "*Where to look for version-control master files.
52The first pair corresponding to a given back end is used as a template
53when creating new masters.")
54
55(defvar vc-make-backup-files nil
56 "*If non-nil, backups of registered files are made as with other files.
57If nil (the default), files covered by version control don't get backups.")
58
59(defvar vc-display-status t
60 "*If non-nil, display revision number and lock status in modeline.
61Otherwise, not displayed.")
62
63(defvar vc-consult-headers t
64 "*Identify work files by searching for version headers.")
65
66(defvar vc-mistrust-permissions nil
67 "*Don't assume that permissions and ownership track version-control status.")
68
69(defvar vc-keep-workfiles t
70 "*If non-nil, don't delete working files after registering changes.
71If the back-end is CVS, workfiles are always kept, regardless of the
72value of this flag.")
73
74;; Tell Emacs about this new kind of minor mode
75(if (not (assoc 'vc-mode minor-mode-alist))
76 (setq minor-mode-alist (cons '(vc-mode vc-mode)
77 minor-mode-alist)))
78
79(make-variable-buffer-local 'vc-mode)
80(put 'vc-mode 'permanent-local t)
81
82;; We need a notion of per-file properties because the version
83;; control state of a file is expensive to derive --- we compute
84;; them when the file is initially found, keep them up to date
85;; during any subsequent VC operations, and forget them when
86;; the buffer is killed.
87
88(defmacro vc-error-occurred (&rest body)
89 (list 'condition-case nil (cons 'progn (append body '(nil))) '(error t)))
90
91(defvar vc-file-prop-obarray [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
92 "Obarray for per-file properties.")
93
94(defvar vc-buffer-backend t)
95(make-variable-buffer-local 'vc-buffer-backend)
96
97(defun vc-file-setprop (file property value)
98 ;; set per-file property
99 (put (intern file vc-file-prop-obarray) property value))
100
101(defun vc-file-getprop (file property)
102 ;; get per-file property
103 (get (intern file vc-file-prop-obarray) property))
104
105(defun vc-file-clearprops (file)
106 ;; clear all properties of a given file
107 (setplist (intern file vc-file-prop-obarray) nil))
108
109;;; Functions that determine property values, by examining the
110;;; working file, the master file, or log program output
111
112(defun vc-match-substring (bn)
113 (buffer-substring (match-beginning bn) (match-end bn)))
114
115(defun vc-lock-file (file)
116 ;; Generate lock file name corresponding to FILE
117 (let ((master (vc-name file)))
118 (and
119 master
120 (string-match "\\(.*/\\)s\\.\\(.*\\)" master)
121 (concat
122 (substring master (match-beginning 1) (match-end 1))
123 "p."
124 (substring master (match-beginning 2) (match-end 2))))))
125
126(defun vc-parse-buffer (patterns &optional file properties)
127 ;; Use PATTERNS to parse information out of the current buffer.
128 ;; Each element of PATTERNS is a list of 2 to 3 elements. The first element
129 ;; is the pattern to be matched, and the second (an integer) is the
130 ;; number of the subexpression that should be returned. If there's
131 ;; a third element (also the number of a subexpression), that
132 ;; subexpression is assumed to be a date field and we want the most
133 ;; recent entry matching the template.
134 ;; If FILE and PROPERTIES are given, the latter must be a list of
135 ;; properties of the same length as PATTERNS; each property is assigned
136 ;; the corresponding value.
137 (mapcar (function (lambda (p)
138 (goto-char (point-min))
139 (cond
140 ((eq (length p) 2) ;; search for first entry
141 (let ((value nil))
142 (if (re-search-forward (car p) nil t)
143 (setq value (vc-match-substring (elt p 1))))
144 (if file
145 (progn (vc-file-setprop file (car properties) value)
146 (setq properties (cdr properties))))
147 value))
148 ((eq (length p) 3) ;; search for latest entry
149 (let ((latest-date "") (latest-val))
150 (while (re-search-forward (car p) nil t)
151 (let ((date (vc-match-substring (elt p 2))))
152 (if (string< latest-date date)
153 (progn
154 (setq latest-date date)
155 (setq latest-val
156 (vc-match-substring (elt p 1)))))))
157 (if file
158 (progn (vc-file-setprop file (car properties) latest-val)
159 (setq properties (cdr properties))))
160 latest-val)))))
161 patterns)
162 )
163
164(defun vc-insert-file (file &optional limit blocksize)
165 ;; Insert the contents of FILE into the current buffer.
166 ;; Optional argument LIMIT is a regexp. If present,
167 ;; the file is inserted in chunks of size BLOCKSIZE
168 ;; (default 8 kByte), until the first occurence of
169 ;; LIMIT is found. The function returns nil if FILE
170 ;; doesn't exist.
171 (cond ((file-exists-p file)
172 (cond (limit
173 (if (not blocksize) (setq blocksize 8192))
174 (let (found s)
175 (while (not found)
176 (setq s (buffer-size))
177 (goto-char (1+ s))
178 (setq found
179 (or (zerop (car (cdr
180 (insert-file-contents file nil s
181 (+ s blocksize)))))
182 (progn (beginning-of-line)
183 (re-search-forward limit nil t)))))))
184 (t (insert-file-contents file)))
185 (set-buffer-modified-p nil)
186 (auto-save-mode nil)
187 t)
188 (t nil)))
189
190(defun vc-parse-locks (file locks)
191 ;; Parse RCS or SCCS locks.
192 ;; The result is a list of the form ((VERSION USER) (VERSION USER) ...),
193 ;; which is returned and stored into the property `vc-master-locks'.
194 (if (not locks)
195 (vc-file-setprop file 'vc-master-locks 'none)
196 (let ((found t) (index 0) master-locks version user)
197 (cond ((eq (vc-backend file) 'SCCS)
198 (while (string-match "^\\([0-9.]+\\) [0-9.]+ \\([^ ]+\\) .*\n?"
199 locks index)
200 (setq version (substring locks
201 (match-beginning 1) (match-end 1)))
202 (setq user (substring locks
203 (match-beginning 2) (match-end 2)))
204 (setq master-locks (append master-locks
205 (list (cons version user))))
206 (setq index (match-end 0))))
207 ((eq (vc-backend file) 'RCS)
208 (while (string-match "[ \t\n]*\\([^:]+\\):\\([0-9.]+\\)"
209 locks index)
210 (setq version (substring locks
211 (match-beginning 2) (match-end 2)))
212 (setq user (substring locks
213 (match-beginning 1) (match-end 1)))
214 (setq master-locks (append master-locks
215 (list (cons version user))))
216 (setq index (match-end 0)))))
217 (vc-file-setprop file 'vc-master-locks (or master-locks 'none)))))
218
219(defun vc-fetch-master-properties (file)
220 ;; Fetch those properties of FILE that are stored in the master file.
221 ;; For an RCS file, we don't get vc-latest-version vc-your-latest-version
222 ;; here because that is slow.
223 ;; That gets done if/when the functions vc-latest-version
224 ;; and vc-your-latest-version get called.
225 (save-excursion
226 (cond
227 ((eq (vc-backend file) 'SCCS)
228 (set-buffer (get-buffer-create "*vc-info*"))
229 (if (vc-insert-file (vc-lock-file file))
230 (progn (vc-parse-locks file (buffer-string))
231 (erase-buffer))
232 (vc-file-setprop file 'vc-master-locks 'none))
233 (vc-insert-file (vc-name file) "^\001e")
234 (vc-parse-buffer
235 (list '("^\001d D \\([^ ]+\\)" 1)
236 (list (concat "^\001d D \\([^ ]+\\) .* "
237 (regexp-quote (user-login-name)) " ") 1))
238 file
239 '(vc-latest-version vc-your-latest-version)))
240
241 ((eq (vc-backend file) 'RCS)
242 (set-buffer (get-buffer-create "*vc-info*"))
243 (vc-insert-file (vc-name file) "^locks")
244 (vc-parse-buffer
245 (list '("^head[ \t\n]+\\([^;]+\\);" 1)
246 '("^branch[ \t\n]+\\([^;]+\\);" 1)
247 '("^locks\\([^;]+\\);" 1))
248 file
249 '(vc-head-version
250 vc-default-branch
251 vc-master-locks))
252 ;; determine vc-top-version: it is either the head version,
253 ;; or the tip of the default branch
254 (let ((default-branch (vc-file-getprop file 'vc-default-branch)))
255 (cond
256 ;; no default branch
257 ((or (not default-branch) (string= "" default-branch))
258 (vc-file-setprop file 'vc-top-version
259 (vc-file-getprop file 'vc-head-version)))
260 ;; default branch is actually a revision
261 ((string-match "^[0-9]+\\.[0-9]+\\(\\.[0-9]+\\.[0-9]+\\)*$"
262 default-branch)
263 (vc-file-setprop file 'vc-top-version default-branch))
264 ;; else, search for the tip of the default branch
265 (t (erase-buffer)
266 (vc-insert-file (vc-name file) "^desc")
267 (vc-parse-buffer (list (list
268 (concat "^\\("
269 (regexp-quote default-branch)
270 "\\.[0-9]+\\)\ndate[ \t]+\\([0-9.]+\\);") 1 2))
271 file '(vc-top-version)))))
272 ;; translate the locks
273 (vc-parse-locks file (vc-file-getprop file 'vc-master-locks)))
274
275 ((eq (vc-backend file) 'CVS)
276 ;; don't switch to the *vc-info* buffer before running the
277 ;; command, because that would change its default directory
278 (save-excursion (set-buffer (get-buffer-create "*vc-info*"))
279 (erase-buffer))
280 (let ((exec-path (append vc-path exec-path))
281 ;; Add vc-path to PATH for the execution of this command.
282 (process-environment
283 (cons (concat "PATH=" (getenv "PATH")
284 ":" (mapconcat 'identity vc-path ":"))
285 process-environment)))
286 (apply 'call-process "cvs" nil "*vc-info*" nil
287 (list "status" (file-name-nondirectory file))))
288 (set-buffer (get-buffer "*vc-info*"))
289 (set-buffer-modified-p nil)
290 (auto-save-mode nil)
291 (vc-parse-buffer
292 ;; CVS 1.3 says "RCS Version:", other releases "RCS Revision:",
293 ;; and CVS 1.4a1 says "Repository revision:".
294 '(("\\(RCS Version\\|RCS Revision\\|Repository revision\\):[\t ]+\\([0-9.]+\\)" 2)
295 ("^File: [^ \t]+[ \t]+Status: \\(.*\\)" 1))
296 file
297 '(vc-latest-version vc-cvs-status))
298 ;; Translate those status values that are needed into symbols.
299 ;; Any other value is converted to nil.
300 (let ((status (vc-file-getprop file 'vc-cvs-status)))
301 (cond ((string-match "Up-to-date" status)
302 (vc-file-setprop file 'vc-cvs-status 'up-to-date)
303 (vc-file-setprop file 'vc-checkout-time
304 (nth 5 (file-attributes file))))
305 ((string-match "Locally Modified" status)
306 (vc-file-setprop file 'vc-cvs-status 'locally-modified))
307 ((string-match "Needs Merge" status)
308 (vc-file-setprop file 'vc-cvs-status 'needs-merge))
309 (t (vc-file-setprop file 'vc-cvs-status nil))))))
310 (kill-buffer (current-buffer))))
311
312;;; Functions that determine property values, by examining the
313;;; working file, the master file, or log program output
314
315(defun vc-consult-rcs-headers (file)
316 ;; Search for RCS headers in FILE, and set properties
317 ;; accordingly. This function can be disabled by setting
318 ;; vc-consult-headers to nil.
319 ;; Returns: nil if no headers were found
320 ;; (or if the feature is disabled,
321 ;; or if there is currently no buffer
322 ;; visiting FILE)
323 ;; 'rev if a workfile revision was found
324 ;; 'rev-and-lock if revision and lock info was found
325 (cond
326 ((or (not vc-consult-headers)
327 (not (get-file-buffer file))) nil)
328 ((save-excursion
329 (set-buffer (get-file-buffer file))
330 (goto-char (point-min))
331 (cond
332 ;; search for $Id or $Header
333 ;; -------------------------
334 ((or (and (search-forward "$Id: " nil t)
335 (looking-at "[^ ]+ \\([0-9.]+\\) "))
336 (and (progn (goto-char (point-min))
337 (search-forward "$Headers: " nil t))
338 (looking-at "[^ ]+ \\([0-9.]+\\) ")))
339 ;; if found, store the revision number ...
340 (let ((rev (buffer-substring (match-beginning 1)
341 (match-end 1))))
342 ;; ... and check for the locking state
343 (if (re-search-forward
344 (concat "\\=[0-9]+/[0-9]+/[0-9]+ " ; date
345 "[0-9]+:[0-9]+:[0-9]+ " ; time
346 "[^ ]+ [^ ]+ ") ; author & state
347 nil t)
348 (cond
349 ;; unlocked revision
350 ((looking-at "\\$")
351 (vc-file-setprop file 'vc-workfile-version rev)
352 (vc-file-setprop file 'vc-locking-user 'none)
353 'rev-and-lock)
354 ;; revision is locked by some user
355 ((looking-at "\\([^ ]+\\) \\$")
356 (vc-file-setprop file 'vc-workfile-version rev)
357 (vc-file-setprop file 'vc-locking-user
358 (buffer-substring (match-beginning 1)
359 (match-end 1)))
360 'rev-and-lock)
361 ;; everything else: false
362 (nil))
363 ;; unexpected information in
364 ;; keyword string --> quit
365 nil)))
366 ;; search for $Revision
367 ;; --------------------
368 ((re-search-forward (concat "\\$"
369 "Revision: \\([0-9.]+\\) \\$")
370 nil t)
371 ;; if found, store the revision number ...
372 (let ((rev (buffer-substring (match-beginning 1)
373 (match-end 1))))
374 ;; and see if there's any lock information
375 (goto-char (point-min))
376 (if (re-search-forward (concat "\\$" "Locker:") nil t)
377 (cond ((looking-at " \\([^ ]+\\) \\$")
378 (vc-file-setprop file 'vc-workfile-version rev)
379 (vc-file-setprop file 'vc-locking-user
380 (buffer-substring (match-beginning 1)
381 (match-end 1)))
382 'rev-and-lock)
383 ((looking-at " *\\$")
384 (vc-file-setprop file 'vc-workfile-version rev)
385 (vc-file-setprop file 'vc-locking-user 'none)
386 'rev-and-lock)
387 (t
388 (vc-file-setprop file 'vc-workfile-version rev)
389 (vc-file-setprop file 'vc-locking-user 'none)
390 'rev-and-lock))
391 (vc-file-setprop file 'vc-workfile-version rev)
392 'rev)))
393 ;; else: nothing found
394 ;; -------------------
395 (t nil))))))
396
397;;; Access functions to file properties
398;;; (Properties should be _set_ using vc-file-setprop, but
399;;; _retrieved_ only through these functions, which decide
400;;; if the property is already known or not. A property should
401;;; only be retrieved by vc-file-getprop if there is no
402;;; access function.)
403
404;;; properties indicating the backend
405;;; being used for FILE
406
407(defun vc-backend-subdirectory-name (&optional file)
408 ;; Where the master and lock files for the current directory are kept
409 (symbol-name
410 (or
411 (and file (vc-backend file))
412 vc-default-back-end
413 (setq vc-default-back-end (if (vc-find-binary "rcs") 'RCS 'SCCS)))))
414
415(defun vc-name (file)
416 "Return the master name of a file, nil if it is not registered."
417 (or (vc-file-getprop file 'vc-name)
418 (let ((name-and-type (vc-registered file)))
419 (if name-and-type
420 (progn
421 (vc-file-setprop file 'vc-backend (cdr name-and-type))
422 (vc-file-setprop file 'vc-name (car name-and-type)))))))
423
424(defun vc-backend (file)
425 "Return the version-control type of a file, nil if it is not registered."
426 (and file
427 (or (vc-file-getprop file 'vc-backend)
428 (let ((name-and-type (vc-registered file)))
429 (if name-and-type
430 (progn
431 (vc-file-setprop file 'vc-name (car name-and-type))
432 (vc-file-setprop file 'vc-backend (cdr name-and-type))))))))
433
434;;; properties indicating the locking state
435
436(defun vc-cvs-status (file)
437 ;; Return the cvs status of FILE
438 ;; (Status field in output of "cvs status")
439 (cond ((vc-file-getprop file 'vc-cvs-status))
440 (t (vc-fetch-master-properties file)
441 (vc-file-getprop file 'vc-cvs-status))))
442
443(defun vc-master-locks (file)
444 ;; Return the lock entries in the master of FILE.
445 ;; Return 'none if there are no such entries, and a list
446 ;; of the form ((VERSION USER) (VERSION USER) ...) otherwise.
447 (cond ((vc-file-getprop file 'vc-master-locks))
448 (t (vc-fetch-master-properties file)
449 (vc-file-getprop file 'vc-master-locks))))
450
451(defun vc-master-locking-user (file)
452 ;; Return the master file's idea of who is locking
453 ;; the current workfile version of FILE.
454 ;; Return 'none if it is not locked.
455 (let ((master-locks (vc-master-locks file)) lock)
456 (if (eq master-locks 'none) 'none
457 ;; search for a lock on the current workfile version
458 (setq lock (assoc (vc-workfile-version file) master-locks))
459 (cond (lock (cdr lock))
460 ('none)))))
461
462(defun vc-locking-user (file)
463 ;; Return the name of the person currently holding a lock on FILE.
464 ;; Return nil if there is no such person.
465 ;; Under CVS, a file is considered locked if it has been modified since
466 ;; it was checked out. Under CVS, this will sometimes return the uid of
467 ;; the owner of the file (as a number) instead of a string.
468 ;; The property is cached. It is only looked up if it is currently nil.
469 ;; Note that, for a file that is not locked, the actual property value
470 ;; is 'none, to distinguish it from an unknown locking state. That value
471 ;; is converted to nil by this function, and returned to the caller.
472 (let ((locking-user (vc-file-getprop file 'vc-locking-user)))
473 (if locking-user
474 ;; if we already know the property, return it
475 (if (eq locking-user 'none) nil locking-user)
476
477 ;; otherwise, infer the property...
478 (cond
479 ;; in the CVS case, check the status
480 ((eq (vc-backend file) 'CVS)
481 (if (eq (vc-cvs-status file) 'up-to-date)
482 (vc-file-setprop file 'vc-locking-user 'none)
483 ;; The expression below should return the username of the owner
484 ;; of the file. It doesn't. It returns the username if it is
485 ;; you, or otherwise the UID of the owner of the file. The
486 ;; return value from this function is only used by
487 ;; vc-dired-reformat-line, and it does the proper thing if a UID
488 ;; is returned.
489 ;;
490 ;; The *proper* way to fix this would be to implement a built-in
491 ;; function in Emacs, say, (username UID), that returns the
492 ;; username of a given UID.
493 ;;
494 ;; The result of this hack is that vc-directory will print the
495 ;; name of the owner of the file for any files that are
496 ;; modified.
497 (let ((uid (nth 2 (file-attributes file))))
498 (if (= uid (user-uid))
499 (vc-file-setprop file 'vc-locking-user (user-login-name))
500 (vc-file-setprop file 'vc-locking-user uid)))))
501
502 ;; RCS case: attempt a header search. If this feature is
503 ;; disabled, vc-consult-rcs-headers always returns nil.
504 ((and (eq (vc-backend file) 'RCS)
505 (eq (vc-consult-rcs-headers file) 'rev-and-lock)))
506
507 ;; if the file permissions are not trusted,
508 ;; use the information from the master file
509 ((or (not vc-keep-workfiles)
510 (eq vc-mistrust-permissions 't)
511 (and vc-mistrust-permissions
512 (funcall vc-mistrust-permissions
513 (vc-backend-subdirectory-name file))))
514 (vc-file-setprop file 'vc-locking-user (vc-master-locking-user file)))
515
516 ;; Otherwise: Use the file permissions. (But if it turns out that the
517 ;; file is not owned by the user, use the master file.)
518 ;; This implementation assumes that any file which is under version
519 ;; control and has -rw-r--r-- is locked by its owner. This is true
520 ;; for both RCS and SCCS, which keep unlocked files at -r--r--r--.
521 ;; We have to be careful not to exclude files with execute bits on;
522 ;; scripts can be under version control too. Also, we must ignore the
523 ;; group-read and other-read bits, since paranoid users turn them off.
524 ;; This hack wins because calls to the somewhat expensive
525 ;; `vc-fetch-master-properties' function only have to be made if
526 ;; (a) the file is locked by someone other than the current user,
527 ;; or (b) some untoward manipulation behind vc's back has changed
528 ;; the owner or the `group' or `other' write bits.
529 (t
530 (let ((attributes (file-attributes file)))
531 (cond ((string-match ".r-..-..-." (nth 8 attributes))
532 (vc-file-setprop file 'vc-locking-user 'none))
533 ((and (= (nth 2 attributes) (user-uid))
534 (string-match ".rw..-..-." (nth 8 attributes)))
535 (vc-file-setprop file 'vc-locking-user (user-login-name)))
536 (t
537 (vc-file-setprop file 'vc-locking-user
538 (vc-master-locking-user file))))
539 )))
540 ;; recursively call the function again,
541 ;; to convert a possible 'none value
542 (vc-locking-user file))))
543
544;;; properties to store current and recent version numbers
545
546(defun vc-latest-version (file)
547 ;; Return version level of the latest version of FILE
548 (cond ((vc-file-getprop file 'vc-latest-version))
549 (t (vc-fetch-properties file)
550 (vc-file-getprop file 'vc-latest-version))))
551
552(defun vc-your-latest-version (file)
553 ;; Return version level of the latest version of FILE checked in by you
554 (cond ((vc-file-getprop file 'vc-your-latest-version))
555 (t (vc-fetch-properties file)
556 (vc-file-getprop file 'vc-your-latest-version))))
557
558(defun vc-top-version (file)
559 ;; Return version level of the highest revision on the default branch
560 ;; If there is no default branch, return the highest version number
561 ;; on the trunk.
562 ;; This property is defined for RCS only.
563 (cond ((vc-file-getprop file 'vc-top-version))
564 (t (vc-fetch-master-properties file)
565 (vc-file-getprop file 'vc-top-version))))
566
567(defun vc-fetch-properties (file)
568 ;; Fetch vc-latest-version and vc-your-latest-version
569 ;; if that wasn't already done.
570 (vc-backend-dispatch
571 file
572 ;; SCCS
573 (vc-fetch-master-properties file)
574 ;; RCS
575 (progn
576 (set-buffer (get-buffer-create "*vc-info*"))
577 (vc-insert-file (vc-name file) "^desc")
578 (vc-parse-buffer
579 (list '("^\\([0-9]+\\.[0-9.]+\\)\ndate[ \t]+\\([0-9.]+\\);" 1 2)
580 (list (concat "^\\([0-9]+\\.[0-9.]+\\)\n"
581 "date[ \t]+\\([0-9.]+\\);[ \t]+"
582 "author[ \t]+"
583 (regexp-quote (user-login-name)) ";") 1 2))
584 file
585 '(vc-latest-version vc-your-latest-version)))
586 ;; CVS
587 (vc-fetch-master-properties file)
588 ))
589
590(defun vc-workfile-version (file)
591 ;; Return version level of the current workfile FILE
592 ;; This is attempted by first looking at the RCS keywords.
593 ;; If there are no keywords in the working file,
594 ;; vc-top-version is taken.
595 ;; Note that this property is cached, that is, it is only
596 ;; looked up if it is nil.
597 ;; For SCCS, this property is equivalent to vc-latest-version.
598 (cond ((vc-file-getprop file 'vc-workfile-version))
599 ((eq (vc-backend file) 'SCCS) (vc-latest-version file))
600 ((eq (vc-backend file) 'RCS)
601 (if (vc-consult-rcs-headers file)
602 (vc-file-getprop file 'vc-workfile-version)
603 (let ((rev (cond ((vc-top-version file))
604 ((vc-latest-version file)))))
605 (vc-file-setprop file 'vc-workfile-version rev)
606 rev)))
607 ((eq (vc-backend file) 'CVS)
608 (if (vc-consult-rcs-headers file) ;; CVS
609 (vc-file-getprop file 'vc-workfile-version)
610 (vc-find-cvs-master (file-name-directory file)
611 (file-name-nondirectory file))
612 (vc-file-getprop file 'vc-workfile-version)))))
613
614;;; actual version-control code starts here
615
616(defun vc-registered (file)
617 (let (handler handlers)
618 (if (boundp 'file-name-handler-alist)
619 (setq handler (find-file-name-handler file 'vc-registered)))
620 (if handler
621 (funcall handler 'vc-registered file)
622 ;; Search for a master corresponding to the given file
623 (let ((dirname (or (file-name-directory file) ""))
624 (basename (file-name-nondirectory file)))
625 (catch 'found
626 (mapcar
627 (function (lambda (s)
628 (if (atom s)
629 (funcall s dirname basename)
630 (let ((trial (format (car s) dirname basename)))
631 (if (and (file-exists-p trial)
632 ;; Make sure the file we found with name
633 ;; TRIAL is not the source file itself.
634 ;; That can happen with RCS-style names
635 ;; if the file name is truncated
636 ;; (e.g. to 14 chars). See if either
637 ;; directory or attributes differ.
638 (or (not (string= dirname
639 (file-name-directory trial)))
640 (not (equal
641 (file-attributes file)
642 (file-attributes trial)))))
643 (throw 'found (cons trial (cdr s))))))))
644 vc-master-templates)
645 nil)))))
646
647(defun vc-find-cvs-master (dirname basename)
648 ;; Check if DIRNAME/BASENAME is handled by CVS.
649 ;; If it is, do a (throw 'found (cons MASTER 'CVS)).
650 ;; Note: If the file is ``cvs add''ed but not yet ``cvs commit''ed
651 ;; the MASTER will not actually exist yet. The other parts of VC
652 ;; checks for this condition. This function returns nil if
653 ;; DIRNAME/BASENAME is not handled by CVS.
654 (if (and (file-directory-p (concat dirname "CVS/"))
655 (file-readable-p (concat dirname "CVS/Entries")))
656 (let ((bufs nil) (fold case-fold-search))
657 (unwind-protect
658 (save-excursion
659 (setq bufs (list
660 (find-file-noselect (concat dirname "CVS/Entries"))))
661 (set-buffer (car bufs))
662 (goto-char (point-min))
663 ;; make sure the file name is searched
664 ;; case-sensitively
665 (setq case-fold-search nil)
666 (cond
667 ((re-search-forward
668 (concat "^/" (regexp-quote basename) "/\\([^/]*\\)/")
669 nil t)
670 (setq case-fold-search fold) ;; restore the old value
671 ;; We found it. Store away version number, now
672 ;; that we are anyhow so close to finding it.
673 (vc-file-setprop (concat dirname basename)
674 'vc-workfile-version
675 (buffer-substring (match-beginning 1)
676 (match-end 1)))
677 (setq bufs (cons (find-file-noselect
678 (concat dirname "CVS/Repository"))
679 bufs))
680 (set-buffer (car bufs))
681 (let ((master
682 (concat (file-name-as-directory
683 (buffer-substring (point-min)
684 (1- (point-max))))
685 basename
686 ",v")))
687 (throw 'found (cons master 'CVS))))
688 (t (setq case-fold-search fold) ;; restore the old value
689 nil)))
690 (mapcar (function kill-buffer) bufs)))))
691
692(defun vc-buffer-backend ()
693 "Return the version-control type of the visited file, or nil if none."
694 (if (eq vc-buffer-backend t)
695 (setq vc-buffer-backend (vc-backend (buffer-file-name)))
696 vc-buffer-backend))
697
698(defun vc-toggle-read-only (&optional verbose)
699 "Change read-only status of current buffer, perhaps via version control.
700If the buffer is visiting a file registered with version control,
701then check the file in or out. Otherwise, just change the read-only flag
702of the buffer. With prefix argument, ask for version number."
703 (interactive "P")
704 (if (vc-backend (buffer-file-name))
705 (vc-next-action verbose)
706 (toggle-read-only)))
707(define-key global-map "\C-x\C-q" 'vc-toggle-read-only)
708
709(defun vc-mode-line (file &optional label)
710 "Set `vc-mode' to display type of version control for FILE.
711The value is set in the current buffer, which should be the buffer
712visiting FILE. Second optional arg LABEL is put in place of version
713control system name."
714 (interactive (list buffer-file-name nil))
715 (let ((vc-type (vc-backend file))
716 (vc-status-string (and vc-display-status (vc-status file))))
717 (setq vc-mode
718 (concat " " (or label (symbol-name vc-type)) vc-status-string))
719 ;; Make the buffer read-only if the file is not locked
720 ;; (or unchanged, in the CVS case).
721 ;; Determine this by looking at the mode string,
722 ;; so that no further external status query is necessary
723 (if vc-status-string
724 (if (eq (elt vc-status-string 0) ?-)
725 (setq buffer-read-only t))
726 (if (not (vc-locking-user file))
727 (setq buffer-read-only t)))
728 ;; Even root shouldn't modify a registered file without
729 ;; locking it first.
730 (and vc-type
731 (not buffer-read-only)
732 (zerop (user-uid))
733 (require 'vc)
734 (not (equal (user-login-name) (vc-locking-user file)))
735 (setq buffer-read-only t))
736 (and (null vc-type)
737 (file-symlink-p file)
738 (let ((link-type (vc-backend (file-symlink-p file))))
739 (if link-type
740 (message
741 "Warning: symbolic link to %s-controlled source file"
742 link-type))))
743 (force-mode-line-update)
744 ;;(set-buffer-modified-p (buffer-modified-p)) ;;use this if Emacs 18
745 vc-type))
746
747(defun vc-status (file)
748 ;; Return string for placement in modeline by `vc-mode-line'.
749 ;; Format:
750 ;;
751 ;; "-REV" if the revision is not locked
752 ;; ":REV" if the revision is locked by the user
753 ;; ":LOCKER:REV" if the revision is locked by somebody else
754 ;; " @@" for a CVS file that is added, but not yet committed
755 ;;
756 ;; In the CVS case, a "locked" working file is a
757 ;; working file that is modified with respect to the master.
758 ;; The file is "locked" from the moment when the user makes
759 ;; the buffer writable.
760 ;;
761 ;; This function assumes that the file is registered.
762
763 (let ((locker (vc-locking-user file))
764 (rev (vc-workfile-version file)))
765 (cond ((string= "0" rev)
766 " @@")
767 ((not locker)
768 (concat "-" rev))
769 ((if (stringp locker)
770 (string= locker (user-login-name))
771 (= locker (user-uid)))
772 (concat ":" rev))
773 (t
774 (concat ":" locker ":" rev)))))
775
776;;; install a call to the above as a find-file hook
777(defun vc-find-file-hook ()
778 ;; Recompute whether file is version controlled,
779 ;; if user has killed the buffer and revisited.
780 (cond
781 (buffer-file-name
782 (vc-file-clearprops buffer-file-name)
783 (cond
784 ((vc-backend buffer-file-name)
785 (vc-mode-line buffer-file-name)
786 (cond ((not vc-make-backup-files)
787 ;; Use this variable, not make-backup-files,
788 ;; because this is for things that depend on the file name.
789 (make-local-variable 'backup-inhibited)
790 (setq backup-inhibited t))))))))
791
792(add-hook 'find-file-hooks 'vc-find-file-hook)
793
794;;; more hooks, this time for file-not-found
795(defun vc-file-not-found-hook ()
796 "When file is not found, try to check it out from RCS or SCCS.
797Returns t if checkout was successful, nil otherwise."
798 (if (vc-backend buffer-file-name)
799 (save-excursion
800 (require 'vc)
801 (setq default-directory (file-name-directory (buffer-file-name)))
802 (not (vc-error-occurred (vc-checkout buffer-file-name))))))
803
804(add-hook 'find-file-not-found-hooks 'vc-file-not-found-hook)
805
806;; Discard info about a file when we kill its buffer.
807(defun vc-kill-buffer-hook ()
808 (if (stringp (buffer-file-name))
809 (progn
810 (vc-file-clearprops (buffer-file-name))
811 (kill-local-variable 'vc-buffer-backend))))
812
813;;;(add-hook 'kill-buffer-hook 'vc-kill-buffer-hook)
814
815;;; Now arrange for bindings and autoloading of the main package.
816;;; Bindings for this have to go in the global map, as we'll often
817;;; want to call them from random buffers.
818
819(setq vc-prefix-map (lookup-key global-map "\C-xv"))
820(if (not (keymapp vc-prefix-map))
821 (progn
822 (setq vc-prefix-map (make-sparse-keymap))
823 (define-key global-map "\C-xv" vc-prefix-map)
824 (define-key vc-prefix-map "a" 'vc-update-change-log)
825 (define-key vc-prefix-map "c" 'vc-cancel-version)
826 (define-key vc-prefix-map "d" 'vc-directory)
827 (define-key vc-prefix-map "h" 'vc-insert-headers)
828 (define-key vc-prefix-map "i" 'vc-register)
829 (define-key vc-prefix-map "l" 'vc-print-log)
830 (define-key vc-prefix-map "r" 'vc-retrieve-snapshot)
831 (define-key vc-prefix-map "s" 'vc-create-snapshot)
832 (define-key vc-prefix-map "u" 'vc-revert-buffer)
833 (define-key vc-prefix-map "v" 'vc-next-action)
834 (define-key vc-prefix-map "=" 'vc-diff)
835 (define-key vc-prefix-map "~" 'vc-version-other-window)))
836
837(if (not (boundp 'vc-menu-map))
838 ;; Don't do the menu bindings if menu-bar.el wasn't loaded to defvar
839 ;; vc-menu-map.
840 ()
841 ;;(define-key vc-menu-map [show-files]
842 ;; '("Show Files under VC" . (vc-directory t)))
843 (define-key vc-menu-map [vc-directory] '("Show Locked Files" . vc-directory))
844 (define-key vc-menu-map [separator1] '("----"))
845 (define-key vc-menu-map [vc-rename-file] '("Rename File" . vc-rename-file))
846 (define-key vc-menu-map [vc-version-other-window]
847 '("Show Other Version" . vc-version-other-window))
848 (define-key vc-menu-map [vc-diff] '("Compare with Last Version" . vc-diff))
849 (define-key vc-menu-map [vc-update-change-log]
850 '("Update ChangeLog" . vc-update-change-log))
851 (define-key vc-menu-map [vc-print-log] '("Show History" . vc-print-log))
852 (define-key vc-menu-map [separator2] '("----"))
853 (define-key vc-menu-map [undo] '("Undo Last Check-In" . vc-cancel-version))
854 (define-key vc-menu-map [vc-revert-buffer]
855 '("Revert to Last Version" . vc-revert-buffer))
856 (define-key vc-menu-map [vc-insert-header]
857 '("Insert Header" . vc-insert-headers))
858 (define-key vc-menu-map [vc-menu-check-in] '("Check In" . vc-next-action))
859 (define-key vc-menu-map [vc-check-out] '("Check Out" . vc-toggle-read-only))
860 (define-key vc-menu-map [vc-register] '("Register" . vc-register))
861 (put 'vc-rename-file 'menu-enable 'vc-mode)
862 (put 'vc-version-other-window 'menu-enable 'vc-mode)
863 (put 'vc-diff 'menu-enable 'vc-mode)
864 (put 'vc-update-change-log 'menu-enable
865 '(eq (vc-buffer-backend) 'RCS))
866 (put 'vc-print-log 'menu-enable 'vc-mode)
867 (put 'vc-cancel-version 'menu-enable 'vc-mode)
868 (put 'vc-revert-buffer 'menu-enable 'vc-mode)
869 (put 'vc-insert-headers 'menu-enable 'vc-mode)
870 (put 'vc-next-action 'menu-enable '(and vc-mode (not buffer-read-only)))
871 (put 'vc-toggle-read-only 'menu-enable '(and vc-mode buffer-read-only))
872 (put 'vc-register 'menu-enable '(and buffer-file-name (not vc-mode)))
873 )
874
875(provide 'vc-hooks)
876
877;;; vc-hooks.el ends here