]> code.delx.au - gnu-emacs/commitdiff
Functions reordered.
authorAndré Spiegel <spiegel@gnu.org>
Thu, 16 Nov 2000 18:14:41 +0000 (18:14 +0000)
committerAndré Spiegel <spiegel@gnu.org>
Thu, 16 Nov 2000 18:14:41 +0000 (18:14 +0000)
lisp/vc-cvs.el
lisp/vc-rcs.el
lisp/vc-sccs.el

index b78d9c0829f437d23ffbf09f796124633a01de79..d761b6c625f92532dc25dd20f6f8c248894720d5 100644 (file)
@@ -5,7 +5,7 @@
 ;; Author:      FSF (see vc.el for full credits)
 ;; Maintainer:  Andre Spiegel <spiegel@gnu.org>
 
-;; $Id: vc-cvs.el,v 1.10 2000/11/16 15:29:40 spiegel Exp $
+;; $Id: vc-cvs.el,v 1.11 2000/11/16 16:42:10 spiegel Exp $
 
 ;; This file is part of GNU Emacs.
 
 
 ;;; Code:
 
+;;; 
+;;; Customization options
+;;;
+
 (defcustom vc-cvs-register-switches nil
   "*Extra switches for registering a file into CVS.
 A string or list of strings passed to the checkin program by
@@ -67,6 +71,22 @@ then VC only stays local for hosts that match it."
   :version "21.1"
   :group 'vc)
 
+\f
+;;;
+;;; Internal variables
+;;;
+
+(defvar vc-cvs-local-month-numbers
+  '(("Jan" . 1) ("Feb" .  2) ("Mar" .  3) ("Apr" .  4)
+    ("May" . 5) ("Jun" .  6) ("Jul" .  7) ("Aug" .  8)
+    ("Sep" . 9) ("Oct" . 10) ("Nov" . 11) ("Dec" . 12))
+  "Local association list of month numbers.")
+
+\f
+;;;
+;;; State-querying functions 
+;;;
+
 ;;;###autoload (defun vc-cvs-registered (f)
 ;;;###autoload   (when (file-readable-p (expand-file-name
 ;;;###autoload                           "CVS/Entries" (file-name-directory f)))
@@ -92,97 +112,6 @@ then VC only stays local for hosts that match it."
           (t nil)))
       nil)))
 
