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