1
;;; vc-mcvs.el --- VC backend for the Meta-CVS version-control system
3
;; Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009
4
;; Free Software Foundation, Inc.
6
;; Author: FSF (see vc.el for full credits)
9
;; This file is part of GNU Emacs.
11
;; GNU Emacs is free software: you can redistribute it and/or modify
12
;; it under the terms of the GNU General Public License as published by
13
;; the Free Software Foundation, either version 3 of the License, or
14
;; (at your option) any later version.
16
;; GNU Emacs is distributed in the hope that it will be useful,
17
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
;; GNU General Public License for more details.
21
;; You should have received a copy of the GNU General Public License
22
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
26
;; ********** READ THIS! **********
28
;; This file apparently does not work with the new (as of Emacs 23)
29
;; VC code. Use at your own risk. Please contact emacs-devel if you
30
;; can maintain this file and update it to work correctly.
32
;; ********** READ THIS! **********
34
;; This file has been obsolete and unsupported since Emacs 23.1.
37
;; The home page of the Meta-CVS version control system is at
39
;; http://users.footprints.net/~kaz/mcvs.html
41
;; This is derived from vc-cvs.el as follows:
42
;; - cp vc-cvs.el vc-mcvs.el
43
;; - Replace CVS/ with MCVS/CVS/
44
;; - Replace 'CVS with 'MCVS
45
;; - Replace -cvs- with -mcvs-
46
;; - Replace most of the rest of CVS to Meta-CVS
48
;; Then of course started the hacking. Only a small part of the code
49
;; has been touched and not much more than that was tested, so if
50
;; you bump into a bug, don't be surprised: just report it to me.
52
;; What has been partly tested:
53
;; - C-x v v to start editing a file that was checked out with CVSREAD on.
54
;; - C-x v v to commit a file
59
;; - M-x vc-rename-file RET
63
;; - Retrieving tags doesn't filter `cvs update' output and thus
64
;; parses bogus filenames. Don't know if it harms.
68
(eval-when-compile (require 'vc))
72
;;; Customization options
75
(defcustom vc-mcvs-global-switches nil
76
"Global switches to pass to any Meta-CVS command."
77
:type '(choice (const :tag "None" nil)
78
(string :tag "Argument String")
79
(repeat :tag "Argument List" :value ("") string))
83
(defcustom vc-mcvs-register-switches nil
84
"Switches for registering a file into Meta-CVS.
85
A string or list of strings passed to the checkin program by
86
\\[vc-register]. If nil, use the value of `vc-register-switches'.
87
If t, use no switches."
88
:type '(choice (const :tag "Unspecified" nil)
90
(string :tag "Argument String")
91
(repeat :tag "Argument List" :value ("") string))
95
(defcustom vc-mcvs-diff-switches nil
96
"String or list of strings specifying switches for Meta-CVS diff under VC.
97
If nil, use the value of `vc-diff-switches'. If t, use no switches."
98
:type '(choice (const :tag "Unspecified" nil)
100
(string :tag "Argument String")
101
(repeat :tag "Argument List" :value ("") string))
105
(defcustom vc-mcvs-header (or (cdr (assoc 'MCVS vc-header-alist))
107
"Header keywords to be inserted by `vc-insert-headers'."
109
:type '(repeat string)
112
(defcustom vc-mcvs-use-edit vc-cvs-use-edit
113
"Non-nil means to use `cvs edit' to \"check out\" a file.
114
This is only meaningful if you don't use the implicit checkout model
115
\(i.e. if you have $CVSREAD set)."
120
;;; Properties of the backend
122
(defalias 'vc-mcvs-revision-granularity 'vc-cvs-revision-granularity)
123
(defalias 'vc-mcvs-checkout-model 'vc-cvs-checkout-model)
126
;;; State-querying functions
129
;;;###autoload (defun vc-mcvs-registered (file)
130
;;;###autoload (if (vc-find-root file "MCVS/CVS")
131
;;;###autoload (progn
132
;;;###autoload (load "vc-mcvs")
133
;;;###autoload (vc-mcvs-registered file))))
135
(defun vc-mcvs-root (file)
136
"Return the root directory of a Meta-CVS project, if any."
137
(or (vc-file-getprop file 'mcvs-root)
138
(vc-file-setprop file 'mcvs-root (vc-find-root file "MCVS/CVS"))))
140
(defun vc-mcvs-read (file)
141
(if (file-readable-p file)
143
(insert-file-contents file)
144
(goto-char (point-min))
145
(read (current-buffer)))))
147
(defun vc-mcvs-map-file (dir file)
148
(let ((map (vc-mcvs-read (expand-file-name "MCVS/MAP" dir)))
150
(dolist (x map inode)
151
(if (equal (nth 2 x) file) (setq inode (nth 1 x))))))
153
(defun vc-mcvs-registered (file)
154
(let (root inode cvsfile)
155
(when (and (setq root (vc-mcvs-root file))
156
(setq inode (vc-mcvs-map-file
157
root (file-relative-name file root))))
158
(vc-file-setprop file 'mcvs-inode inode)
159
;; Avoid calling `mcvs diff' in vc-workfile-unchanged-p.
160
(vc-file-setprop file 'vc-checkout-time
161
(if (vc-cvs-registered
162
(setq cvsfile (expand-file-name inode root)))
163
(vc-file-getprop cvsfile 'vc-checkout-time)
164
;; The file might not be registered yet because
169
(defun vc-mcvs-state (file)
170
;; This would assume the Meta-CVS sandbox is synchronized.
171
;; (vc-mcvs-cvs state file))
172
"Meta-CVS-specific version of `vc-state'."
173
(if (vc-stay-local-p file)
174
(let ((state (vc-file-getprop file 'vc-state)))
175
;; If we should stay local, use the heuristic but only if
176
;; we don't have a more precise state already available.
177
(if (memq state '(up-to-date edited))
178
(vc-mcvs-state-heuristic file)
181
(setq default-directory (vc-mcvs-root file))
182
(vc-mcvs-command t 0 file "status")
183
(vc-cvs-parse-status t))))
186
(defalias 'vc-mcvs-state-heuristic 'vc-cvs-state-heuristic)
188
(defun vc-mcvs-working-revision (file)
189
(vc-cvs-working-revision
190
(expand-file-name (vc-file-getprop file 'mcvs-inode)
191
(vc-file-getprop file 'mcvs-root))))
194
;;; State-changing functions
197
(defun vc-mcvs-register (files &optional rev comment)
198
"Register FILES into the Meta-CVS version-control system.
199
COMMENT can be used to provide an initial description of FILE.
200
Passes either `vc-mcvs-register-switches' or `vc-register-switches'
201
to the Meta-CVS command."
202
;; FIXME: multiple-file case should be made to work.
203
(if (> (length files) 1) (error "Registering filesets is not yet supported."))
204
(let* ((file (car files))
205
(filename (file-name-nondirectory file))
206
(extpos (string-match "\\." filename))
207
(ext (if extpos (substring filename (1+ extpos))))
208
(root (vc-mcvs-root file))
209
(types-file (expand-file-name "MCVS/TYPES" root))
210
(map-file (expand-file-name "MCVS/MAP" root))
211
(types (vc-mcvs-read types-file)))
212
;; Make sure meta files like MCVS/MAP are not read-only (happens with
213
;; CVSREAD) since Meta-CVS doesn't pay attention to it at all and goes
215
(unless (file-writable-p map-file)
216
(vc-checkout map-file t))
217
(unless (or (file-writable-p types-file) (not (file-exists-p types-file)))
218
(vc-checkout types-file t))
219
;; Make sure the `mcvs add' will not fire up the CVSEDITOR
220
;; to add a rule for the given file's extension.
221
(when (and ext (not (assoc ext types)))
222
(let ((type (completing-read "Type to use (default): "
223
'("default" "name-only" "keep-old"
224
"binary" "value-only")
225
nil t nil nil "default")))
226
(push (list ext (make-symbol (upcase (concat ":" type)))) types)
227
(setq types (sort types (lambda (x y) (string< (car x) (car y)))))
228
(with-current-buffer (find-file-noselect types-file)
230
(pp types (current-buffer))
232
(unless (get-buffer-window (current-buffer) t)
233
(kill-buffer (current-buffer))))))
235
(prog1 (apply 'vc-mcvs-command nil 0 file
237
(and comment (string-match "[^\t\n ]" comment)
238
(concat "-m" comment))
239
(vc-switches 'MCVS 'register))
240
;; I'm not sure exactly why, but if we don't setup the inode and root
241
;; prop of the file, things break later on in vc-mode-line that
242
;; ends up calling vc-mcvs-working-revision.
243
;; We also need to set vc-checkout-time so that vc-workfile-unchanged-p
244
;; doesn't try to call `mcvs diff' on the file.
245
(vc-mcvs-registered file))))
247
(defalias 'vc-mcvs-responsible-p 'vc-mcvs-root
248
"Return non-nil if CVS thinks it is responsible for FILE.")
250
(defalias 'vc-cvs-could-register 'vc-cvs-responsible-p
251
"Return non-nil if FILE could be registered in Meta-CVS.
252
This is only possible if Meta-CVS is responsible for FILE's directory.")
254
(defun vc-mcvs-checkin (files rev comment)
255
"Meta-CVS-specific version of `vc-backend-checkin'."
256
(unless (or (not rev) (vc-mcvs-valid-revision-number-p rev))
257
(if (not (vc-mcvs-valid-symbolic-tag-name-p rev))
258
(error "%s is not a valid symbolic tag name" rev)
259
;; If the input revision is a valid symbolic tag name, we create it
260
;; as a branch, commit and switch to it.
261
;; This file-specific form of branching is deprecated.
262
;; We can't use `mcvs branch' and `mcvs switch' because they cannot
263
;; be applied just to this one file.
264
(apply 'vc-mcvs-command nil 0 files "tag" "-b" (list rev))
265
(apply 'vc-mcvs-command nil 0 files "update" "-r" (list rev))
266
(mapc (lambda (file) (vc-file-setprop file 'vc-mcvs-sticky-tag rev))
269
;; This commit might cvs-commit several files (e.g. MAP and TYPES)
270
;; so using numbered revs here is dangerous and somewhat meaningless.
271
(when rev (error "Cannot commit to a specific revision number"))
272
(let ((status (apply 'vc-mcvs-command nil 1 files
274
(vc-switches 'MCVS 'checkin))))
276
(goto-char (point-min))
277
(when (not (zerop status))
278
;; Check checkin problem.
280
((re-search-forward "Up-to-date check failed" nil t)
281
(mapc (lambda (file) (vc-file-setprop file 'vc-state 'needs-merge))
283
(error "%s" (substitute-command-keys
284
(concat "Up-to-date check failed: "
285
"type \\[vc-next-action] to merge in changes"))))
287
(pop-to-buffer (current-buffer))
288
(goto-char (point-min))
289
(shrink-window-if-larger-than-buffer)
290
(error "Check-in failed"))))
291
;; Single-file commit? Then update the revision by parsing the buffer.
292
;; Otherwise we can't necessarily tell what goes with what; clear
293
;; its properties so they have to be refetched.
294
(if (= (length files) 1)
296
(car files) 'vc-working-revision
297
(vc-parse-buffer "^\\(new\\|initial\\) revision: \\([0-9.]+\\)" 2))
298
(mapc (lambda (file) (vc-file-clearprops file)) files))
299
;; Anyway, forget the checkout model of the file, because we might have
300
;; guessed wrong when we found the file. After commit, we can
301
;; tell it from the permissions of the file (see
302
;; vc-mcvs-checkout-model).
303
(mapc (lambda (file) (vc-file-setprop file 'vc-checkout-model nil))
306
;; if this was an explicit check-in (does not include creation of
307
;; a branch), remove the sticky tag.
308
(if (and rev (not (vc-mcvs-valid-symbolic-tag-name-p rev)))
309
(vc-mcvs-command nil 0 files "update" "-A"))))
311
(defun vc-mcvs-find-revision (file rev buffer)
312
(apply 'vc-mcvs-command
314
"-Q" ; suppress diagnostic output
316
(and rev (not (string= rev ""))
319
(vc-switches 'MCVS 'checkout)))
321
(defun vc-mcvs-checkout (file &optional editable rev)
322
(message "Checking out %s..." file)
323
(with-current-buffer (or (get-file-buffer file) (current-buffer))
324
(vc-mcvs-update file editable rev (vc-switches 'MCVS 'checkout)))
326
(message "Checking out %s...done" file))
328
(defun vc-mcvs-update (file editable rev switches)
329
(if (and (file-exists-p file) (not rev))
330
;; If no revision was specified, just make the file writable
331
;; if necessary (using `cvs-edit' if requested).
332
(and editable (not (eq (vc-mcvs-checkout-model (list file)) 'implicit))
334
(vc-mcvs-command nil 0 file "edit")
335
(set-file-modes file (logior (file-modes file) 128))
336
(if (equal file buffer-file-name) (toggle-read-only -1))))
337
;; Check out a particular revision (or recreate the file).
338
(vc-file-setprop file 'vc-working-revision nil)
339
(apply 'vc-mcvs-command nil 0 file
342
;; default for verbose checkout: clear the sticky tag so
343
;; that the actual update will get the head of the trunk
344
(if (or (not rev) (string= rev ""))
349
(defun vc-mcvs-rename-file (old new)
350
(vc-mcvs-command nil 0 new "move" (file-relative-name old)))
352
(defun vc-mcvs-revert (file &optional contents-done)
353
"Revert FILE to the working revision it was based on."
354
(vc-default-revert 'MCVS file contents-done)
355
(unless (eq (vc-mcvs-checkout-model (list file)) 'implicit)
357
(vc-mcvs-command nil 0 file "unedit")
358
;; Make the file read-only by switching off all w-bits
359
(set-file-modes file (logand (file-modes file) 3950)))))
361
(defun vc-mcvs-merge (file first-revision &optional second-revision)
362
"Merge changes into current working copy of FILE.
363
The changes are between FIRST-REVISION and SECOND-REVISION."
364
(vc-mcvs-command nil 0 file
366
(concat "-j" first-revision)
367
(concat "-j" second-revision))
368
(vc-file-setprop file 'vc-state 'edited)
369
(with-current-buffer (get-buffer "*vc*")
370
(goto-char (point-min))
371
(if (re-search-forward "conflicts during merge" nil t)
373
0))) ; signal success
375
(defun vc-mcvs-merge-news (file)
376
"Merge in any new changes made to FILE."
377
(message "Merging changes into %s..." file)
378
;; (vc-file-setprop file 'vc-working-revision nil)
379
(vc-file-setprop file 'vc-checkout-time 0)
380
(vc-mcvs-command nil 0 file "update")
381
;; Analyze the merge result reported by Meta-CVS, and set
382
;; file properties accordingly.
383
(with-current-buffer (get-buffer "*vc*")
384
(goto-char (point-min))
385
;; get new working revision
386
(if (re-search-forward
387
"^Merging differences between [0-9.]* and \\([0-9.]*\\) into" nil t)
388
(vc-file-setprop file 'vc-working-revision (match-string 1))
389
(vc-file-setprop file 'vc-working-revision nil))
392
(if (eq (buffer-size) 0)
393
0 ;; there were no news; indicate success
394
(if (re-search-forward
395
(concat "^\\([CMUP] \\)?"
397
"\\( already contains the differences between \\)?")
400
;; Merge successful, we are in sync with repository now
401
((or (match-string 2)
402
(string= (match-string 1) "U ")
403
(string= (match-string 1) "P "))
404
(vc-file-setprop file 'vc-state 'up-to-date)
405
(vc-file-setprop file 'vc-checkout-time
406
(nth 5 (file-attributes file)))
407
0);; indicate success to the caller
408
;; Merge successful, but our own changes are still in the file
409
((string= (match-string 1) "M ")
410
(vc-file-setprop file 'vc-state 'edited)
411
0);; indicate success to the caller
412
;; Conflicts detected!
414
(vc-file-setprop file 'vc-state 'edited)
415
1);; signal the error to the caller
417
(pop-to-buffer "*vc*")
418
(error "Couldn't analyze mcvs update result")))
419
(message "Merging changes into %s...done" file))))
421
(defun vc-mcvs-modify-change-comment (files rev comment)
422
"Modify the change comments for FILES on a specified REV.
423
Will fail unless you have administrative privileges on the repo."
424
(vc-mcvs-command nil 0 files "rcs" (concat "-m" comment ":" rev)))
428
;;; History functions
431
(defun vc-mcvs-print-log (files &optional buffer)
432
"Get change log associated with FILES."
433
(let ((default-directory (vc-mcvs-root (car files))))
434
;; Run the command from the root dir so that `mcvs filt' returns
435
;; valid relative names.
438
(if (vc-stay-local-p files) 'async 0)
441
(defun vc-mcvs-diff (files &optional oldvers newvers buffer)
442
"Get a difference report using Meta-CVS between two revisions of FILES."
443
(let* ((async (and (not vc-disable-async-diff)
444
(vc-stay-local-p files)))
445
;; Run the command from the root dir so that `mcvs filt' returns
446
;; valid relative names.
447
(default-directory (vc-mcvs-root (car files)))
449
(apply 'vc-mcvs-command (or buffer "*vc-diff*")
452
(and oldvers (concat "-r" oldvers))
453
(and newvers (concat "-r" newvers))
454
(vc-switches 'MCVS 'diff))))
455
(if async 1 status))) ; async diff, pessimistic assumption.
457
(defun vc-mcvs-annotate-command (file buffer &optional revision)
458
"Execute \"mcvs annotate\" on FILE, inserting the contents in BUFFER.
459
Optional arg REVISION is a revision to annotate from."
462
(if (vc-stay-local-p file) 'async 0)
463
file "annotate" (if revision (concat "-r" revision)))
464
(with-current-buffer buffer
465
(goto-char (point-min))
466
(re-search-forward "^[0-9]")
467
(delete-region (point-min) (1- (point)))))
469
(defalias 'vc-mcvs-annotate-current-time 'vc-cvs-annotate-current-time)
470
(defalias 'vc-mcvs-annotate-time 'vc-cvs-annotate-time)
476
(defun vc-mcvs-create-tag (dir name branchp)
477
"Assign to DIR's current revision a given NAME.
478
If BRANCHP is non-nil, the name is created as a branch (and the current
479
workspace is immediately moved to that new branch)."
481
(vc-mcvs-command nil 0 dir "tag" "-c" name)
482
(vc-mcvs-command nil 0 dir "branch" name)
483
(vc-mcvs-command nil 0 dir "switch" name)))
485
(defun vc-mcvs-retrieve-tag (dir name update)
486
"Retrieve a tag at and below DIR.
487
NAME is the name of the tag; if it is empty, do a `cvs update'.
488
If UPDATE is non-nil, then update (resynch) any affected buffers."
489
(with-current-buffer (get-buffer-create "*vc*")
490
(let ((default-directory dir)
493
(if (or (not name) (string= name ""))
494
(vc-mcvs-command t 0 nil "update")
495
(vc-mcvs-command t 0 nil "update" "-r" name)
496
(setq sticky-tag name))
498
(goto-char (point-min))
500
(if (looking-at "\\([CMUP]\\) \\(.*\\)")
501
(let* ((file (expand-file-name (match-string 2) dir))
502
(state (match-string 1))
503
(buffer (find-buffer-visiting file)))
506
((or (string= state "U")
508
(vc-file-setprop file 'vc-state 'up-to-date)
509
(vc-file-setprop file 'vc-working-revision nil)
510
(vc-file-setprop file 'vc-checkout-time
511
(nth 5 (file-attributes file))))
512
((or (string= state "M")
514
(vc-file-setprop file 'vc-state 'edited)
515
(vc-file-setprop file 'vc-working-revision nil)
516
(vc-file-setprop file 'vc-checkout-time 0)))
517
(vc-file-setprop file 'vc-mcvs-sticky-tag sticky-tag)
518
(vc-resynch-buffer file t t))))
519
(forward-line 1))))))
526
(defalias 'vc-mcvs-make-version-backups-p 'vc-stay-local-p
527
"Return non-nil if version backups should be made for FILE.")
528
(defalias 'vc-mcvs-check-headers 'vc-cvs-check-headers)
532
;;; Internal functions
535
(defun vc-mcvs-command (buffer okstatus file &rest flags)
536
"A wrapper around `vc-do-command' for use in vc-mcvs.el.
537
The difference to vc-do-command is that this function always invokes `mcvs',
538
and that it passes `vc-mcvs-global-switches' to it before FLAGS."
539
(let ((args (append '("--error-terminate")
540
(if (stringp vc-mcvs-global-switches)
541
(cons vc-mcvs-global-switches flags)
542
(append vc-mcvs-global-switches flags)))))
543
(if (not (member (car flags) '("diff" "log" "status")))
544
;; No need to filter: do it the easy way.
545
(apply 'vc-do-command (or buffer "*vc*") okstatus "mcvs" file args)
546
;; We need to filter the output.
547
;; The output of the filter uses filenames relative to the root,
548
;; so we need to change the default-directory.
549
;; (assert (equal default-directory (vc-mcvs-root file)))
551
(or buffer "*vc*") okstatus "sh" nil "-c"
554
'shell-quote-argument
555
(append (remq nil args)
556
(if file (list (file-relative-name file))))
560
(defun vc-mcvs-repository-hostname (dirname)
561
(vc-cvs-repository-hostname (vc-mcvs-root dirname)))
563
(defun vc-mcvs-dir-state-heuristic (dir)
564
"Find the Meta-CVS state of all files in DIR, using only local information."
566
(vc-cvs-get-entries dir)
567
(goto-char (point-min))
569
;; Meta-MCVS-removed files are not taken under VC control.
570
(when (looking-at "/\\([^/]*\\)/[^/-]")
571
(let ((file (expand-file-name (match-string 1) dir)))
572
(unless (vc-file-getprop file 'vc-state)
573
(vc-cvs-parse-entry file t))))
576
(defalias 'vc-mcvs-valid-symbolic-tag-name-p 'vc-cvs-valid-symbolic-tag-name-p)
577
(defalias 'vc-mcvs-valid-revision-number-p 'vc-cvs-valid-revision-number-p)
581
;; ********** READ THIS! **********
583
;; This file apparently does not work with the new (as of Emacs 23)
584
;; VC code. Use at your own risk. Please contact emacs-devel if you
585
;; can maintain this file and update it to work correctly.
587
;; ********** READ THIS! **********
589
;; arch-tag: a39c7c1c-5247-429d-88df-dd7187d2e704
590
;;; vc-mcvs.el ends here