-(defun vc-cvs-stay-local-p (file)
-  "Return non-nil if VC should stay local when handling FILE."
-  (if vc-cvs-stay-local
-      (let* ((dirname (if (file-directory-p file)
-                         (directory-file-name file)
-                       (file-name-directory file)))
-            (prop
-             (or (vc-file-getprop dirname 'vc-cvs-stay-local-p)
-                 (let ((rootname (expand-file-name "CVS/Root" dirname)))
-                   (vc-file-setprop
-                    dirname 'vc-cvs-stay-local-p
-                    (when (file-readable-p rootname)
-                      (with-temp-buffer
-                        (vc-insert-file rootname)
-                        (goto-char (point-min))
-                        (if (looking-at "\\([^:]*\\):")
-                            (if (not (stringp vc-cvs-stay-local))
-                                'yes
-                              (let ((hostname (match-string 1)))
-                                (if (string-match vc-cvs-stay-local hostname)
-                                    'yes
-                                  'no)))
-                          'no))))))))
-       (if (eq prop 'yes) t nil))))
-           
-(defun vc-cvs-workfile-version (file)
-  "CVS-specific version of `vc-workfile-version'."
-  ;; There is no need to consult RCS headers under CVS, because we
-  ;; get the workfile version for free when we recognize that a file
-  ;; is registered in CVS.
-  (vc-cvs-registered file)
-  (vc-file-getprop file 'vc-workfile-version))
-
-(defun vc-cvs-checkout-model (file)
-  "CVS-specific version of `vc-checkout-model'."
-  (if (or (getenv "CVSREAD")
-          ;; If the file is not writable (despite CVSREAD being
-          ;; undefined), this is probably because the file is being
-          ;; "watched" by other developers.
-          ;; (If vc-mistrust-permissions was t, we actually shouldn't
-          ;; trust this, but there is no other way to learn this from CVS
-          ;; at the moment (version 1.9).)
-          (string-match "r-..-..-." (nth 8 (file-attributes file))))
-      'announce
-    'implicit))
-\f
-;; VC Dired functions
-
-(defun vc-cvs-dired-state-info (file)
-  "CVS-specific version of `vc-dired-state-info'."
-  (let* ((cvs-state (vc-state file))
-        (state (cond ((eq cvs-state 'edited)    "modified")
-                     ((eq cvs-state 'needs-patch)      "patch")
-                     ((eq cvs-state 'needs-merge)         "merge")
-                     ;; FIXME: those two states cannot occur right now
-                     ((eq cvs-state 'unlocked-changes) "conflict")
-                     ((eq cvs-state 'locally-added)       "added")
-                     )))
-    (if state (concat "(" state ")"))))
-
-(defun vc-cvs-parse-status (&optional full)
-  "Parse output of \"cvs status\" command in the current buffer.
-Set file properties accordingly.  Unless FULL is t, parse only
-essential information."
-  (let (file status)
-    (goto-char (point-min))
-    (if (re-search-forward "^File: " nil t)
-        (cond
-         ((looking-at "no file") nil)
-         ((re-search-forward "\\=\\([^ \t]+\\)" nil t)
-         (setq file (expand-file-name (match-string 1)))
-          (vc-file-setprop file 'vc-backend 'CVS)
-          (if (not (re-search-forward "\\=[ \t]+Status: \\(.*\\)" nil t))
-              (setq status "Unknown")
-            (setq status (match-string 1)))
-          (if (and full
-                   (re-search-forward
-                   "\\(RCS Version\\|RCS Revision\\|Repository revision\\):\
-\[\t ]+\\([0-9.]+\\)"
-                    nil t))
-              (vc-file-setprop file 'vc-latest-version (match-string 2)))
-          (cond
-           ((string-match "Up-to-date" status)
-            (vc-file-setprop file 'vc-checkout-time
-                             (nth 5 (file-attributes file)))
-            'up-to-date)
-           ((string-match "Locally Modified"    status) 'edited)
-          ((string-match "Needs Merge"         status) 'needs-merge)
-          ((string-match "Needs \\(Checkout\\|Patch\\)" status) 'needs-patch)
-          (t 'edited)))))))
-
 (defun vc-cvs-state (file)
   "CVS-specific version of `vc-state'."
   (if (vc-cvs-stay-local-p file)
@@ -207,6 +136,50 @@ essential information."
         'up-to-date
       'edited)))
 
+(defun vc-cvs-dir-state (dir)
+  "Find the CVS state of all files in DIR."
+  (if (vc-cvs-stay-local-p dir)
+      (vc-cvs-dir-state-heuristic dir)
+    (let ((default-directory dir))
+      ;; Don't specify DIR in this command, the default-directory is
+      ;; enough.  Otherwise it might fail with remote repositories.
+      (with-temp-buffer
+       (vc-do-command t 0 "cvs" nil "status" "-l")
+       (goto-char (point-min))
+       (while (re-search-forward "^=+\n\\([^=\n].*\n\\|\n\\)+" nil t)
+         (narrow-to-region (match-beginning 0) (match-end 0))
+         (vc-cvs-parse-status)
+         (goto-char (point-max))
+         (widen))))))
+
+(defun vc-cvs-workfile-version (file)
+  "CVS-specific version of `vc-workfile-version'."
+  ;; There is no need to consult RCS headers under CVS, because we
+  ;; get the workfile version for free when we recognize that a file
+  ;; is registered in CVS.
+  (vc-cvs-registered file)
+  (vc-file-getprop file 'vc-workfile-version))
+
+(defun vc-cvs-latest-on-branch-p (file)
+  "Return t iff current workfile version of FILE is the latest on its branch."
+  ;; Since this is only used as a sanity check for vc-cancel-version,
+  ;; and that is not supported under CVS at all, we can safely return t here.
+  ;; TODO: Think of getting rid of this altogether.
+  t)
+
+(defun vc-cvs-checkout-model (file)
+  "CVS-specific version of `vc-checkout-model'."
+  (if (or (getenv "CVSREAD")
+          ;; If the file is not writable (despite CVSREAD being
+          ;; undefined), this is probably because the file is being
+          ;; "watched" by other developers.
+          ;; (If vc-mistrust-permissions was t, we actually shouldn't
+          ;; trust this, but there is no other way to learn this from CVS
+          ;; at the moment (version 1.9).)
+          (string-match "r-..-..-." (nth 8 (file-attributes file))))
+      'announce
+    'implicit))
+
 (defun vc-cvs-mode-line-string (file)
   "Return string for placement into the modeline for FILE.
 Compared to the default implementation, this function handles the
@@ -227,288 +200,54 @@ special case of a CVS file that is added but not yet comitted."
            ;; for 'needs-patch and 'needs-merge.
            (concat "CVS:" rev)))))
 
-(defun vc-cvs-dir-state (dir)
-  "Find the CVS state of all files in DIR."
-  (if (vc-cvs-stay-local-p dir)
-      (vc-cvs-dir-state-heuristic dir)
-    (let ((default-directory dir))
-      ;; Don't specify DIR in this command, the default-directory is
-      ;; enough.  Otherwise it might fail with remote repositories.
-      (with-temp-buffer
-       (vc-do-command t 0 "cvs" nil "status" "-l")
-       (goto-char (point-min))
-       (while (re-search-forward "^=+\n\\([^=\n].*\n\\|\n\\)+" nil t)
-         (narrow-to-region (match-beginning 0) (match-end 0))
-         (vc-cvs-parse-status)
-         (goto-char (point-max))
-         (widen))))))
-
-(defun vc-cvs-dir-state-heuristic (dir)
-  "Find the CVS state of all files in DIR, using only local information."
-  (with-temp-buffer
-    (vc-insert-file (expand-file-name "CVS/Entries" dir))
-    (goto-char (point-min))
-    (while (not (eobp))
-      (when (looking-at "/\\([^/]*\\)/")
-       (let ((file (expand-file-name (match-string 1) dir)))
-         (unless (vc-file-getprop file 'vc-state)
-           (vc-cvs-parse-entry file t))))
-      (forward-line 1))))
+(defun vc-cvs-dired-state-info (file)
+  "CVS-specific version of `vc-dired-state-info'."
+  (let* ((cvs-state (vc-state file))
+        (state (cond ((eq cvs-state 'edited)    "modified")
+                     ((eq cvs-state 'needs-patch)      "patch")
+                     ((eq cvs-state 'needs-merge)         "merge")
+                     ;; FIXME: those two states cannot occur right now
+                     ((eq cvs-state 'unlocked-changes) "conflict")
+                     ((eq cvs-state 'locally-added)       "added")
+                     )))
+    (if state (concat "(" state ")"))))
 
-(defun vc-cvs-parse-entry (file &optional set-state)
-  "Parse a line from CVS/Entries.
-Compare modification time to that of the FILE, set file properties
-accordingly.  However, `vc-state' is set only if optional arg SET-STATE
-is non-nil."
-  (cond
-   ;; entry for a "locally added" file (not yet committed)
-   ((looking-at "/[^/]+/0/")
-    (vc-file-setprop file 'vc-checkout-time 0)
-    (vc-file-setprop file 'vc-workfile-version "0")
-    (if set-state (vc-file-setprop file 'vc-state 'edited)))
-   ;; normal entry
-   ((looking-at
-     (concat "/[^/]+"
-            ;; revision
-            "/\\([^/]*\\)"
-            ;; timestamp
-            "/[A-Z][a-z][a-z]"       ;; week day (irrelevant)
-            " \\([A-Z][a-z][a-z]\\)" ;; month name
-            " *\\([0-9]*\\)"         ;; day of month
-            " \\([0-9]*\\):\\([0-9]*\\):\\([0-9]*\\)"  ;; hms
-            " \\([0-9]*\\)"          ;; year
-            ;; optional conflict field
-            "\\(+[^/]*\\)?/"))
-    (vc-file-setprop file 'vc-workfile-version (match-string 1))
-    ;; compare checkout time and modification time
-    (let ((second (string-to-number (match-string 6)))
-         (minute (string-to-number (match-string 5)))
-         (hour (string-to-number (match-string 4)))
-         (day (string-to-number (match-string 3)))
-         (year (string-to-number (match-string 7)))
-         (month (/ (string-match
-                    (match-string 2)
-                    "xxxJanFebMarAprMayJunJulAugSepOctNovDec")
-                   3))
-         (mtime (nth 5 (file-attributes file))))
-      (cond ((equal mtime
-                   (encode-time second minute hour day month year 0))
-            (vc-file-setprop file 'vc-checkout-time mtime)
-            (if set-state (vc-file-setprop file 'vc-state 'up-to-date)))
-           (t
-            (vc-file-setprop file 'vc-checkout-time 0)
-            (if set-state (vc-file-setprop file 'vc-state 'edited))))))
-   ;; entry with arbitrary text as timestamp
-   ;; (this means we should consider it modified)
-   ((looking-at
-     (concat "/[^/]+"
-            ;; revision
-            "/\\([^/]*\\)"
-            ;; timestamp (arbitrary text)
-            "/[^/]*"
-            ;; optional conflict field
-            "\\(+[^/]*\\)?/"))
-    (vc-file-setprop file 'vc-workfile-version (match-string 1))
-    (vc-file-setprop file 'vc-checkout-time 0)
-    (if set-state (vc-file-setprop file 'vc-state 'edited)))))
 \f
-(defun vc-cvs-print-log (file)
-  "Get change log associated with FILE."
-  (vc-do-command t (if (vc-cvs-stay-local-p file) 'async 0)
-                 "cvs" file "log"))
+;;;
+;;; State-changing functions
+;;;
 
-(defun vc-cvs-show-log-entry (version)
-  (when (re-search-forward
-        ;; also match some context, for safety
-        (concat "----\nrevision " version
-                "\\(\tlocked by:.*\n\\|\n\\)date: ") nil t)
-    ;; set the display window so that
-    ;; the whole log entry is displayed
-    (let (start end lines)
-      (beginning-of-line) (forward-line -1) (setq start (point))
-      (if (not (re-search-forward "^----*\nrevision" nil t))
-         (setq end (point-max))
-       (beginning-of-line) (forward-line -1) (setq end (point)))
-      (setq lines (count-lines start end))
-      (cond
-       ;; if the global information and this log entry fit
-       ;; into the window, display from the beginning
-       ((< (count-lines (point-min) end) (window-height))
-       (goto-char (point-min))
-       (recenter 0)
-       (goto-char start))
-       ;; if the whole entry fits into the window,
-       ;; display it centered
-       ((< (1+ lines) (window-height))
-       (goto-char start)
-       (recenter (1- (- (/ (window-height) 2) (/ lines 2)))))
-       ;; otherwise (the entry is too large for the window),
-       ;; display from the start
-       (t
-       (goto-char start)
-       (recenter 0))))))
-
-(defun vc-cvs-create-snapshot (dir name branchp)
-  "Assign to DIR's current version a given NAME.
-If BRANCHP is non-nil, the name is created as a branch (and the current
-workspace is immediately moved to that new branch)."
-  (vc-do-command nil 0 "cvs" dir "tag" "-c" (if branchp "-b") name)
-  (when branchp (vc-do-command nil 0 "cvs" dir "update" "-r" name)))
-
-(defun vc-cvs-retrieve-snapshot (dir name update)
-  "Retrieve a snapshot at and below DIR.
-NAME is the name of the snapshot; if it is empty, do a `cvs update'.
-If UPDATE is non-nil, then update (resynch) any affected buffers."
-  (with-current-buffer (get-buffer-create "*vc*")
-    (let ((default-directory dir))
-      (erase-buffer)
-      (if (or (not name) (string= name ""))
-         (vc-do-command t 0 "cvs" nil "update")
-       (vc-do-command t 0 "cvs" nil "update" "-r" name))
-      (when update
-       (goto-char (point-min))
-       (while (not (eobp))
-         (if (looking-at "\\([CMUP]\\) \\(.*\\)")
-             (let* ((file (expand-file-name (match-string 2) dir))
-                    (state (match-string 1))
-                    (buffer (find-buffer-visiting file)))
-               (when buffer
-                 (cond
-                  ((or (string= state "U")
-                       (string= state "P"))
-                   (vc-file-setprop file 'vc-state 'up-to-date)
-                   (vc-file-setprop file 'vc-workfile-version nil)
-                   (vc-file-setprop file 'vc-checkout-time
-                                    (nth 5 (file-attributes file))))
-                  ((or (string= state "M")
-                       (string= state "C"))
-                   (vc-file-setprop file 'vc-state 'edited)
-                   (vc-file-setprop file 'vc-workfile-version nil)
-                   (vc-file-setprop file 'vc-checkout-time 0)))
-                 (vc-resynch-buffer file t t))))
-         (forward-line 1))))))
-
-(defun vc-cvs-merge (file first-version &optional second-version)
-  "Merge changes into current working copy of FILE.
-The changes are between FIRST-VERSION and SECOND-VERSION."
-  (vc-do-command nil 0 "cvs" file
-                 "update" "-kk"
-                 (concat "-j" first-version)
-                 (concat "-j" second-version))
-  (vc-file-setprop file 'vc-state 'edited)
-  (save-excursion
-    (set-buffer (get-buffer "*vc*"))
-    (goto-char (point-min))
-    (if (re-search-forward "conflicts during merge" nil t)
-        1                              ; signal error
-      0)))                             ; signal success
-
-(defun vc-cvs-merge-news (file)
-  "Merge in any new changes made to FILE."
-  (message "Merging changes into %s..." file)
-  (save-excursion
-    ;; (vc-file-setprop file 'vc-workfile-version nil)
-    (vc-file-setprop file 'vc-checkout-time 0)
-    (vc-do-command nil 0 "cvs" file "update")
-    ;; Analyze the merge result reported by CVS, and set
-    ;; file properties accordingly.
-    (set-buffer (get-buffer "*vc*"))
-    (goto-char (point-min))
-    ;; get new workfile version
-    (if (re-search-forward (concat "^Merging differences between "
-                                  "[01234567890.]* and "
-                                  "\\([01234567890.]*\\) into")
-                          nil t)
-       (vc-file-setprop file 'vc-workfile-version (match-string 1))
-      (vc-file-setprop file 'vc-workfile-version nil))
-    ;; get file status
-    (prog1
-        (if (eq (buffer-size) 0)
-            0 ;; there were no news; indicate success
-          (if (re-search-forward
-               (concat "^\\([CMUP] \\)?"
-                       (regexp-quote (file-name-nondirectory file))
-                       "\\( already contains the differences between \\)?")
-               nil t)
-              (cond
-               ;; Merge successful, we are in sync with repository now
-               ((or (match-string 2)
-                    (string= (match-string 1) "U ")
-                    (string= (match-string 1) "P "))
-                (vc-file-setprop file 'vc-state 'up-to-date)
-                (vc-file-setprop file 'vc-checkout-time
-                                 (nth 5 (file-attributes file)))
-                0);; indicate success to the caller
-               ;; Merge successful, but our own changes are still in the file
-               ((string= (match-string 1) "M ")
-                (vc-file-setprop file 'vc-state 'edited)
-                0);; indicate success to the caller
-               ;; Conflicts detected!
-               (t
-                (vc-file-setprop file 'vc-state 'edited)
-                1);; signal the error to the caller
-               )
-            (pop-to-buffer "*vc*")
-            (error "Couldn't analyze cvs update result")))
-      (message "Merging changes into %s...done" file))))
-
-(defun vc-cvs-check-headers ()
-  "Check if the current file has any headers in it."
-  (save-excursion
-    (goto-char (point-min))
-    (re-search-forward "\\$[A-Za-z\300-\326\330-\366\370-\377]+\
-\\(: [\t -#%-\176\240-\377]*\\)?\\$" nil t)))
-
-(defun vc-cvs-steal (file &optional rev)
-  "Steal the lock on the current workfile for FILE and revision REV.
-Inappropriate for CVS"
-  (error "You cannot steal a CVS lock; there are no CVS locks to steal"))
-
-;; vc-check `not reached' for CVS.
+(defun vc-cvs-register (file &optional rev comment)
+  "Register FILE into the CVS version-control system.
+COMMENT can be used to provide an initial description of FILE.
 
-(defun vc-cvs-revert (file)
-  "Revert FILE to the version it was based on."
-  ;; Check out via standard output (caused by the final argument
-  ;; FILE below), so that no sticky tag is set.
-  (vc-cvs-checkout file nil (vc-workfile-version file) file)
-  ;; If "cvs edit" was used to make the file writable,
-  ;; call "cvs unedit" now to undo that.
-  (if (and (not (eq (vc-cvs-checkout-model file) 'implicit))
-           vc-cvs-use-edit)
-      (vc-do-command nil 0 "cvs" file "unedit")))
+`vc-register-switches' and `vc-cvs-register-switches' are passed to
+the CVS command (in that order)."
+    (let ((switches (list
+                    (if (stringp vc-register-switches)
+                        (list vc-register-switches)
+                      vc-register-switches)
+                    (if (stringp vc-cvs-register-switches)
+                        (list vc-cvs-register-switches)
+                      vc-cvs-register-switches))))
+    
+      (apply 'vc-do-command nil 0 "cvs" file
+            "add"
+            (and comment (string-match "[^\t\n ]" comment)
+                 (concat "-m" comment))
+            switches)))
 
-(defun vc-cvs-diff (file &optional oldvers newvers)
-  "Get a difference report using CVS between two versions of FILE."
-  (let (options status
-        (diff-switches-list (if (listp diff-switches)
-                                diff-switches
-                              (list diff-switches))))
-    (if (string= (vc-workfile-version file) "0")
-       ;; This file is added but not yet committed; there is no master file.
-       (if (or oldvers newvers)
-           (error "No revisions of %s exist" file)
-         ;; we regard this as "changed".
-         ;; diff it against /dev/null.
-          (apply 'vc-do-command t
-                 1 "diff" file
-                 (append diff-switches-list '("/dev/null"))))
-      (setq status
-            (apply 'vc-do-command t
-                   (if (vc-cvs-stay-local-p file) 'async 1)
-                   "cvs" file "diff"
-                   (and oldvers (concat "-r" oldvers))
-                   (and newvers (concat "-r" newvers))
-                   diff-switches-list))
-      (if (vc-cvs-stay-local-p file) 
-          1 ;; async diff, pessimistic assumption 
-        status))))
+(defun vc-cvs-responsible-p (file)
+  "Return non-nil if CVS thinks it is responsible for FILE."
+  (file-directory-p (expand-file-name "CVS"
+                                     (if (file-directory-p file)
+                                         file
+                                       (file-name-directory file)))))
 
-(defun vc-cvs-latest-on-branch-p (file)
-  "Return t iff current workfile version of FILE is the latest on its branch."
-  ;; Since this is only used as a sanity check for vc-cancel-version,
-  ;; and that is not supported under CVS at all, we can safely return t here.
-  ;; TODO: Think of getting rid of this altogether.
-  t)
+(defun vc-cvs-could-register (file)
+  "Return non-nil if FILE could be registered in CVS.
+This is only possible if CVS is responsible for FILE's directory."
+  (vc-cvs-responsible-p file))
 
 (defun vc-cvs-checkin (file rev comment)
   "CVS-specific version of `vc-backend-checkin'."
@@ -553,42 +292,6 @@ Inappropriate for CVS"
     ;; if this was an explicit check-in, remove the sticky tag
     (if rev (vc-do-command t 0 "cvs" file "update" "-A"))))
 
-(defun vc-cvs-responsible-p (file)
-  "Return non-nil if CVS thinks it is responsible for FILE."
-  (file-directory-p (expand-file-name "CVS"
-                                     (if (file-directory-p file)
-                                         file
-                                       (file-name-directory file)))))
-
-(defun vc-cvs-could-register (file)
-  "Return non-nil if FILE could be registered in CVS.
-This is only possible if CVS is responsible for FILE's directory."
-  (vc-cvs-responsible-p file))
-
-(defun vc-cvs-make-version-backups-p (file)
-  "Return non-nil if version backups should be made for FILE."
-  (vc-cvs-stay-local-p file))
-
-(defun vc-cvs-register (file &optional rev comment)
-  "Register FILE into the CVS version-control system.
-COMMENT can be used to provide an initial description of FILE.
-
-`vc-register-switches' and `vc-cvs-register-switches' are passed to
-the CVS command (in that order)."
-    (let ((switches (list
-                    (if (stringp vc-register-switches)
-                        (list vc-register-switches)
-                      vc-register-switches)
-                    (if (stringp vc-cvs-register-switches)
-                        (list vc-cvs-register-switches)
-                      vc-cvs-register-switches))))
-    
-      (apply 'vc-do-command nil 0 "cvs" file
-            "add"
-            (and comment (string-match "[^\t\n ]" comment)
-                 (concat "-m" comment))
-            switches)))
-
 (defun vc-cvs-checkout (file &optional writable rev workfile)
   "Retrieve a revision of FILE into a WORKFILE.
 WRITABLE non-nil means that the file should be writable.
@@ -670,17 +373,153 @@ REV is the revision to check out into WORKFILE."
        (vc-mode-line file)
        (message "Checking out %s...done" filename)))))
 
-(defun vc-cvs-annotate-command (file buffer &optional version)
-  "Execute \"cvs annotate\" on FILE, inserting the contents in BUFFER.
-Optional arg VERSION is a version to annotate from."
-  (vc-do-command buffer 0 "cvs" file "annotate" (if version
-                                                    (concat "-r" version))))
-
-(defvar vc-cvs-local-month-numbers
-  '(("Jan" . 1) ("Feb" .  2) ("Mar" .  3) ("Apr" .  4)
-    ("May" . 5) ("Jun" .  6) ("Jul" .  7) ("Aug" .  8)
-    ("Sep" . 9) ("Oct" . 10) ("Nov" . 11) ("Dec" . 12))
-  "Local association list of month numbers.")
+(defun vc-cvs-revert (file)
+  "Revert FILE to the version it was based on."
+  ;; Check out via standard output (caused by the final argument
+  ;; FILE below), so that no sticky tag is set.
+  (vc-cvs-checkout file nil (vc-workfile-version file) file)
+  ;; If "cvs edit" was used to make the file writable,
+  ;; call "cvs unedit" now to undo that.
+  (if (and (not (eq (vc-cvs-checkout-model file) 'implicit))
+           vc-cvs-use-edit)
+      (vc-do-command nil 0 "cvs" file "unedit")))
+
+(defun vc-cvs-merge (file first-version &optional second-version)
+  "Merge changes into current working copy of FILE.
+The changes are between FIRST-VERSION and SECOND-VERSION."
+  (vc-do-command nil 0 "cvs" file
+                 "update" "-kk"
+                 (concat "-j" first-version)
+                 (concat "-j" second-version))
+  (vc-file-setprop file 'vc-state 'edited)
+  (save-excursion
+    (set-buffer (get-buffer "*vc*"))
+    (goto-char (point-min))
+    (if (re-search-forward "conflicts during merge" nil t)
+        1                              ; signal error
+      0)))                             ; signal success
+
+(defun vc-cvs-merge-news (file)
+  "Merge in any new changes made to FILE."
+  (message "Merging changes into %s..." file)
+  (save-excursion
+    ;; (vc-file-setprop file 'vc-workfile-version nil)
+    (vc-file-setprop file 'vc-checkout-time 0)
+    (vc-do-command nil 0 "cvs" file "update")
+    ;; Analyze the merge result reported by CVS, and set
+    ;; file properties accordingly.
+    (set-buffer (get-buffer "*vc*"))
+    (goto-char (point-min))
+    ;; get new workfile version
+    (if (re-search-forward (concat "^Merging differences between "
+                                  "[01234567890.]* and "
+                                  "\\([01234567890.]*\\) into")
+                          nil t)
+       (vc-file-setprop file 'vc-workfile-version (match-string 1))
+      (vc-file-setprop file 'vc-workfile-version nil))
+    ;; get file status
+    (prog1
+        (if (eq (buffer-size) 0)
+            0 ;; there were no news; indicate success
+          (if (re-search-forward
+               (concat "^\\([CMUP] \\)?"
+                       (regexp-quote (file-name-nondirectory file))
+                       "\\( already contains the differences between \\)?")
+               nil t)
+              (cond
+               ;; Merge successful, we are in sync with repository now
+               ((or (match-string 2)
+                    (string= (match-string 1) "U ")
+                    (string= (match-string 1) "P "))
+                (vc-file-setprop file 'vc-state 'up-to-date)
+                (vc-file-setprop file 'vc-checkout-time
+                                 (nth 5 (file-attributes file)))
+                0);; indicate success to the caller
+               ;; Merge successful, but our own changes are still in the file
+               ((string= (match-string 1) "M ")
+                (vc-file-setprop file 'vc-state 'edited)
+                0);; indicate success to the caller
+               ;; Conflicts detected!
+               (t
+                (vc-file-setprop file 'vc-state 'edited)
+                1);; signal the error to the caller
+               )
+            (pop-to-buffer "*vc*")
+            (error "Couldn't analyze cvs update result")))
+      (message "Merging changes into %s...done" file))))
+
+\f
+;;;
+;;; History functions
+;;;
+
+(defun vc-cvs-print-log (file)
+  "Get change log associated with FILE."
+  (vc-do-command t (if (vc-cvs-stay-local-p file) 'async 0)
+                 "cvs" file "log"))
+
+(defun vc-cvs-show-log-entry (version)
+  (when (re-search-forward
+        ;; also match some context, for safety
+        (concat "----\nrevision " version
+                "\\(\tlocked by:.*\n\\|\n\\)date: ") nil t)
+    ;; set the display window so that
+    ;; the whole log entry is displayed
+    (let (start end lines)
+      (beginning-of-line) (forward-line -1) (setq start (point))
+      (if (not (re-search-forward "^----*\nrevision" nil t))
+         (setq end (point-max))
+       (beginning-of-line) (forward-line -1) (setq end (point)))
+      (setq lines (count-lines start end))
+      (cond
+       ;; if the global information and this log entry fit
+       ;; into the window, display from the beginning
+       ((< (count-lines (point-min) end) (window-height))
+       (goto-char (point-min))
+       (recenter 0)
+       (goto-char start))
+       ;; if the whole entry fits into the window,
+       ;; display it centered
+       ((< (1+ lines) (window-height))
+       (goto-char start)
+       (recenter (1- (- (/ (window-height) 2) (/ lines 2)))))
+       ;; otherwise (the entry is too large for the window),
+       ;; display from the start
+       (t
+       (goto-char start)
+       (recenter 0))))))
+
+(defun vc-cvs-diff (file &optional oldvers newvers)
+  "Get a difference report using CVS between two versions of FILE."
+  (let (options status
+        (diff-switches-list (if (listp diff-switches)
+                                diff-switches
+                              (list diff-switches))))
+    (if (string= (vc-workfile-version file) "0")
+       ;; This file is added but not yet committed; there is no master file.
+       (if (or oldvers newvers)
+           (error "No revisions of %s exist" file)
+         ;; we regard this as "changed".
+         ;; diff it against /dev/null.
+          (apply 'vc-do-command t
+                 1 "diff" file
+                 (append diff-switches-list '("/dev/null"))))
+      (setq status
+            (apply 'vc-do-command t
+                   (if (vc-cvs-stay-local-p file) 'async 1)
+                   "cvs" file "diff"
+                   (and oldvers (concat "-r" oldvers))
+                   (and newvers (concat "-r" newvers))
+                   diff-switches-list))
+      (if (vc-cvs-stay-local-p file) 
+          1 ;; async diff, pessimistic assumption 
+        status))))
+
+(defun vc-cvs-annotate-command (file buffer &optional version)
+  "Execute \"cvs annotate\" on FILE, inserting the contents in BUFFER.
+Optional arg VERSION is a version to annotate from."
+  (vc-do-command buffer 0 "cvs" file "annotate" (if version
+                                                    (concat "-r" version))))
 
 (defun vc-cvs-annotate-difference (point)
   "Return the difference between the time of the line and the current time.
@@ -709,6 +548,197 @@ Return values are as defined for `current-time'."
          (beginning-of-line nil)
          (vc-cvs-annotate-difference (point))))))
 
+\f
+;;;
+;;; Snapshot system
+;;;
+
+(defun vc-cvs-create-snapshot (dir name branchp)
+  "Assign to DIR's current version a given NAME.
+If BRANCHP is non-nil, the name is created as a branch (and the current
+workspace is immediately moved to that new branch)."
+  (vc-do-command nil 0 "cvs" dir "tag" "-c" (if branchp "-b") name)
+  (when branchp (vc-do-command nil 0 "cvs" dir "update" "-r" name)))
+
+(defun vc-cvs-retrieve-snapshot (dir name update)
+  "Retrieve a snapshot at and below DIR.
+NAME is the name of the snapshot; if it is empty, do a `cvs update'.
+If UPDATE is non-nil, then update (resynch) any affected buffers."
+  (with-current-buffer (get-buffer-create "*vc*")
+    (let ((default-directory dir))
+      (erase-buffer)
+      (if (or (not name) (string= name ""))
+         (vc-do-command t 0 "cvs" nil "update")
+       (vc-do-command t 0 "cvs" nil "update" "-r" name))
+      (when update
+       (goto-char (point-min))
+       (while (not (eobp))
+         (if (looking-at "\\([CMUP]\\) \\(.*\\)")
+             (let* ((file (expand-file-name (match-string 2) dir))
+                    (state (match-string 1))
+                    (buffer (find-buffer-visiting file)))
+               (when buffer
+                 (cond
+                  ((or (string= state "U")
+                       (string= state "P"))
+                   (vc-file-setprop file 'vc-state 'up-to-date)
+                   (vc-file-setprop file 'vc-workfile-version nil)
+                   (vc-file-setprop file 'vc-checkout-time
+                                    (nth 5 (file-attributes file))))
+                  ((or (string= state "M")
+                       (string= state "C"))
+                   (vc-file-setprop file 'vc-state 'edited)
+                   (vc-file-setprop file 'vc-workfile-version nil)
+                   (vc-file-setprop file 'vc-checkout-time 0)))
+                 (vc-resynch-buffer file t t))))
+         (forward-line 1))))))
+
+\f
+;;;
+;;; Miscellaneous
+;;;
+
+(defun vc-cvs-make-version-backups-p (file)
+  "Return non-nil if version backups should be made for FILE."
+  (vc-cvs-stay-local-p file))
+
+(defun vc-cvs-check-headers ()
+  "Check if the current file has any headers in it."
+  (save-excursion
+    (goto-char (point-min))
+    (re-search-forward "\\$[A-Za-z\300-\326\330-\366\370-\377]+\
+\\(: [\t -#%-\176\240-\377]*\\)?\\$" nil t)))
+
+\f
+;;;
+;;; Internal functions
+;;;
+
+(defun vc-cvs-stay-local-p (file)
+  "Return non-nil if VC should stay local when handling FILE."
+  (if vc-cvs-stay-local
+      (let* ((dirname (if (file-directory-p file)
+                         (directory-file-name file)
+                       (file-name-directory file)))
+            (prop
+             (or (vc-file-getprop dirname 'vc-cvs-stay-local-p)
+                 (let ((rootname (expand-file-name "CVS/Root" dirname)))
+                   (vc-file-setprop
+                    dirname 'vc-cvs-stay-local-p
+                    (when (file-readable-p rootname)
+                      (with-temp-buffer
+                        (vc-insert-file rootname)
+                        (goto-char (point-min))
+                        (if (looking-at "\\([^:]*\\):")
+                            (if (not (stringp vc-cvs-stay-local))
+                                'yes
+                              (let ((hostname (match-string 1)))
+                                (if (string-match vc-cvs-stay-local hostname)
+                                    'yes
+                                  'no)))
+                          'no))))))))
+       (if (eq prop 'yes) t nil))))
+
+(defun vc-cvs-parse-status (&optional full)
+  "Parse output of \"cvs status\" command in the current buffer.
+Set file properties accordingly.  Unless FULL is t, parse only
+essential information."
+  (let (file status)
+    (goto-char (point-min))
+    (if (re-search-forward "^File: " nil t)
+        (cond
+         ((looking-at "no file") nil)
+         ((re-search-forward "\\=\\([^ \t]+\\)" nil t)
+         (setq file (expand-file-name (match-string 1)))
+          (vc-file-setprop file 'vc-backend 'CVS)
+          (if (not (re-search-forward "\\=[ \t]+Status: \\(.*\\)" nil t))
+              (setq status "Unknown")
+            (setq status (match-string 1)))
+          (if (and full
+                   (re-search-forward
+                   "\\(RCS Version\\|RCS Revision\\|Repository revision\\):\
+\[\t ]+\\([0-9.]+\\)"
+                    nil t))
+              (vc-file-setprop file 'vc-latest-version (match-string 2)))
+          (cond
+           ((string-match "Up-to-date" status)
+            (vc-file-setprop file 'vc-checkout-time
+                             (nth 5 (file-attributes file)))
+            'up-to-date)
+           ((string-match "Locally Modified"    status) 'edited)
+          ((string-match "Needs Merge"         status) 'needs-merge)
+          ((string-match "Needs \\(Checkout\\|Patch\\)" status) 'needs-patch)
+          (t 'edited)))))))
+
+(defun vc-cvs-dir-state-heuristic (dir)
+  "Find the CVS state of all files in DIR, using only local information."
+  (with-temp-buffer
+    (vc-insert-file (expand-file-name "CVS/Entries" dir))
+    (goto-char (point-min))
+    (while (not (eobp))
+      (when (looking-at "/\\([^/]*\\)/")
+       (let ((file (expand-file-name (match-string 1) dir)))
+         (unless (vc-file-getprop file 'vc-state)
+           (vc-cvs-parse-entry file t))))
+      (forward-line 1))))
+
+(defun vc-cvs-parse-entry (file &optional set-state)
+  "Parse a line from CVS/Entries.
+Compare modification time to that of the FILE, set file properties
+accordingly.  However, `vc-state' is set only if optional arg SET-STATE
+is non-nil."
+  (cond
+   ;; entry for a "locally added" file (not yet committed)
+   ((looking-at "/[^/]+/0/")
+    (vc-file-setprop file 'vc-checkout-time 0)
+    (vc-file-setprop file 'vc-workfile-version "0")
+    (if set-state (vc-file-setprop file 'vc-state 'edited)))
+   ;; normal entry
+   ((looking-at
+     (concat "/[^/]+"
+            ;; revision
+            "/\\([^/]*\\)"
+            ;; timestamp
+            "/[A-Z][a-z][a-z]"       ;; week day (irrelevant)
+            " \\([A-Z][a-z][a-z]\\)" ;; month name
+            " *\\([0-9]*\\)"         ;; day of month
+            " \\([0-9]*\\):\\([0-9]*\\):\\([0-9]*\\)"  ;; hms
+            " \\([0-9]*\\)"          ;; year
+            ;; optional conflict field
+            "\\(+[^/]*\\)?/"))
+    (vc-file-setprop file 'vc-workfile-version (match-string 1))
+    ;; compare checkout time and modification time
+    (let ((second (string-to-number (match-string 6)))
+         (minute (string-to-number (match-string 5)))
+         (hour (string-to-number (match-string 4)))
+         (day (string-to-number (match-string 3)))
+         (year (string-to-number (match-string 7)))
+         (month (/ (string-match
+                    (match-string 2)
+                    "xxxJanFebMarAprMayJunJulAugSepOctNovDec")
+                   3))
+         (mtime (nth 5 (file-attributes file))))
+      (cond ((equal mtime
+                   (encode-time second minute hour day month year 0))
+            (vc-file-setprop file 'vc-checkout-time mtime)
+            (if set-state (vc-file-setprop file 'vc-state 'up-to-date)))
+           (t
+            (vc-file-setprop file 'vc-checkout-time 0)
+            (if set-state (vc-file-setprop file 'vc-state 'edited))))))
+   ;; entry with arbitrary text as timestamp
+   ;; (this means we should consider it modified)
+   ((looking-at
+     (concat "/[^/]+"
+            ;; revision
+            "/\\([^/]*\\)"
+            ;; timestamp (arbitrary text)
+            "/[^/]*"
+            ;; optional conflict field
+            "\\(+[^/]*\\)?/"))
+    (vc-file-setprop file 'vc-workfile-version (match-string 1))
+    (vc-file-setprop file 'vc-checkout-time 0)
+    (if set-state (vc-file-setprop file 'vc-state 'edited)))))
+           
 (provide 'vc-cvs)
 
 ;;; vc-cvs.el ends here
index 920fc4c1360b4d534316b0d3b959d226fbbd2189..35c09d6335f57e9acf087c9477b40567e2736fd9 100644 (file)
@@ -5,7 +5,7 @@
 ;; Author:     FSF (see vc.el for full credits)
 ;; Maintainer: Andre Spiegel <spiegel@gnu.org>
 
-;; $Id: vc-rcs.el,v 1.10 2000/10/03 11:33:59 spiegel Exp $
+;; $Id: vc-rcs.el,v 1.11 2000/10/03 12:08:40 spiegel Exp $
 
 ;; This file is part of GNU Emacs.
 
 
 ;;; Code:
 
+;;;
+;;; Customization options
+;;;
+
 (eval-when-compile
   (require 'cl))
 
@@ -99,6 +103,11 @@ For a description of possible values, see `vc-check-master-templates'."
   :version "21.1"
   :group 'vc)
 
+\f
+;;;
+;;; State-querying functions
+;;;
+
 ;;;###autoload
 (progn (defun vc-rcs-registered (f) (vc-default-registered 'RCS f)))
 
@@ -164,16 +173,6 @@ For a description of possible values, see `vc-check-master-templates'."
                    (vc-rcs-state file))))
         (vc-rcs-state file)))))
 
-(defun vc-rcs-workfile-is-newer (file)
-  "Return non-nil if FILE is newer than its RCS master.
-This likely means that FILE has been changed with respect
-to its master version."
-  (let ((file-time (nth 5 (file-attributes file)))
-       (master-time (nth 5 (file-attributes (vc-name file)))))
-    (or (> (nth 0 file-time) (nth 0 master-time))
-       (and (= (nth 0 file-time) (nth 0 master-time))
-            (> (nth 1 file-time) (nth 1 master-time))))))
-
 (defun vc-rcs-workfile-version (file)
   "RCS-specific version of `vc-workfile-version'."
   (or (and vc-consult-headers
@@ -183,6 +182,22 @@ to its master version."
         (vc-rcs-fetch-master-state file)
         (vc-file-getprop file 'vc-workfile-version))))
 
+(defun vc-rcs-latest-on-branch-p (file &optional version)
+  "Return non-nil if workfile version of FILE is the latest on its branch.
+When VERSION is given, perform check for that version."
+  (unless version (setq version (vc-workfile-version file)))
+  (with-temp-buffer
+    (string= version
+            (if (vc-rcs-trunk-p version)
+                (progn
+                  ;; Compare VERSION to the head version number.
+                  (vc-insert-file (vc-name file) "^[0-9]")
+                  (vc-parse-buffer "^head[ \t\n]+\\([^;]+\\);" 1))
+              ;; If we are not on the trunk, we need to examine the
+              ;; whole current branch.
+              (vc-insert-file (vc-name file) "^desc")
+              (vc-rcs-find-most-recent-rev (vc-rcs-branch-part version))))))
+
 (defun vc-rcs-checkout-model (file)
   "RCS-specific version of `vc-checkout-model'."
   (vc-rcs-consult-headers file)
@@ -190,189 +205,6 @@ to its master version."
       (progn (vc-rcs-fetch-master-state file)
             (vc-file-getprop file 'vc-checkout-model))))
 
-;;; internal code
-
-(defun vc-rcs-find-most-recent-rev (branch)
-  "Find most recent revision on BRANCH."
-  (goto-char (point-min))
-  (let ((latest-rev -1) value)
-    (while (re-search-forward (concat "^\\(" (regexp-quote branch)
-                                     "\\.\\([0-9]+\\)\\)\ndate[ \t]+[0-9.]+;")
-                             nil t)
-      (let ((rev (string-to-number (match-string 2))))
-       (when (< latest-rev rev)
-         (setq latest-rev rev)
-         (setq value (match-string 1)))))
-    (or value
-       (vc-rcs-branch-part branch))))
-
-(defun vc-rcs-fetch-master-state (file &optional workfile-version)
-  "Compute the master file's idea of the state of FILE.
-If a WORKFILE-VERSION is given, compute the state of that version,
-otherwise determine the workfile version based on the master file.
-This function sets the properties `vc-workfile-version' and
-`vc-checkout-model' to their correct values, based on the master
-file."
-  (with-temp-buffer
-    (vc-insert-file (vc-name file) "^[0-9]")
-    (let ((workfile-is-latest nil)
-         (default-branch (vc-parse-buffer "^branch[ \t\n]+\\([^;]*\\);" 1)))
-      (vc-file-setprop file 'vc-rcs-default-branch default-branch)
-      (unless workfile-version
-       ;; Workfile version not known yet.  Determine that first.  It
-       ;; is either the head of the trunk, the head of the default
-       ;; branch, or the "default branch" itself, if that is a full
-       ;; revision number.
-       (cond
-        ;; no default branch
-        ((or (not default-branch) (string= "" default-branch))
-         (setq workfile-version
-               (vc-parse-buffer "^head[ \t\n]+\\([^;]+\\);" 1))
-         (setq workfile-is-latest t))
-        ;; default branch is actually a revision
-        ((string-match "^[0-9]+\\.[0-9]+\\(\\.[0-9]+\\.[0-9]+\\)*$"
-                       default-branch)
-         (setq workfile-version default-branch))
-        ;; else, search for the head of the default branch
-        (t (vc-insert-file (vc-name file) "^desc")
-           (setq workfile-version
-                 (vc-rcs-find-most-recent-rev default-branch))
-           (setq workfile-is-latest t)))
-       (vc-file-setprop file 'vc-workfile-version workfile-version))
-      ;; Check strict locking
-      (goto-char (point-min))
-      (vc-file-setprop file 'vc-checkout-model
-                      (if (re-search-forward ";[ \t\n]*strict;" nil t)
-                          'locking 'implicit))
-      ;; Compute state of workfile version
-      (goto-char (point-min))
-      (let ((locking-user
-            (vc-parse-buffer (concat "^locks[ \t\n]+[^;]*[ \t\n]+\\([^:]+\\):"
-                                     (regexp-quote workfile-version)
-                                     "[^0-9.]")
-                             1)))
-       (cond
-        ;; not locked
-        ((not locking-user)
-          (if (or workfile-is-latest
-                  (vc-rcs-latest-on-branch-p file workfile-version))
-              ;; workfile version is latest on branch
-              (if (eq (vc-checkout-model file) 'locking)
-                 'up-to-date
-               (require 'vc)
-               (if (vc-workfile-unchanged-p file)
-                   'up-to-date
-                 'edited))
-            ;; workfile version is not latest on branch
-            'needs-patch))
-        ;; locked by the calling user
-        ((and (stringp locking-user)
-              (string= locking-user (vc-user-login-name)))
-         (if (or (eq (vc-checkout-model file) 'locking)
-                 workfile-is-latest
-                 (vc-rcs-latest-on-branch-p file workfile-version))
-             'edited
-           ;; Locking is not used for the file, but the owner does
-           ;; have a lock, and there is a higher version on the current
-           ;; branch.  Not sure if this can occur, and if it is right
-           ;; to use `needs-merge' in this case.
-           'needs-merge))
-        ;; locked by somebody else
-        ((stringp locking-user)
-         locking-user)
-        (t
-         (error "Error getting state of RCS file")))))))
-
-(defun vc-rcs-consult-headers (file)
-  "Search for RCS headers in FILE, and set properties accordingly.
-
-Returns: nil            if no headers were found
-         'rev           if a workfile revision was found
-         'rev-and-lock  if revision and lock info was found"
-  (cond
-   ((not (get-file-buffer file)) nil)
-   ((let (status version locking-user)
-     (save-excursion
-      (set-buffer (get-file-buffer file))
-      (goto-char (point-min))
-      (cond
-       ;; search for $Id or $Header
-       ;; -------------------------
-       ;; The `\ 's below avoid an RCS 5.7 bug when checking in this file.
-       ((or (and (search-forward "$Id\ : " nil t)
-                (looking-at "[^ ]+ \\([0-9.]+\\) "))
-           (and (progn (goto-char (point-min))
-                       (search-forward "$Header\ : " nil t))
-                (looking-at "[^ ]+ \\([0-9.]+\\) ")))
-       (goto-char (match-end 0))
-       ;; if found, store the revision number ...
-       (setq version (match-string-no-properties 1))
-       ;; ... and check for the locking state
-       (cond
-        ((looking-at
-          (concat "[0-9]+[/-][01][0-9][/-][0-3][0-9] "             ; date
-           "[0-2][0-9]:[0-5][0-9]+:[0-6][0-9]+\\([+-][0-9:]+\\)? " ; time
-                  "[^ ]+ [^ ]+ "))                       ; author & state
-         (goto-char (match-end 0)) ; [0-6] in regexp handles leap seconds
-         (cond
-          ;; unlocked revision
-          ((looking-at "\\$")
-           (setq locking-user 'none)
-           (setq status 'rev-and-lock))
-          ;; revision is locked by some user
-          ((looking-at "\\([^ ]+\\) \\$")
-           (setq locking-user (match-string-no-properties 1))
-           (setq status 'rev-and-lock))
-          ;; everything else: false
-          (nil)))
-        ;; unexpected information in
-        ;; keyword string --> quit
-        (nil)))
-       ;; search for $Revision
-       ;; --------------------
-       ((re-search-forward (concat "\\$"
-                                  "Revision: \\([0-9.]+\\) \\$")
-                          nil t)
-       ;; if found, store the revision number ...
-       (setq version (match-string-no-properties 1))
-       ;; and see if there's any lock information
-       (goto-char (point-min))
-       (if (re-search-forward (concat "\\$" "Locker:") nil t)
-           (cond ((looking-at " \\([^ ]+\\) \\$")
-                  (setq locking-user (match-string-no-properties 1))
-                  (setq status 'rev-and-lock))
-                 ((looking-at " *\\$")
-                  (setq locking-user 'none)
-                  (setq status 'rev-and-lock))
-                 (t
-                  (setq locking-user 'none)
-                  (setq status 'rev-and-lock)))
-         (setq status 'rev)))
-       ;; else: nothing found
-       ;; -------------------
-       (t nil)))
-     (if status (vc-file-setprop file 'vc-workfile-version version))
-     (and (eq status 'rev-and-lock)
-         (vc-file-setprop file 'vc-state
-                          (cond
-                           ((eq locking-user 'none) 'up-to-date)
-                           ((string= locking-user (vc-user-login-name)) 'edited)
-                           (t locking-user)))
-         ;; If the file has headers, we don't want to query the
-         ;; master file, because that would eliminate all the
-         ;; performance gain the headers brought us.  We therefore
-         ;; use a heuristic now to find out whether locking is used
-         ;; for this file.  If we trust the file permissions, and the
-         ;; file is not locked, then if the file is read-only we
-          ;; assume that locking is used for the file, otherwise
-          ;; locking is not used.
-         (not (vc-mistrust-permissions file))
-         (vc-up-to-date-p file)
-         (if (string-match ".r-..-..-." (nth 8 (file-attributes file)))
-             (vc-file-setprop file 'vc-checkout-model 'locking)
-           (vc-file-setprop file 'vc-checkout-model 'implicit)))
-     status))))
-
 (defun vc-rcs-workfile-unchanged-p (file)
   "RCS-specific implementation of vc-workfile-unchanged-p."
   ;; Try to use rcsdiff --brief.  If rcsdiff does not understand that,
@@ -390,283 +222,15 @@ Returns: nil            if no headers were found
     ;; The workfile is unchanged if rcsdiff found no differences.
     (zerop status)))
 
-(defun vc-rcs-trunk-p (rev)
-  "Return t if REV is an RCS revision on the trunk."
-  (not (eq nil (string-match "\\`[0-9]+\\.[0-9]+\\'" rev))))
-
-(defun vc-rcs-branch-part (rev)
-  "Return the branch part of an RCS revision number REV"
-  (substring rev 0 (string-match "\\.[0-9]+\\'" rev)))
-
-(defun vc-rcs-latest-on-branch-p (file &optional version)
-  "Return non-nil if workfile version of FILE is the latest on its branch.
-When VERSION is given, perform check for that version."
-  (unless version (setq version (vc-workfile-version file)))
-  (with-temp-buffer
-    (string= version
-            (if (vc-rcs-trunk-p version)
-                (progn
-                  ;; Compare VERSION to the head version number.
-                  (vc-insert-file (vc-name file) "^[0-9]")
-                  (vc-parse-buffer "^head[ \t\n]+\\([^;]+\\);" 1))
-              ;; If we are not on the trunk, we need to examine the
-              ;; whole current branch.
-              (vc-insert-file (vc-name file) "^desc")
-              (vc-rcs-find-most-recent-rev (vc-rcs-branch-part version))))))
 \f
-(defun vc-rcs-branch-p (rev)
-  "Return t if REV is an RCS branch revision"
-  (not (eq nil (string-match "\\`[0-9]+\\(\\.[0-9]+\\.[0-9]+\\)*\\'" rev))))
+;;;
+;;; State-changing functions
+;;;
 
-(defun vc-rcs-minor-part (rev)
-  "Return the minor version number of an RCS revision number REV."
-  (string-match "[0-9]+\\'" rev)
-  (substring rev (match-beginning 0) (match-end 0)))
-
-(defun vc-rcs-previous-version (rev)
-  "Guess the previous RCS version number"
-  (let ((branch (vc-rcs-branch-part rev))
-        (minor-num (string-to-number (vc-rcs-minor-part rev))))
-    (if (> minor-num 1)
-        ;; version does probably not start a branch or release
-        (concat branch "." (number-to-string (1- minor-num)))
-      (if (vc-rcs-trunk-p rev)
-          ;; we are at the beginning of the trunk --
-          ;; don't know anything to return here
-          ""
-        ;; we are at the beginning of a branch --
-        ;; return version of starting point
-        (vc-rcs-branch-part branch)))))
-
-(defun vc-rcs-print-log (file)
-  "Get change log associated with FILE."
-  (vc-do-command t 0 "rlog" (vc-name file)))
-
-(defun vc-rcs-show-log-entry (version)
-  (when (re-search-forward
-        ;; also match some context, for safety
-        (concat "----\nrevision " version
-                "\\(\tlocked by:.*\n\\|\n\\)date: ") nil t)
-    ;; set the display window so that
-    ;; the whole log entry is displayed
-    (let (start end lines)
-      (beginning-of-line) (forward-line -1) (setq start (point))
-      (if (not (re-search-forward "^----*\nrevision" nil t))
-         (setq end (point-max))
-       (beginning-of-line) (forward-line -1) (setq end (point)))
-      (setq lines (count-lines start end))
-      (cond
-       ;; if the global information and this log entry fit
-       ;; into the window, display from the beginning
-       ((< (count-lines (point-min) end) (window-height))
-       (goto-char (point-min))
-       (recenter 0)
-       (goto-char start))
-       ;; if the whole entry fits into the window,
-       ;; display it centered
-       ((< (1+ lines) (window-height))
-       (goto-char start)
-       (recenter (1- (- (/ (window-height) 2) (/ lines 2)))))
-       ;; otherwise (the entry is too large for the window),
-       ;; display from the start
-       (t
-       (goto-char start)
-       (recenter 0))))))
-
-(defun vc-rcs-assign-name (file name)
-  "Assign to FILE's latest version a given NAME."
-  (vc-do-command nil 0 "rcs" (vc-name file) (concat "-n" name ":")))
-
-(defun vc-rcs-merge (file first-version &optional second-version)
-  "Merge changes into current working copy of FILE.
-The changes are between FIRST-VERSION and SECOND-VERSION."
-  (vc-do-command nil 1 "rcsmerge" (vc-name file)
-                "-kk"                  ; ignore keyword conflicts
-                (concat "-r" first-version)
-                (if second-version (concat "-r" second-version))))
-
-(defun vc-rcs-check-headers ()
-  "Check if the current file has any headers in it."
-  (save-excursion
-    (goto-char (point-min))
-         (re-search-forward "\\$[A-Za-z\300-\326\330-\366\370-\377]+\
-\\(: [\t -#%-\176\240-\377]*\\)?\\$" nil t)))
-
-(defun vc-rcs-clear-headers ()
-  "Implementation of vc-clear-headers for RCS."
-  (let ((case-fold-search nil))
-    (goto-char (point-min))
-    (while (re-search-forward
-            (concat "\\$\\(Author\\|Date\\|Header\\|Id\\|Locker\\|Name\\|"
-                    "RCSfile\\|Revision\\|Source\\|State\\): [^$\n]+\\$")
-            nil t)
-      (replace-match "$\\1$"))))
-
-(defun vc-rcs-steal-lock (file &optional rev)
-  "Steal the lock on the current workfile for FILE and revision REV.
-Needs RCS 5.6.2 or later for -M."
-  (vc-do-command nil 0 "rcs" (vc-name file) "-M"
-                (concat "-u" rev) (concat "-l" rev)))
-
-(defun vc-rcs-cancel-version (file writable)
-  "Undo the most recent checkin of FILE.
-WRITABLE non-nil means previous version should be locked."
-  (let* ((target (vc-workfile-version file))
-        (previous (if (vc-trunk-p target) "" (vc-branch-part target)))
-        (config (current-window-configuration))
-        (done nil))
-    (vc-do-command nil 0 "rcs" (vc-name file) (concat "-o" target))
-    ;; Check out the most recent remaining version.  If it fails, because
-    ;; the whole branch got deleted, do a double-take and check out the
-    ;; version where the branch started.
-    (while (not done)
-      (condition-case err
-         (progn
-           (vc-do-command nil 0 "co" (vc-name file) "-f"
-                          (concat (if writable "-l" "-u") previous))
-           (setq done t))
-       (error (set-buffer "*vc*")
-              (goto-char (point-min))
-              (if (search-forward "no side branches present for" nil t)
-                  (progn (setq previous (vc-branch-part previous))
-                         (vc-rcs-set-default-branch file previous)
-                         ;; vc-do-command popped up a window with
-                         ;; the error message.  Get rid of it, by
-                         ;; restoring the old window configuration.
-                         (set-window-configuration config))
-                ;; No, it was some other error: re-signal it.
-                (signal (car err) (cdr err))))))))
-
-(defun vc-rcs-revert (file)
-  "Revert FILE to the version it was based on."
-  (vc-do-command nil 0 "co" (vc-name file) "-f"
-                (concat "-u" (vc-workfile-version file))))
-
-(defun vc-rcs-rename-file (old new)
-  ;; Just move the master file (using vc-rcs-master-templates).
-  (vc-rename-master (vc-name old) new vc-rcs-master-templates))
-
-(defun vc-release-greater-or-equal (r1 r2)
-  "Compare release numbers, represented as strings.
-Release components are assumed cardinal numbers, not decimal fractions
-\(5.10 is a higher release than 5.9\).  Omitted fields are considered
-lower \(5.6.7 is earlier than 5.6.7.1\).  Comparison runs till the end
-of the string is found, or a non-numeric component shows up \(5.6.7 is
-earlier than \"5.6.7 beta\", which is probably not what you want in
-some cases\).  This code is suitable for existing RCS release numbers.
-CVS releases are handled reasonably, too \(1.3 < 1.4* < 1.5\)."
-  (let (v1 v2 i1 i2)
-    (catch 'done
-      (or (and (string-match "^\\.?\\([0-9]+\\)" r1)
-              (setq i1 (match-end 0))
-              (setq v1 (string-to-number (match-string 1 r1)))
-              (or (and (string-match "^\\.?\\([0-9]+\\)" r2)
-                       (setq i2 (match-end 0))
-                       (setq v2 (string-to-number (match-string 1 r2)))
-                       (if (> v1 v2) (throw 'done t)
-                         (if (< v1 v2) (throw 'done nil)
-                           (throw 'done
-                                  (vc-release-greater-or-equal
-                                   (substring r1 i1)
-                                   (substring r2 i2)))))))
-                  (throw 'done t)))
-         (or (and (string-match "^\\.?\\([0-9]+\\)" r2)
-                  (throw 'done nil))
-             (throw 'done t)))))
-
-(defun vc-rcs-release-p (release)
-  "Return t if we have RELEASE or better."
-  (let ((installation (vc-rcs-system-release)))
-    (if (and installation
-            (not (eq installation 'unknown)))
-       (vc-release-greater-or-equal installation release))))
-
-(defun vc-rcs-checkin (file rev comment)
-  "RCS-specific version of `vc-backend-checkin'."
-  (let ((switches (if (stringp vc-checkin-switches)
-                     (list vc-checkin-switches)
-                   vc-checkin-switches)))
-    (let ((old-version (vc-workfile-version file)) new-version
-         (default-branch (vc-file-getprop file 'vc-rcs-default-branch)))
-      ;; Force branch creation if an appropriate 
-      ;; default branch has been set.
-      (and (not rev)
-          default-branch
-          (string-match (concat "^" (regexp-quote old-version) "\\.")
-                        default-branch)
-          (setq rev default-branch)
-          (setq switches (cons "-f" switches)))
-      (apply 'vc-do-command nil 0 "ci" (vc-name file)
-            ;; if available, use the secure check-in option
-            (and (vc-rcs-release-p "5.6.4") "-j")
-            (concat (if vc-keep-workfiles "-u" "-r") rev)
-            (concat "-m" comment)
-            switches)
-      (vc-file-setprop file 'vc-workfile-version nil)
-
-      ;; determine the new workfile version
-      (set-buffer "*vc*")
-      (goto-char (point-min))
-      (when (or (re-search-forward
-                "new revision: \\([0-9.]+\\);" nil t)
-               (re-search-forward
-                "reverting to previous revision \\([0-9.]+\\)" nil t))
-       (setq new-version (match-string 1))
-       (vc-file-setprop file 'vc-workfile-version new-version))
-
-      ;; if we got to a different branch, adjust the default
-      ;; branch accordingly
-      (cond
-       ((and old-version new-version
-            (not (string= (vc-rcs-branch-part old-version)
-                          (vc-rcs-branch-part new-version))))
-       (vc-rcs-set-default-branch file 
-                                  (if (vc-rcs-trunk-p new-version) nil
-                                    (vc-rcs-branch-part new-version)))
-       ;; If this is an old RCS release, we might have
-       ;; to remove a remaining lock.
-       (if (not (vc-rcs-release-p "5.6.2"))
-           ;; exit status of 1 is also accepted.
-           ;; It means that the lock was removed before.
-           (vc-do-command nil 1 "rcs" (vc-name file)
-                          (concat "-u" old-version))))))))
-
-(defun vc-rcs-system-release ()
-  "Return the RCS release installed on this system, as a string.
-Return symbol UNKNOWN if the release cannot be deducted.  The user can
-override this using variable `vc-rcs-release'.
-
-If the user has not set variable `vc-rcs-release' and it is nil,
-variable `vc-rcs-release' is set to the returned value."
-  (or vc-rcs-release
-      (setq vc-rcs-release
-           (or (and (zerop (vc-do-command nil nil "rcs" nil "-V"))
-                    (with-current-buffer (get-buffer "*vc*")
-                      (vc-parse-buffer "^RCS version \\([0-9.]+ *.*\\)" 1)))
-               'unknown))))
-
-(defun vc-rcs-diff (file &optional oldvers newvers)
-  "Get a difference report using RCS between two versions of FILE."
-  (if (not oldvers) (setq oldvers (vc-workfile-version file)))
-  ;; If we know that --brief is not supported, don't try it.
-  (let* ((diff-switches-list (if (listp diff-switches)
-                                diff-switches
-                              (list diff-switches)))
-        (options (append (list "-q"
-                               (concat "-r" oldvers)
-                               (and newvers (concat "-r" newvers)))
-                         diff-switches-list)))
-    (apply 'vc-do-command t 1 "rcsdiff" file options)))
-
-(defun vc-rcs-responsible-p (file)
-  "Return non-nil if RCS thinks it would be responsible for registering FILE."
-  ;; TODO: check for all the patterns in vc-rcs-master-templates
-  (file-directory-p (expand-file-name "RCS" (file-name-directory file))))
-
-(defun vc-rcs-register (file &optional rev comment)
-  "Register FILE into the RCS version-control system.
-REV is the optional revision number for the file.  COMMENT can be used
-to provide an initial description of FILE.
+(defun vc-rcs-register (file &optional rev comment)
+  "Register FILE into the RCS version-control system.
+REV is the optional revision number for the file.  COMMENT can be used
+to provide an initial description of FILE.
 
 `vc-register-switches' and `vc-rcs-register-switches' are passed to
 the RCS command (in that order).
@@ -716,6 +280,19 @@ expanded if `vc-keep-workfiles' is non-nil, otherwise, delete the workfile."
                               nil t)
                              (match-string 1))))))
 
+(defun vc-rcs-responsible-p (file)
+  "Return non-nil if RCS thinks it would be responsible for registering FILE."
+  ;; TODO: check for all the patterns in vc-rcs-master-templates
+  (file-directory-p (expand-file-name "RCS" (file-name-directory file))))
+
+(defun vc-rcs-receive-file (file rev)
+  "Implementation of receive-file for RCS."
+  (let ((checkout-model (vc-checkout-model file)))
+    (vc-rcs-register file rev "")
+    (when (eq checkout-model 'implicit)
+      (vc-rcs-set-non-strict-locking file))
+    (vc-rcs-set-default-branch file (concat rev ".1"))))
+
 (defun vc-rcs-unregister (file)
   "Unregister FILE from RCS.
 If this leaves the RCS subdirectory empty, ask the user
@@ -735,22 +312,55 @@ whether to remove it."
         (yes-or-no-p (format "Directory %s is empty; remove it? " dir))
         (delete-directory dir))))
 
-(defun vc-rcs-receive-file (file rev)
-  "Implementation of receive-file for RCS."
-  (let ((checkout-model (vc-checkout-model file)))
-    (vc-rcs-register file rev "")
-    (when (eq checkout-model 'implicit)
-      (vc-rcs-set-non-strict-locking file))
-    (vc-rcs-set-default-branch file (concat rev ".1"))))
-
-(defun vc-rcs-set-non-strict-locking (file)
-  (vc-do-command nil 0 "rcs" file "-U")
-  (vc-file-setprop file 'vc-checkout-model 'implicit)
-  (set-file-modes file (logior (file-modes file) 128)))
+(defun vc-rcs-checkin (file rev comment)
+  "RCS-specific version of `vc-backend-checkin'."
+  (let ((switches (if (stringp vc-checkin-switches)
+                     (list vc-checkin-switches)
+                   vc-checkin-switches)))
+    (let ((old-version (vc-workfile-version file)) new-version
+         (default-branch (vc-file-getprop file 'vc-rcs-default-branch)))
+      ;; Force branch creation if an appropriate 
+      ;; default branch has been set.
+      (and (not rev)
+          default-branch
+          (string-match (concat "^" (regexp-quote old-version) "\\.")
+                        default-branch)
+          (setq rev default-branch)
+          (setq switches (cons "-f" switches)))
+      (apply 'vc-do-command nil 0 "ci" (vc-name file)
+            ;; if available, use the secure check-in option
+            (and (vc-rcs-release-p "5.6.4") "-j")
+            (concat (if vc-keep-workfiles "-u" "-r") rev)
+            (concat "-m" comment)
+            switches)
+      (vc-file-setprop file 'vc-workfile-version nil)
 
-(defun vc-rcs-set-default-branch (file branch)
-  (vc-do-command nil 0 "rcs" (vc-name file) (concat "-b" branch))
-  (vc-file-setprop file 'vc-rcs-default-branch branch))
+      ;; determine the new workfile version
+      (set-buffer "*vc*")
+      (goto-char (point-min))
+      (when (or (re-search-forward
+                "new revision: \\([0-9.]+\\);" nil t)
+               (re-search-forward
+                "reverting to previous revision \\([0-9.]+\\)" nil t))
+       (setq new-version (match-string 1))
+       (vc-file-setprop file 'vc-workfile-version new-version))
+
+      ;; if we got to a different branch, adjust the default
+      ;; branch accordingly
+      (cond
+       ((and old-version new-version
+            (not (string= (vc-rcs-branch-part old-version)
+                          (vc-rcs-branch-part new-version))))
+       (vc-rcs-set-default-branch file 
+                                  (if (vc-rcs-trunk-p new-version) nil
+                                    (vc-rcs-branch-part new-version)))
+       ;; If this is an old RCS release, we might have
+       ;; to remove a remaining lock.
+       (if (not (vc-rcs-release-p "5.6.2"))
+           ;; exit status of 1 is also accepted.
+           ;; It means that the lock was removed before.
+           (vc-do-command nil 1 "rcs" (vc-name file)
+                          (concat "-u" old-version))))))))
 
 (defun vc-rcs-checkout (file &optional writable rev workfile)
   "Retrieve a copy of a saved version of FILE into a workfile."
@@ -829,6 +439,430 @@ whether to remove it."
                      new-version))))))
        (message "Checking out %s...done" filename)))))
 
+(defun vc-rcs-revert (file)
+  "Revert FILE to the version it was based on."
+  (vc-do-command nil 0 "co" (vc-name file) "-f"
+                (concat "-u" (vc-workfile-version file))))
+
+(defun vc-rcs-cancel-version (file writable)
+  "Undo the most recent checkin of FILE.
+WRITABLE non-nil means previous version should be locked."
+  (let* ((target (vc-workfile-version file))
+        (previous (if (vc-trunk-p target) "" (vc-branch-part target)))
+        (config (current-window-configuration))
+        (done nil))
+    (vc-do-command nil 0 "rcs" (vc-name file) (concat "-o" target))
+    ;; Check out the most recent remaining version.  If it fails, because
+    ;; the whole branch got deleted, do a double-take and check out the
+    ;; version where the branch started.
+    (while (not done)
+      (condition-case err
+         (progn
+           (vc-do-command nil 0 "co" (vc-name file) "-f"
+                          (concat (if writable "-l" "-u") previous))
+           (setq done t))
+       (error (set-buffer "*vc*")
+              (goto-char (point-min))
+              (if (search-forward "no side branches present for" nil t)
+                  (progn (setq previous (vc-branch-part previous))
+                         (vc-rcs-set-default-branch file previous)
+                         ;; vc-do-command popped up a window with
+                         ;; the error message.  Get rid of it, by
+                         ;; restoring the old window configuration.
+                         (set-window-configuration config))
+                ;; No, it was some other error: re-signal it.
+                (signal (car err) (cdr err))))))))
+
+(defun vc-rcs-merge (file first-version &optional second-version)
+  "Merge changes into current working copy of FILE.
+The changes are between FIRST-VERSION and SECOND-VERSION."
+  (vc-do-command nil 1 "rcsmerge" (vc-name file)
+                "-kk"                  ; ignore keyword conflicts
+                (concat "-r" first-version)
+                (if second-version (concat "-r" second-version))))
+
+(defun vc-rcs-steal-lock (file &optional rev)
+  "Steal the lock on the current workfile for FILE and revision REV.
+Needs RCS 5.6.2 or later for -M."
+  (vc-do-command nil 0 "rcs" (vc-name file) "-M"
+                (concat "-u" rev) (concat "-l" rev)))
+
+
+\f
+;;;
+;;; History functions
+;;;
+
+(defun vc-rcs-print-log (file)
+  "Get change log associated with FILE."
+  (vc-do-command t 0 "rlog" (vc-name file)))
+
+(defun vc-rcs-show-log-entry (version)
+  (when (re-search-forward
+        ;; also match some context, for safety
+        (concat "----\nrevision " version
+                "\\(\tlocked by:.*\n\\|\n\\)date: ") nil t)
+    ;; set the display window so that
+    ;; the whole log entry is displayed
+    (let (start end lines)
+      (beginning-of-line) (forward-line -1) (setq start (point))
+      (if (not (re-search-forward "^----*\nrevision" nil t))
+         (setq end (point-max))
+       (beginning-of-line) (forward-line -1) (setq end (point)))
+      (setq lines (count-lines start end))
+      (cond
+       ;; if the global information and this log entry fit
+       ;; into the window, display from the beginning
+       ((< (count-lines (point-min) end) (window-height))
+       (goto-char (point-min))
+       (recenter 0)
+       (goto-char start))
+       ;; if the whole entry fits into the window,
+       ;; display it centered
+       ((< (1+ lines) (window-height))
+       (goto-char start)
+       (recenter (1- (- (/ (window-height) 2) (/ lines 2)))))
+       ;; otherwise (the entry is too large for the window),
+       ;; display from the start
+       (t
+       (goto-char start)
+       (recenter 0))))))
+
+(defun vc-rcs-diff (file &optional oldvers newvers)
+  "Get a difference report using RCS between two versions of FILE."
+  (if (not oldvers) (setq oldvers (vc-workfile-version file)))
+  ;; If we know that --brief is not supported, don't try it.
+  (let* ((diff-switches-list (if (listp diff-switches)
+                                diff-switches
+                              (list diff-switches)))
+        (options (append (list "-q"
+                               (concat "-r" oldvers)
+                               (and newvers (concat "-r" newvers)))
+                         diff-switches-list)))
+    (apply 'vc-do-command t 1 "rcsdiff" file options)))
+
+\f
+;;;
+;;; Snapshot system
+;;;
+
+(defun vc-rcs-assign-name (file name)
+  "Assign to FILE's latest version a given NAME."
+  (vc-do-command nil 0 "rcs" (vc-name file) (concat "-n" name ":")))
+
+\f
+;;;
+;;; Miscellaneous
+;;;
+
+(defun vc-rcs-check-headers ()
+  "Check if the current file has any headers in it."
+  (save-excursion
+    (goto-char (point-min))
+         (re-search-forward "\\$[A-Za-z\300-\326\330-\366\370-\377]+\
+\\(: [\t -#%-\176\240-\377]*\\)?\\$" nil t)))
+
+(defun vc-rcs-clear-headers ()
+  "Implementation of vc-clear-headers for RCS."
+  (let ((case-fold-search nil))
+    (goto-char (point-min))
+    (while (re-search-forward
+            (concat "\\$\\(Author\\|Date\\|Header\\|Id\\|Locker\\|Name\\|"
+                    "RCSfile\\|Revision\\|Source\\|State\\): [^$\n]+\\$")
+            nil t)
+      (replace-match "$\\1$"))))
+
+(defun vc-rcs-rename-file (old new)
+  ;; Just move the master file (using vc-rcs-master-templates).
+  (vc-rename-master (vc-name old) new vc-rcs-master-templates))
+
+\f
+;;;
+;;; Internal functions
+;;;
+
+(defun vc-rcs-trunk-p (rev)
+  "Return t if REV is an RCS revision on the trunk."
+  (not (eq nil (string-match "\\`[0-9]+\\.[0-9]+\\'" rev))))
+
+(defun vc-rcs-branch-part (rev)
+  "Return the branch part of an RCS revision number REV"
+  (substring rev 0 (string-match "\\.[0-9]+\\'" rev)))
+
+(defun vc-rcs-branch-p (rev)
+  "Return t if REV is an RCS branch revision"
+  (not (eq nil (string-match "\\`[0-9]+\\(\\.[0-9]+\\.[0-9]+\\)*\\'" rev))))
+
+(defun vc-rcs-minor-part (rev)
+  "Return the minor version number of an RCS revision number REV."
+  (string-match "[0-9]+\\'" rev)
+  (substring rev (match-beginning 0) (match-end 0)))
+
+(defun vc-rcs-previous-version (rev)
+  "Guess the previous RCS version number"
+  (let ((branch (vc-rcs-branch-part rev))
+        (minor-num (string-to-number (vc-rcs-minor-part rev))))
+    (if (> minor-num 1)
+        ;; version does probably not start a branch or release
+        (concat branch "." (number-to-string (1- minor-num)))
+      (if (vc-rcs-trunk-p rev)
+          ;; we are at the beginning of the trunk --
+          ;; don't know anything to return here
+          ""
+        ;; we are at the beginning of a branch --
+        ;; return version of starting point
+        (vc-rcs-branch-part branch)))))
+
+(defun vc-rcs-workfile-is-newer (file)
+  "Return non-nil if FILE is newer than its RCS master.
+This likely means that FILE has been changed with respect
+to its master version."
+  (let ((file-time (nth 5 (file-attributes file)))
+       (master-time (nth 5 (file-attributes (vc-name file)))))
+    (or (> (nth 0 file-time) (nth 0 master-time))
+       (and (= (nth 0 file-time) (nth 0 master-time))
+            (> (nth 1 file-time) (nth 1 master-time))))))
+
+(defun vc-rcs-find-most-recent-rev (branch)
+  "Find most recent revision on BRANCH."
+  (goto-char (point-min))
+  (let ((latest-rev -1) value)
+    (while (re-search-forward (concat "^\\(" (regexp-quote branch)
+                                     "\\.\\([0-9]+\\)\\)\ndate[ \t]+[0-9.]+;")
+                             nil t)
+      (let ((rev (string-to-number (match-string 2))))
+       (when (< latest-rev rev)
+         (setq latest-rev rev)
+         (setq value (match-string 1)))))
+    (or value
+       (vc-rcs-branch-part branch))))
+
+(defun vc-rcs-fetch-master-state (file &optional workfile-version)
+  "Compute the master file's idea of the state of FILE.
+If a WORKFILE-VERSION is given, compute the state of that version,
+otherwise determine the workfile version based on the master file.
+This function sets the properties `vc-workfile-version' and
+`vc-checkout-model' to their correct values, based on the master
+file."
+  (with-temp-buffer
+    (vc-insert-file (vc-name file) "^[0-9]")
+    (let ((workfile-is-latest nil)
+         (default-branch (vc-parse-buffer "^branch[ \t\n]+\\([^;]*\\);" 1)))
+      (vc-file-setprop file 'vc-rcs-default-branch default-branch)
+      (unless workfile-version
+       ;; Workfile version not known yet.  Determine that first.  It
+       ;; is either the head of the trunk, the head of the default
+       ;; branch, or the "default branch" itself, if that is a full
+       ;; revision number.
+       (cond
+        ;; no default branch
+        ((or (not default-branch) (string= "" default-branch))
+         (setq workfile-version
+               (vc-parse-buffer "^head[ \t\n]+\\([^;]+\\);" 1))
+         (setq workfile-is-latest t))
+        ;; default branch is actually a revision
+        ((string-match "^[0-9]+\\.[0-9]+\\(\\.[0-9]+\\.[0-9]+\\)*$"
+                       default-branch)
+         (setq workfile-version default-branch))
+        ;; else, search for the head of the default branch
+        (t (vc-insert-file (vc-name file) "^desc")
+           (setq workfile-version
+                 (vc-rcs-find-most-recent-rev default-branch))
+           (setq workfile-is-latest t)))
+       (vc-file-setprop file 'vc-workfile-version workfile-version))
+      ;; Check strict locking
+      (goto-char (point-min))
+      (vc-file-setprop file 'vc-checkout-model
+                      (if (re-search-forward ";[ \t\n]*strict;" nil t)
+                          'locking 'implicit))
+      ;; Compute state of workfile version
+      (goto-char (point-min))
+      (let ((locking-user
+            (vc-parse-buffer (concat "^locks[ \t\n]+[^;]*[ \t\n]+\\([^:]+\\):"
+                                     (regexp-quote workfile-version)
+                                     "[^0-9.]")
+                             1)))
+       (cond
+        ;; not locked
+        ((not locking-user)
+          (if (or workfile-is-latest
+                  (vc-rcs-latest-on-branch-p file workfile-version))
+              ;; workfile version is latest on branch
+              (if (eq (vc-checkout-model file) 'locking)
+                 'up-to-date
+               (require 'vc)
+               (if (vc-workfile-unchanged-p file)
+                   'up-to-date
+                 'edited))
+            ;; workfile version is not latest on branch
+            'needs-patch))
+        ;; locked by the calling user
+        ((and (stringp locking-user)
+              (string= locking-user (vc-user-login-name)))
+         (if (or (eq (vc-checkout-model file) 'locking)
+                 workfile-is-latest
+                 (vc-rcs-latest-on-branch-p file workfile-version))
+             'edited
+           ;; Locking is not used for the file, but the owner does
+           ;; have a lock, and there is a higher version on the current
+           ;; branch.  Not sure if this can occur, and if it is right
+           ;; to use `needs-merge' in this case.
+           'needs-merge))
+        ;; locked by somebody else
+        ((stringp locking-user)
+         locking-user)
+        (t
+         (error "Error getting state of RCS file")))))))
+
+(defun vc-rcs-consult-headers (file)
+  "Search for RCS headers in FILE, and set properties accordingly.
+
+Returns: nil            if no headers were found
+         'rev           if a workfile revision was found
+         'rev-and-lock  if revision and lock info was found"
+  (cond
+   ((not (get-file-buffer file)) nil)
+   ((let (status version locking-user)
+     (save-excursion
+      (set-buffer (get-file-buffer file))
+      (goto-char (point-min))
+      (cond
+       ;; search for $Id or $Header
+       ;; -------------------------
+       ;; The `\ 's below avoid an RCS 5.7 bug when checking in this file.
+       ((or (and (search-forward "$Id\ : " nil t)
+                (looking-at "[^ ]+ \\([0-9.]+\\) "))
+           (and (progn (goto-char (point-min))
+                       (search-forward "$Header\ : " nil t))
+                (looking-at "[^ ]+ \\([0-9.]+\\) ")))
+       (goto-char (match-end 0))
+       ;; if found, store the revision number ...
+       (setq version (match-string-no-properties 1))
+       ;; ... and check for the locking state
+       (cond
+        ((looking-at
+          (concat "[0-9]+[/-][01][0-9][/-][0-3][0-9] "             ; date
+           "[0-2][0-9]:[0-5][0-9]+:[0-6][0-9]+\\([+-][0-9:]+\\)? " ; time
+                  "[^ ]+ [^ ]+ "))                       ; author & state
+         (goto-char (match-end 0)) ; [0-6] in regexp handles leap seconds
+         (cond
+          ;; unlocked revision
+          ((looking-at "\\$")
+           (setq locking-user 'none)
+           (setq status 'rev-and-lock))
+          ;; revision is locked by some user
+          ((looking-at "\\([^ ]+\\) \\$")
+           (setq locking-user (match-string-no-properties 1))
+           (setq status 'rev-and-lock))
+          ;; everything else: false
+          (nil)))
+        ;; unexpected information in
+        ;; keyword string --> quit
+        (nil)))
+       ;; search for $Revision
+       ;; --------------------
+       ((re-search-forward (concat "\\$"
+                                  "Revision: \\([0-9.]+\\) \\$")
+                          nil t)
+       ;; if found, store the revision number ...
+       (setq version (match-string-no-properties 1))
+       ;; and see if there's any lock information
+       (goto-char (point-min))
+       (if (re-search-forward (concat "\\$" "Locker:") nil t)
+           (cond ((looking-at " \\([^ ]+\\) \\$")
+                  (setq locking-user (match-string-no-properties 1))
+                  (setq status 'rev-and-lock))
+                 ((looking-at " *\\$")
+                  (setq locking-user 'none)
+                  (setq status 'rev-and-lock))
+                 (t
+                  (setq locking-user 'none)
+                  (setq status 'rev-and-lock)))
+         (setq status 'rev)))
+       ;; else: nothing found
+       ;; -------------------
+       (t nil)))
+     (if status (vc-file-setprop file 'vc-workfile-version version))
+     (and (eq status 'rev-and-lock)
+         (vc-file-setprop file 'vc-state
+                          (cond
+                           ((eq locking-user 'none) 'up-to-date)
+                           ((string= locking-user (vc-user-login-name)) 'edited)
+                           (t locking-user)))
+         ;; If the file has headers, we don't want to query the
+         ;; master file, because that would eliminate all the
+         ;; performance gain the headers brought us.  We therefore
+         ;; use a heuristic now to find out whether locking is used
+         ;; for this file.  If we trust the file permissions, and the
+         ;; file is not locked, then if the file is read-only we
+          ;; assume that locking is used for the file, otherwise
+          ;; locking is not used.
+         (not (vc-mistrust-permissions file))
+         (vc-up-to-date-p file)
+         (if (string-match ".r-..-..-." (nth 8 (file-attributes file)))
+             (vc-file-setprop file 'vc-checkout-model 'locking)
+           (vc-file-setprop file 'vc-checkout-model 'implicit)))
+     status))))
+
+(defun vc-release-greater-or-equal (r1 r2)
+  "Compare release numbers, represented as strings.
+Release components are assumed cardinal numbers, not decimal fractions
+\(5.10 is a higher release than 5.9\).  Omitted fields are considered
+lower \(5.6.7 is earlier than 5.6.7.1\).  Comparison runs till the end
+of the string is found, or a non-numeric component shows up \(5.6.7 is
+earlier than \"5.6.7 beta\", which is probably not what you want in
+some cases\).  This code is suitable for existing RCS release numbers.
+CVS releases are handled reasonably, too \(1.3 < 1.4* < 1.5\)."
+  (let (v1 v2 i1 i2)
+    (catch 'done
+      (or (and (string-match "^\\.?\\([0-9]+\\)" r1)
+              (setq i1 (match-end 0))
+              (setq v1 (string-to-number (match-string 1 r1)))
+              (or (and (string-match "^\\.?\\([0-9]+\\)" r2)
+                       (setq i2 (match-end 0))
+                       (setq v2 (string-to-number (match-string 1 r2)))
+                       (if (> v1 v2) (throw 'done t)
+                         (if (< v1 v2) (throw 'done nil)
+                           (throw 'done
+                                  (vc-release-greater-or-equal
+                                   (substring r1 i1)
+                                   (substring r2 i2)))))))
+                  (throw 'done t)))
+         (or (and (string-match "^\\.?\\([0-9]+\\)" r2)
+                  (throw 'done nil))
+             (throw 'done t)))))
+
+(defun vc-rcs-release-p (release)
+  "Return t if we have RELEASE or better."
+  (let ((installation (vc-rcs-system-release)))
+    (if (and installation
+            (not (eq installation 'unknown)))
+       (vc-release-greater-or-equal installation release))))
+
+
+(defun vc-rcs-system-release ()
+  "Return the RCS release installed on this system, as a string.
+Return symbol UNKNOWN if the release cannot be deducted.  The user can
+override this using variable `vc-rcs-release'.
+
+If the user has not set variable `vc-rcs-release' and it is nil,
+variable `vc-rcs-release' is set to the returned value."
+  (or vc-rcs-release
+      (setq vc-rcs-release
+           (or (and (zerop (vc-do-command nil nil "rcs" nil "-V"))
+                    (with-current-buffer (get-buffer "*vc*")
+                      (vc-parse-buffer "^RCS version \\([0-9.]+ *.*\\)" 1)))
+               'unknown))))
+
+(defun vc-rcs-set-non-strict-locking (file)
+  (vc-do-command nil 0 "rcs" file "-U")
+  (vc-file-setprop file 'vc-checkout-model 'implicit)
+  (set-file-modes file (logior (file-modes file) 128)))
+
+(defun vc-rcs-set-default-branch (file branch)
+  (vc-do-command nil 0 "rcs" (vc-name file) (concat "-b" branch))
+  (vc-file-setprop file 'vc-rcs-default-branch branch))
+
 (provide 'vc-rcs)
 
 ;;; vc-rcs.el ends here
index db618915e90e571efb9202254a497009fce132f1..bc02d19912414dc3951676c87334573f16017856 100644 (file)
@@ -5,7 +5,7 @@
 ;; Author:     FSF (see vc.el for full credits)
 ;; Maintainer: Andre Spiegel <spiegel@gnu.org>
 
-;; $Id: vc-sccs.el,v 1.3 2000/09/07 20:06:55 fx Exp $
+;; $Id: vc-sccs.el,v 1.4 2000/09/09 00:48:40 monnier Exp $
 
 ;; This file is part of GNU Emacs.
 
 
 ;;; Code:
 
+;;; 
+;;; Customization options
+;;;
+
 (defcustom vc-sccs-register-switches nil
   "*Extra switches for registering a file in SCCS.
 A string or list of strings passed to the checkin program by
@@ -58,8 +62,18 @@ For a description of possible values, see `vc-check-master-templates'."
   :version "21.1"
   :group 'vc)
 
+\f
+;;;
+;;; Internal variables
+;;;
+
 (defconst vc-sccs-name-assoc-file "VC-names")
 
+\f
+;;;
+;;; State-querying functions
+;;;
+
 ;;;###autoload
 (progn (defun vc-sccs-registered (f) (vc-default-registered 'SCCS f)))
 
@@ -108,6 +122,12 @@ For a description of possible values, see `vc-check-master-templates'."
     (vc-insert-file (vc-name file) "^\001e")
     (vc-parse-buffer "^\001d D \\([^ ]+\\)" 1)))
 
+(defun vc-sccs-latest-on-branch-p (file)
+  "Return t iff the current workfile version of FILE is latest on its branch."
+  ;; Always return t; we do not support previous versions in the workfile
+  ;; under SCCS.
+  t)
+
 (defun vc-sccs-checkout-model (file)
   "SCCS-specific version of `vc-checkout-model'."
   'locking)
@@ -118,174 +138,10 @@ For a description of possible values, see `vc-check-master-templates'."
          (list "--brief" "-q"
                (concat "-r" (vc-workfile-version file)))))
 
-;; internal code
-
-;; This function is wrapped with `progn' so that the autoload cookie
-;; copies the whole function itself into loaddefs.el rather than just placing
-;; a (autoload 'vc-sccs-search-project-dir "vc-sccs") which would not
-;; help us avoid loading vc-sccs.
-;;;###autoload
-(progn (defun vc-sccs-search-project-dir (dirname basename)
-  "Return the name of a master file in the SCCS project directory.
-Does not check whether the file exists but returns nil if it does not
-find any project directory."
-  (let ((project-dir (getenv "PROJECTDIR")) dirs dir)
-    (when project-dir
-      (if (file-name-absolute-p project-dir)
-         (setq dirs '("SCCS" ""))
-       (setq dirs '("src/SCCS" "src" "source/SCCS" "source"))
-       (setq project-dir (expand-file-name (concat "~" project-dir))))
-      (while (and (not dir) dirs)
-       (setq dir (expand-file-name (car dirs) project-dir))
-       (unless (file-directory-p dir)
-         (setq dir nil)
-         (setq dirs (cdr dirs))))
-      (and dir (expand-file-name (concat "s." basename) dir))))))
-
-(defun vc-sccs-lock-file (file)
-  "Generate lock file name corresponding to FILE."
-  (let ((master (vc-name file)))
-    (and
-     master
-     (string-match "\\(.*/\\)\\(s\\.\\)\\(.*\\)" master)
-     (replace-match "p." t t master 2))))
-
-(defun vc-sccs-parse-locks ()
-  "Parse SCCS locks in current buffer.
-The result is a list of the form ((VERSION . USER) (VERSION . USER) ...)."
-  (let (master-locks)
-    (goto-char (point-min))
-    (while (re-search-forward "^\\([0-9.]+\\) [0-9.]+ \\([^ ]+\\) .*\n?"
-                             nil t)
-      (setq master-locks
-           (cons (cons (match-string 1) (match-string 2)) master-locks)))
-    ;; FIXME: is it really necessary to reverse ?
-    (nreverse master-locks)))
 \f
-(defun vc-sccs-print-log (file)
-  "Get change log associated with FILE."
-  (vc-do-command t 0 "prs" (vc-name file)))
-
-(defun vc-sccs-assign-name (file name)
-  "Assign to FILE's latest version a given NAME."
-  (vc-sccs-add-triple name file (vc-workfile-version file)))
-\f
-;; Named-configuration support
-
-(defun vc-sccs-add-triple (name file rev)
-  (with-current-buffer
-      (find-file-noselect
-       (expand-file-name vc-sccs-name-assoc-file
-                        (file-name-directory (vc-name file))))
-    (goto-char (point-max))
-    (insert name "\t:\t" file "\t" rev "\n")
-    (basic-save-buffer)
-    (kill-buffer (current-buffer))))
-
-(defun vc-sccs-rename-file (old new)
-  ;; Move the master file (using vc-rcs-master-templates).
-  (vc-rename-master (vc-name old) new vc-sccs-master-templates)
-  ;; Update the snapshot file.
-  (with-current-buffer
-      (find-file-noselect
-       (expand-file-name vc-sccs-name-assoc-file
-                        (file-name-directory (vc-name old))))
-    (goto-char (point-min))
-    ;; (replace-regexp (concat ":" (regexp-quote old) "$") (concat ":" new))
-    (while (re-search-forward (concat ":" (regexp-quote old) "$") nil t)
-      (replace-match (concat ":" new) nil nil))
-    (basic-save-buffer)
-    (kill-buffer (current-buffer))))
-
-(defun vc-sccs-lookup-triple (file name)
-  "Return the numeric version corresponding to a named snapshot of FILE.
-If NAME is nil or a version number string it's just passed through."
-  (if (or (null name)
-         (let ((firstchar (aref name 0)))
-           (and (>= firstchar ?0) (<= firstchar ?9))))
-      name
-    (with-temp-buffer
-      (vc-insert-file
-       (expand-file-name vc-sccs-name-assoc-file
-                        (file-name-directory (vc-name file))))
-      (vc-parse-buffer (concat name "\t:\t" file "\t\\(.+\\)") 1))))
-
-(defun vc-sccs-merge (file first-version &optional second-version)
-  (error "Merging not implemented for SCCS"))
-
-(defun vc-sccs-check-headers ()
-  "Check if the current file has any headers in it."
-  (save-excursion
-    (goto-char (point-min))
-    (re-search-forward  "%[A-Z]%" nil t)))
-
-(defun vc-sccs-steal-lock (file &optional rev)
-  "Steal the lock on the current workfile for FILE and revision REV."
-  (vc-do-command nil 0 "unget" (vc-name file) "-n" (if rev (concat "-r" rev)))
-  (vc-do-command nil 0 "get" (vc-name file) "-g" (if rev (concat "-r" rev))))
-
-(defun vc-sccs-cancel-version (file writable)
-  "Undo the most recent checkin of FILE.
-WRITABLE non-nil means previous version should be locked."
-  (vc-do-command nil 0 "rmdel"
-                (vc-name file)
-                (concat "-r" (vc-workfile-version file)))
-  (vc-do-command nil 0 "get"
-                (vc-name file)
-                (if writable "-e")))
-
-(defun vc-sccs-revert (file)
-  "Revert FILE to the version it was based on."
-  (vc-do-command nil 0 "unget" (vc-name file))
-  (vc-do-command nil 0 "get" (vc-name file))
-  ;; Checking out explicit versions is not supported under SCCS, yet.
-  ;; We always "revert" to the latest version; therefore
-  ;; vc-workfile-version is cleared here so that it gets recomputed.
-  (vc-file-setprop file 'vc-workfile-version nil))
-
-(defun vc-sccs-checkin (file rev comment)
-  "SCCS-specific version of `vc-backend-checkin'."
-  (let ((switches (if (stringp vc-checkin-switches)
-                     (list vc-checkin-switches)
-                   vc-checkin-switches)))
-    (apply 'vc-do-command nil 0 "delta" (vc-name file)
-          (if rev (concat "-r" rev))
-          (concat "-y" comment)
-          switches)
-    (if vc-keep-workfiles
-       (vc-do-command nil 0 "get" (vc-name file)))))
-
-(defun vc-sccs-latest-on-branch-p (file)
-  "Return t iff the current workfile version of FILE is latest on its branch."
-  ;; Always return t; we do not support previous versions in the workfile
-  ;; under SCCS.
-  t)
-
-(defun vc-sccs-logentry-check ()
-  "Check that the log entry in the current buffer is acceptable for SCCS."
-  (when (>= (buffer-size) 512)
-    (goto-char 512)
-    (error "Log must be less than 512 characters; point is now at pos 512")))
-
-(defun vc-sccs-diff (file &optional oldvers newvers)
-  "Get a difference report using SCCS between two versions of FILE."
-  (setq oldvers (vc-sccs-lookup-triple file oldvers))
-  (setq newvers (vc-sccs-lookup-triple file newvers))
-  (let* ((diff-switches-list (if (listp diff-switches)
-                                diff-switches
-                              (list diff-switches)))
-        (options (append (list "-q"
-                               (and oldvers (concat "-r" oldvers))
-                               (and newvers (concat "-r" newvers)))
-                         diff-switches-list)))
-    (apply 'vc-do-command t 1 "vcdiff" (vc-name file) options)))
-
-(defun vc-sccs-responsible-p (file)
-  "Return non-nil if SCCS thinks it would be responsible for registering FILE."
-  ;; TODO: check for all the patterns in vc-sccs-master-templates
-  (or (file-directory-p (expand-file-name "SCCS" (file-name-directory file)))
-      (stringp (vc-sccs-search-project-dir (or (file-name-directory file) "")
-                                          (file-name-nondirectory file)))))
+;;;
+;;; State-changing functions
+;;;
 
 (defun vc-sccs-register (file &optional rev comment)
   "Register FILE into the SCCS version-control system.
@@ -321,6 +177,25 @@ expanded if `vc-keep-workfiles' is non-nil, otherwise, delete the workfile."
       (if vc-keep-workfiles
          (vc-do-command nil 0 "get" (vc-name file)))))
 
+(defun vc-sccs-responsible-p (file)
+  "Return non-nil if SCCS thinks it would be responsible for registering FILE."
+  ;; TODO: check for all the patterns in vc-sccs-master-templates
+  (or (file-directory-p (expand-file-name "SCCS" (file-name-directory file)))
+      (stringp (vc-sccs-search-project-dir (or (file-name-directory file) "")
+                                          (file-name-nondirectory file)))))
+
+(defun vc-sccs-checkin (file rev comment)
+  "SCCS-specific version of `vc-backend-checkin'."
+  (let ((switches (if (stringp vc-checkin-switches)
+                     (list vc-checkin-switches)
+                   vc-checkin-switches)))
+    (apply 'vc-do-command nil 0 "delta" (vc-name file)
+          (if rev (concat "-r" rev))
+          (concat "-y" comment)
+          switches)
+    (if vc-keep-workfiles
+       (vc-do-command nil 0 "get" (vc-name file)))))
+
 (defun vc-sccs-checkout (file &optional writable rev workfile)
   "Retrieve a copy of a saved version of SCCS controlled FILE into a WORKFILE.
 WRITABLE non-nil means that the file should be writable.  REV is the
@@ -379,9 +254,166 @@ revision to check out into WORKFILE."
                   switches)))))
     (message "Checking out %s...done" filename)))
 
-(defun vc-sccs-update-changelog (files)
-  (error "Sorry, generating ChangeLog entries is not implemented for SCCS"))
+(defun vc-sccs-revert (file)
+  "Revert FILE to the version it was based on."
+  (vc-do-command nil 0 "unget" (vc-name file))
+  (vc-do-command nil 0 "get" (vc-name file))
+  ;; Checking out explicit versions is not supported under SCCS, yet.
+  ;; We always "revert" to the latest version; therefore
+  ;; vc-workfile-version is cleared here so that it gets recomputed.
+  (vc-file-setprop file 'vc-workfile-version nil))
+
+(defun vc-sccs-cancel-version (file writable)
+  "Undo the most recent checkin of FILE.
+WRITABLE non-nil means previous version should be locked."
+  (vc-do-command nil 0 "rmdel"
+                (vc-name file)
+                (concat "-r" (vc-workfile-version file)))
+  (vc-do-command nil 0 "get"
+                (vc-name file)
+                (if writable "-e")))
+
+(defun vc-sccs-steal-lock (file &optional rev)
+  "Steal the lock on the current workfile for FILE and revision REV."
+  (vc-do-command nil 0 "unget" (vc-name file) "-n" (if rev (concat "-r" rev)))
+  (vc-do-command nil 0 "get" (vc-name file) "-g" (if rev (concat "-r" rev))))
+
+\f
+;;;
+;;; History functions
+;;;
+
+(defun vc-sccs-print-log (file)
+  "Get change log associated with FILE."
+  (vc-do-command t 0 "prs" (vc-name file)))
+
+(defun vc-sccs-logentry-check ()
+  "Check that the log entry in the current buffer is acceptable for SCCS."
+  (when (>= (buffer-size) 512)
+    (goto-char 512)
+    (error "Log must be less than 512 characters; point is now at pos 512")))
+
+(defun vc-sccs-diff (file &optional oldvers newvers)
+  "Get a difference report using SCCS between two versions of FILE."
+  (setq oldvers (vc-sccs-lookup-triple file oldvers))
+  (setq newvers (vc-sccs-lookup-triple file newvers))
+  (let* ((diff-switches-list (if (listp diff-switches)
+                                diff-switches
+                              (list diff-switches)))
+        (options (append (list "-q"
+                               (and oldvers (concat "-r" oldvers))
+                               (and newvers (concat "-r" newvers)))
+                         diff-switches-list)))
+    (apply 'vc-do-command t 1 "vcdiff" (vc-name file) options)))
+
+\f
+;;;
+;;; Snapshot system
+;;;
+
+(defun vc-sccs-assign-name (file name)
+  "Assign to FILE's latest version a given NAME."
+  (vc-sccs-add-triple name file (vc-workfile-version file)))
+
+\f
+;;;
+;;; Miscellaneous
+;;;
+
+(defun vc-sccs-check-headers ()
+  "Check if the current file has any headers in it."
+  (save-excursion
+    (goto-char (point-min))
+    (re-search-forward  "%[A-Z]%" nil t)))
+
+(defun vc-sccs-rename-file (old new)
+  ;; Move the master file (using vc-rcs-master-templates).
+  (vc-rename-master (vc-name old) new vc-sccs-master-templates)
+  ;; Update the snapshot file.
+  (with-current-buffer
+      (find-file-noselect
+       (expand-file-name vc-sccs-name-assoc-file
+                        (file-name-directory (vc-name old))))
+    (goto-char (point-min))
+    ;; (replace-regexp (concat ":" (regexp-quote old) "$") (concat ":" new))
+    (while (re-search-forward (concat ":" (regexp-quote old) "$") nil t)
+      (replace-match (concat ":" new) nil nil))
+    (basic-save-buffer)
+    (kill-buffer (current-buffer))))
+
+\f
+;;;
+;;; Internal functions
+;;;
+
+;; This function is wrapped with `progn' so that the autoload cookie
+;; copies the whole function itself into loaddefs.el rather than just placing
+;; a (autoload 'vc-sccs-search-project-dir "vc-sccs") which would not
+;; help us avoid loading vc-sccs.
+;;;###autoload
+(progn (defun vc-sccs-search-project-dir (dirname basename)
+  "Return the name of a master file in the SCCS project directory.
+Does not check whether the file exists but returns nil if it does not
+find any project directory."
+  (let ((project-dir (getenv "PROJECTDIR")) dirs dir)
+    (when project-dir
+      (if (file-name-absolute-p project-dir)
+         (setq dirs '("SCCS" ""))
+       (setq dirs '("src/SCCS" "src" "source/SCCS" "source"))
+       (setq project-dir (expand-file-name (concat "~" project-dir))))
+      (while (and (not dir) dirs)
+       (setq dir (expand-file-name (car dirs) project-dir))
+       (unless (file-directory-p dir)
+         (setq dir nil)
+         (setq dirs (cdr dirs))))
+      (and dir (expand-file-name (concat "s." basename) dir))))))
+
+(defun vc-sccs-lock-file (file)
+  "Generate lock file name corresponding to FILE."
+  (let ((master (vc-name file)))
+    (and
+     master
+     (string-match "\\(.*/\\)\\(s\\.\\)\\(.*\\)" master)
+     (replace-match "p." t t master 2))))
+
+(defun vc-sccs-parse-locks ()
+  "Parse SCCS locks in current buffer.
+The result is a list of the form ((VERSION . USER) (VERSION . USER) ...)."
+  (let (master-locks)
+    (goto-char (point-min))
+    (while (re-search-forward "^\\([0-9.]+\\) [0-9.]+ \\([^ ]+\\) .*\n?"
+                             nil t)
+      (setq master-locks
+           (cons (cons (match-string 1) (match-string 2)) master-locks)))
+    ;; FIXME: is it really necessary to reverse ?
+    (nreverse master-locks)))
+
+(defun vc-sccs-add-triple (name file rev)
+  (with-current-buffer
+      (find-file-noselect
+       (expand-file-name vc-sccs-name-assoc-file
+                        (file-name-directory (vc-name file))))
+    (goto-char (point-max))
+    (insert name "\t:\t" file "\t" rev "\n")
+    (basic-save-buffer)
+    (kill-buffer (current-buffer))))
+
+(defun vc-sccs-lookup-triple (file name)
+  "Return the numeric version corresponding to a named snapshot of FILE.
+If NAME is nil or a version number string it's just passed through."
+  (if (or (null name)
+         (let ((firstchar (aref name 0)))
+           (and (>= firstchar ?0) (<= firstchar ?9))))
+      name
+    (with-temp-buffer
+      (vc-insert-file
+       (expand-file-name vc-sccs-name-assoc-file
+                        (file-name-directory (vc-name file))))
+      (vc-parse-buffer (concat name "\t:\t" file "\t\\(.+\\)") 1))))
 
 (provide 'vc-sccs)
 
 ;;; vc-sccs.el ends here
+
+
+