(dired-do-print): Put spaces between lpr switches.
[bpt/emacs.git] / lisp / vc.el
index d0f7d77..ec712ee 100644 (file)
@@ -1,9 +1,10 @@
 ;;; vc.el --- drive a version-control system from within Emacs
 
-;; Copyright (C) 1992, 1993 Free Software Foundation, Inc.
+;; Copyright (C) 1992, 1993, 1994 Free Software Foundation, Inc.
 
 ;; Author: Eric S. Raymond <esr@snark.thyrsus.com>
-;; Version: 5.4
+;; Maintainer: eggert@twinsun.com
+;; Version: 5.5
 
 ;; This file is part of GNU Emacs.
 
@@ -30,8 +31,8 @@
 ;; and Richard Stallman contributed valuable criticism, support, and testing.
 ;;
 ;; Supported version-control systems presently include SCCS and RCS;
-;; your RCS version should be 5.6.2 or later for proper operation of
-;; the lock-breaking code.
+;; the RCS lock-stealing code doesn't work right unless you use RCS 5.6.2
+;; or newer.  Currently (January 1994) that is only a beta test release.
 ;;
 ;; The RCS code assumes strict locking.  You can support the RCS -x option
 ;; by adding pairs to the vc-master-templates list.
@@ -60,6 +61,7 @@
 
 (require 'vc-hooks)
 (require 'ring)
+(eval-when-compile (require 'dired))   ; for dired-map-over-marks macro
 
 (if (not (assoc 'vc-parent-buffer minor-mode-alist))
     (setq minor-mode-alist
@@ -104,7 +106,7 @@ The value is only computed when needed to avoid an expensive search.")
 (defvar vc-header-alist
   '((SCCS "\%W\%") (RCS "\$Id\$"))
   "*Header keywords to be inserted when `vc-insert-headers' is executed.")
-(defconst vc-static-header-alist
+(defvar vc-static-header-alist
   '(("\\.c$" .
      "\n#ifndef lint\nstatic char vcid[] = \"\%s\";\n#endif /* lint */\n"))
   "*Associate static header string templates with file types.  A \%s in the
@@ -118,11 +120,20 @@ Add an entry in this list if you need to override the normal comment-start
 and comment-end variables.  This will only be necessary if the mode language
 is sensitive to blank lines.")
 
+;; Default is to be extra careful for super-user.
+(defvar vc-checkout-carefully (= (user-uid) 0)
+  "*Non-nil means be extra-careful in checkout.
+Verify that the file really is not locked
+and that its contents match what the master file says.")
+
 ;; Variables the user doesn't need to know about.
 (defvar vc-log-entry-mode nil)
 (defvar vc-log-operation nil)
 (defvar vc-log-after-operation-hook nil)
 (defvar vc-checkout-writable-buffer-hook 'vc-checkout-writable-buffer)
+;; In a log entry buffer, this is a local variable
+;; that points to the buffer for which it was made
+;; (either a file, or a VC dired buffer).
 (defvar vc-parent-buffer nil)
 (defvar vc-parent-buffer-name nil)
 
@@ -164,16 +175,19 @@ is sensitive to blank lines.")
 (defun vc-find-binary (name)
   "Look for a command anywhere on the subprocess-command search path."
   (or (cdr (assoc name vc-binary-assoc))
-      (let ((full nil))
-       (catch 'found
-         (mapcar
-          (function (lambda (s)
-             (if (and s (file-exists-p (setq full (concat s "/" name))))
-                 (throw 'found nil))))
-         exec-path))
-       (if full
-           (setq vc-binary-assoc (cons (cons name full) vc-binary-assoc)))
-       full)))
+      (catch 'found
+       (mapcar
+        (function 
+         (lambda (s)
+           (if s
+               (let ((full (concat s "/" name)))
+                 (if (file-executable-p full)
+                     (progn
+                       (setq vc-binary-assoc
+                             (cons (cons name full) vc-binary-assoc))
+                       (throw 'found full)))))))
+        exec-path)
+       nil)))
 
 (defun vc-do-command (okstatus command file &rest flags)
   "Execute a version-control command, notifying user and checking for errors.
@@ -204,15 +218,17 @@ the master name of FILE; this is appended to an optional list of FLAGS."
     (if vc-file
        (setq squeezed (append squeezed (list vc-file))))
     (let ((default-directory (file-name-directory (or file "./")))
-         (exec-path (if vc-path (append exec-path vc-path) exec-path)))
+         (exec-path (if vc-path (append exec-path vc-path) exec-path))
+         ;; Add vc-path to PATH for the execution of this command.
+         (process-environment
+          (cons (concat "PATH=" (getenv "PATH")
+                        ":" (mapconcat 'identity vc-path ":"))
+                process-environment)))
       (setq status (apply 'call-process command nil t nil squeezed)))
     (goto-char (point-max))
-    (previous-line 1)
+    (forward-line -1)
     (if (or (not (integerp status)) (< okstatus status))
        (progn
-         (previous-line 1)
-         (print (cons command squeezed))
-         (next-line 1)
          (pop-to-buffer "*vc*")
          (goto-char (point-min))
          (shrink-window-if-larger-than-buffer)
@@ -325,30 +341,29 @@ the master name of FILE; this is appended to an optional list of FLAGS."
          (if new-mark (set-mark new-mark))))))
 
 
-(defun vc-buffer-sync ()
+(defun vc-buffer-sync (&optional not-urgent)
   ;; Make sure the current buffer and its working file are in sync
-  (if (and (buffer-modified-p)
-          (or
-           vc-suppress-confirm
-           (y-or-n-p (format "%s has been modified.  Write it out? "
-                             (buffer-name)))))
-      (save-buffer)))
-
-(defun vc-workfile-unchanged-p (file)
+  ;; NOT-URGENT means it is ok to continue if the user says not to save.
+  (if (buffer-modified-p)
+      (if (or vc-suppress-confirm
+             (y-or-n-p (format "Buffer %s modified; save it? " (buffer-name))))
+         (save-buffer)
+       (if not-urgent
+           nil
+         (error "Aborted")))))
+
+
+(defun vc-workfile-unchanged-p (file &optional want-differences-if-changed)
   ;; Has the given workfile changed since last checkout?
   (let ((checkout-time (vc-file-getprop file 'vc-checkout-time))
        (lastmod (nth 5 (file-attributes file))))
-    (if checkout-time
-     (equal lastmod checkout-time)
-     (if (zerop (vc-backend-diff file nil))
-        (progn
-          (vc-file-setprop file 'vc-checkout-time lastmod)
-          t)
-       (progn
-          (vc-file-setprop file 'vc-checkout-time '(0 . 0))
-          nil
-        ))
-     )))
+    (or (equal checkout-time lastmod)
+       (and (or (not checkout-time) want-differences-if-changed)
+            (let ((unchanged (zerop (vc-backend-diff file nil nil
+                                     (not want-differences-if-changed)))))
+              ;; 0 stands for an unknown time; it can't match any mod time.
+              (vc-file-setprop file 'vc-checkout-time (if unchanged lastmod 0))
+              unchanged)))))
 
 (defun vc-next-action-on-file (file verbose &optional comment)
   ;;; If comment is specified, it will be used as an admin or checkin comment.
@@ -357,15 +372,29 @@ the master name of FILE; this is appended to an optional list of FLAGS."
 
      ;; if there is no master file corresponding, create one
      ((not vc-file)
-      (vc-register verbose comment)
-      (if vc-initial-comment
-         (setq vc-log-after-operation-hook
-               'vc-checkout-writable-buffer-hook)
-       (vc-checkout-writable-buffer file)))
+      (vc-register verbose comment))
 
      ;; if there is no lock on the file, assert one and get it
      ((not (setq owner (vc-locking-user file)))
-      (vc-checkout-writable-buffer file))
+      (if (and vc-checkout-carefully
+              (not (vc-workfile-unchanged-p file t)))
+         (if (save-window-excursion
+               (pop-to-buffer "*vc*")
+               (goto-char (point-min))
+               (insert-string (format "Changes to %s since last lock:\n\n"
+                                      file))
+               (not (beep))
+               (yes-or-no-p
+                     (concat "File has unlocked changes, "
+                      "claim lock retaining changes? ")))
+             (progn (vc-backend-steal file)
+                    (vc-mode-line file))
+           (if (not (yes-or-no-p "Revert to checked-in version, instead? "))
+               (error "Checkout aborted.")
+             (vc-revert-buffer1 t t)
+             (vc-checkout-writable-buffer file))
+           )
+       (vc-checkout-writable-buffer file)))
 
      ;; a checked-out version exists, but the user may not own the lock
      ((not (string-equal owner (user-login-name)))
@@ -441,17 +470,19 @@ it will operate on the file in the current line.
 files are marked, it will accept a log message and then operate on
 each one.  The log message will be used as a comment for any register
 or checkin operations, but ignored when doing checkouts.  Attempted
-lock steals will raise an error."
+lock steals will raise an error.
+
+   For checkin, a prefix argument lets you specify the version number to use."
   (interactive "P")
   (catch 'nogo
     (if vc-dired-mode
        (let ((files (dired-get-marked-files)))
          (if (= (length files) 1)
-             (find-file-other-window (dired-get-filename))
+             (find-file-other-window (car files))
            (vc-start-entry nil nil nil
                            "Enter a change comment for the marked files."
                            'vc-next-action-dired)
-           (throw 'nogo))))
+           (throw 'nogo nil))))
     (while vc-parent-buffer
       (pop-to-buffer vc-parent-buffer))
     (if buffer-file-name
@@ -469,8 +500,12 @@ lock steals will raise an error."
 (defun vc-register (&optional override comment)
   "Register the current file into your version-control system."
   (interactive "P")
-  (if (vc-name buffer-file-name)
-      (error "This file is already registered"))
+  (let ((master (vc-name buffer-file-name)))
+    (and master (file-exists-p master)
+        (error "This file is already registered"))
+    (and master
+        (not (y-or-n-p "Previous master file has vanished.  Make a new one? "))
+        (error "This file is already registered")))
   ;; Watch out for new buffers of size 0: the corresponding file
   ;; does not exist yet, even though buffer-modified-p is nil.
   (if (and (not (buffer-modified-p))
@@ -501,11 +536,12 @@ lock steals will raise an error."
           (delete-window)
           (kill-buffer (current-buffer))))))
 
-(defun vc-start-entry (file rev comment msg action)
+(defun vc-start-entry (file rev comment msg action &optional after-hook)
   ;; Accept a comment for an operation on FILE revision REV.  If COMMENT
   ;; is nil, pop up a VC-log buffer, emit MSG, and set the
   ;; action on close to ACTION; otherwise, do action immediately.
-  ;; Remember the file's buffer in parent-buffer (current one if no file).
+  ;; Remember the file's buffer in vc-parent-buffer (current one if no file).
+  ;; AFTER-HOOK specifies the local value for vc-log-operation-hook.
   (let ((parent (if file (find-file-noselect file) (current-buffer))))
     (if comment
        (set-buffer (get-buffer-create "*VC-log*"))
@@ -515,6 +551,9 @@ lock steals will raise an error."
         (concat " from " (buffer-name vc-parent-buffer)))
     (vc-mode-line (or file " (no file)"))
     (vc-log-mode)
+    (make-local-variable 'vc-log-after-operation-hook)
+    (if after-hook
+       (setq vc-log-after-operation-hook after-hook))
     (setq vc-log-operation action)
     (setq vc-log-file file)
     (setq vc-log-version rev)
@@ -531,9 +570,10 @@ lock steals will raise an error."
   "Check a file into your version-control system.
 FILE is the unmodified name of the file.  REV should be the base version
 level to check it in under.  COMMENT, if specified, is the checkin comment."
-      (vc-start-entry file rev
-                     (or comment (not vc-initial-comment))
-                     "Enter initial comment." 'vc-backend-admin))
+  (vc-start-entry file rev
+                 (or comment (not vc-initial-comment))
+                 "Enter initial comment." 'vc-backend-admin
+                 nil))
 
 (defun vc-checkout (file &optional writable)
   "Retrieve a copy of the latest version of the given file."
@@ -548,29 +588,36 @@ level to check it in under.  COMMENT, if specified, is the checkin comment."
 
 (defun vc-steal-lock (file rev &optional owner)
   "Steal the lock on the current workfile."
-  (interactive)
-  (if (not owner)
-      (setq owner (vc-locking-user file)))
-  (if (not (y-or-n-p (format "Take the lock on %s:%s from %s? " file rev owner)))
-      (error "Steal cancelled"))
-  (pop-to-buffer (get-buffer-create "*VC-mail*"))
-  (setq default-directory (expand-file-name "~/"))
-  (auto-save-mode auto-save-default)
-  (mail-mode)
-  (erase-buffer)
-  (mail-setup owner (format "%s:%s" file rev) nil nil nil
-             (list (list 'vc-finish-steal file rev)))
-  (goto-char (point-max))
-  (insert
-   (format "I stole the lock on %s:%s, " file rev)
-   (current-time-string)
-   ".\n")
-  (message "Please explain why you stole the lock.  Type C-c C-c when done."))
+  (let (file-description)
+    (if (not owner)
+       (setq owner (vc-locking-user file)))
+    (if rev
+       (setq file-description (format "%s:%s" file rev))
+      (setq file-description file))
+    (if (not (y-or-n-p (format "Take the lock on %s from %s? "
+                              file-description owner)))
+       (error "Steal cancelled"))
+    (pop-to-buffer (get-buffer-create "*VC-mail*"))
+    (setq default-directory (expand-file-name "~/"))
+    (auto-save-mode auto-save-default)
+    (mail-mode)
+    (erase-buffer)
+    (mail-setup owner (format "Stolen lock on %s" file-description) nil nil nil
+               (list (list 'vc-finish-steal file rev)))
+    (goto-char (point-max))
+    (insert
+     (format "I stole the lock on %s, " file-description)
+     (current-time-string)
+     ".\n")
+    (message "Please explain why you stole the lock.  Type C-c C-c when done.")))
 
 ;; This is called when the notification has been sent.
 (defun vc-finish-steal (file version)
   (vc-backend-steal file version)
-  (vc-resynch-window file t t))
+  (if (get-file-buffer file)
+      (save-excursion
+       (set-buffer (get-file-buffer file))
+       (vc-resynch-window file t t))))
 
 (defun vc-checkin (file &optional rev comment)
   "Check in the file specified by FILE.
@@ -579,8 +626,9 @@ The optional argument REV may be a string specifying the new version level
 permissions zeroed, or deleted (according to the value of `vc-keep-workfiles').
 COMMENT is a comment string; if omitted, a buffer is
 popped up to accept a comment."
-  (setq vc-log-after-operation-hook 'vc-checkin-hook)
-  (vc-start-entry file rev comment "Enter a change comment." 'vc-backend-checkin))
+  (vc-start-entry file rev comment
+                 "Enter a change comment." 'vc-backend-checkin
+                 'vc-checkin-hook))
 
 ;;; Here is a checkin hook that may prove useful to sites using the
 ;;; ChangeLog facility supported by Emacs.
@@ -592,6 +640,9 @@ If nil, uses `change-log-default-name'."
   (interactive (if current-prefix-arg
                   (list current-prefix-arg
                         (prompt-for-change-log-name))))
+  ;; Make sure the defvar for add-log-current-defun-function has been executed
+  ;; before binding it.
+  (require 'add-log)
   (let (;; Extract the comment first so we get any error before doing anything.
        (comment (ring-ref vc-comment-ring 0))
        ;; Don't let add-change-log-entry insert a defun name.
@@ -643,6 +694,12 @@ If nil, uses `change-log-default-name'."
            (setq vc-comment-ring (make-ring vc-maximum-comment-ring-size)))
        (ring-insert vc-comment-ring (buffer-string))
        ))
+  ;; Sync parent buffer in case the user modified it while editing the comment.
+  ;; But not if it is a vc-dired buffer.
+  (save-excursion
+    (set-buffer vc-parent-buffer)
+    (or vc-dired-mode
+       (vc-buffer-sync)))
   ;; OK, do it to it
   (if vc-log-operation
       (save-excursion
@@ -651,14 +708,17 @@ If nil, uses `change-log-default-name'."
                 vc-log-version
                 (buffer-string)))
     (error "No log operation is pending"))
-  ;; Return to "parent" buffer of this checkin and remove checkin window
-  (pop-to-buffer vc-parent-buffer)
-  (delete-windows-on (get-buffer "*VC-log*"))
-  (kill-buffer "*VC-log*")
-  ;; Now make sure we see the expanded headers
-  (if buffer-file-name
+  ;; save the vc-log-after-operation-hook of log buffer
+  (let ((after-hook vc-log-after-operation-hook))
+    ;; Return to "parent" buffer of this checkin and remove checkin window
+    (pop-to-buffer vc-parent-buffer)
+    (let ((logbuf (get-buffer "*VC-log*")))
+      (delete-windows-on logbuf)
+      (kill-buffer logbuf))
+    ;; Now make sure we see the expanded headers
+    (if buffer-file-name
        (vc-resynch-window buffer-file-name vc-keep-workfiles t))
-  (run-hooks vc-log-after-operation-hook))
+    (run-hooks after-hook)))
 
 ;; Code for access to the comment ring
 
@@ -725,7 +785,7 @@ If nil, uses `change-log-default-name'."
 ;; Additional entry points for examining version histories
 
 ;;;###autoload
-(defun vc-diff (historic)
+(defun vc-diff (historic &optional not-urgent)
   "Display diffs between file versions.
 Normally this compares the current file and buffer with the most recent 
 checked in version of that file.  This uses no arguments.
@@ -745,11 +805,11 @@ and two version designators specifying which versions to compare."
          unchanged)
       (or (and file (vc-name file))
          (vc-registration-error file))
-      (vc-buffer-sync)
+      (vc-buffer-sync not-urgent)
       (setq unchanged (vc-workfile-unchanged-p buffer-file-name))
       (if unchanged
          (message "No changes to %s since latest version." file)
-       (vc-backend-diff file nil)
+       (vc-backend-diff file)
        ;; Ideally, we'd like at this point to parse the diff so that
        ;; the buffer effectively goes into compilation mode and we
        ;; can visit the old and new change locations via next-error.
@@ -806,6 +866,26 @@ files in or below it."
        (message "No changes to %s between %s and %s." file rel1 rel2)
       (pop-to-buffer "*vc*"))))
 
+;;;###autoload
+(defun vc-version-other-window (rev)
+  "Visit version REV of the current buffer in another window.
+If the current buffer is named `F', the version is named `F.~REV~'.
+If `F.~REV~' already exists, it is used instead of being re-created."
+  (interactive "sVersion to visit (default is latest version): ")
+  (if vc-dired-mode
+      (set-buffer (find-file-noselect (dired-get-filename))))
+  (while vc-parent-buffer
+      (pop-to-buffer vc-parent-buffer))
+  (if (and buffer-file-name (vc-name buffer-file-name))
+      (let* ((version (if (string-equal rev "")
+                         (vc-latest-version buffer-file-name)
+                       rev))
+            (filename (concat buffer-file-name ".~" version "~")))
+        (or (file-exists-p filename)
+            (vc-backend-checkout buffer-file-name nil version filename))
+        (find-file-other-window filename))
+    (vc-registration-error buffer-file-name)))
+
 ;; Header-insertion code
 
 ;;;###autoload
@@ -975,7 +1055,9 @@ on a buffer attached to the file named in the current Dired buffer line."
   (save-excursion
     (find-file (concat (vc-backend-subdirectory-name file) "/" vc-name-assoc-file))
     (goto-char (point-min))
-    (replace-regexp (concat ":" (regexp-quote file) "$") (concat ":" newname))
+    ;; (replace-regexp (concat ":" (regexp-quote file) "$") (concat ":" newname))
+    (while (re-search-forward (concat ":" (regexp-quote file) "$") nil t)
+      (replace-match (concat ":" newname) nil nil))
     (basic-save-buffer)
     (kill-buffer (current-buffer))
     ))
@@ -983,7 +1065,7 @@ on a buffer attached to the file named in the current Dired buffer line."
 (defun vc-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
-  (cond ((null name) "")
+  (cond ((null name) name)
        ((let ((firstchar (aref name 0)))
           (and (>= firstchar ?0) (<= firstchar ?9)))
         name)
@@ -1052,7 +1134,12 @@ levels in the snapshot."
       (progn
        (vc-backend-print-log buffer-file-name)
        (pop-to-buffer (get-buffer-create "*vc*"))
+       (while (looking-at "=*\n")
+         (delete-char (- (match-end 0) (match-beginning 0)))
+         (forward-line -1))
        (goto-char (point-min))
+       (if (looking-at "[\b\t\n\v\f\r ]+")
+           (delete-char (- (match-end 0) (match-beginning 0))))
        (shrink-window-if-larger-than-buffer)
        )
     (vc-registration-error buffer-file-name)
@@ -1070,7 +1157,7 @@ to that version."
   (while vc-parent-buffer
       (pop-to-buffer vc-parent-buffer))
   (let ((file buffer-file-name)
-       (obuf (current-buffer)) (changed (vc-diff nil)))
+       (obuf (current-buffer)) (changed (vc-diff nil t)))
     (if (and changed (or vc-suppress-confirm
                         (not (yes-or-no-p "Discard changes? "))))
        (progn
@@ -1107,23 +1194,43 @@ A prefix argument means do not revert the buffer afterwards."
        (vc-checkout (buffer-file-name) nil)))
     ))
 
+;;;###autoload
 (defun vc-rename-file (old new)
   "Rename file OLD to NEW, and rename its master file likewise."
   (interactive "fVC rename file: \nFRename to: ")
   (let ((oldbuf (get-file-buffer old)))
-    (if (buffer-modified-p oldbuf)
+    (if (and oldbuf (buffer-modified-p oldbuf))
        (error "Please save files before moving them"))
     (if (get-file-buffer new)
        (error "Already editing new file name"))
+    (if (file-exists-p new)
+       (error "New file already exists"))
     (let ((oldmaster (vc-name old)))
       (if oldmaster
-       (if (vc-locking-user old)
-           (error "Please check in files before moving them"))
-       (if (or (file-symlink-p oldmaster)
-               ;; This had FILE, I changed it to OLD. -- rms.
-               (file-symlink-p (vc-backend-subdirectory-name old)))
-           (error "This is not a safe thing to do in the presence of symbolic links"))
-       (rename-file oldmaster (vc-name new)))
+         (progn
+           (if (vc-locking-user old)
+               (error "Please check in files before moving them"))
+           (if (or (file-symlink-p oldmaster)
+                   ;; This had FILE, I changed it to OLD. -- rms.
+                   (file-symlink-p (vc-backend-subdirectory-name old)))
+               (error "This is not a safe thing to do in the presence of symbolic links"))
+           (rename-file
+            oldmaster
+            (let ((backend (vc-backend-deduce old))
+                  (newdir (or (file-name-directory new) ""))
+                  (newbase (file-name-nondirectory new)))
+              (catch 'found
+                (mapcar
+                 (function
+                  (lambda (s)
+                    (if (eq backend (cdr s))
+                        (let* ((newmaster (format (car s) newdir newbase))
+                               (newmasterdir (file-name-directory newmaster)))
+                          (if (or (not newmasterdir)
+                                  (file-directory-p newmasterdir))
+                              (throw 'found newmaster))))))
+                 vc-master-templates)
+                (error "New file lacks a version control directory"))))))
       (if (or (not oldmaster) (file-exists-p old))
          (rename-file old new)))
 ; ?? Renaming a file might change its contents due to keyword expansion.
@@ -1181,6 +1288,10 @@ From a program, any arguments are passed to the `rcs2log' script."
     (message "Computing change log entries... %s"
             (if (or (null args)
                     (eq 0 (apply 'call-process "rcs2log" nil t nil
+                                 "-n"
+                                 (user-login-name)
+                                 (user-full-name)
+                                 user-mail-address
                                  (mapcar (function
                                           (lambda (f)
                                             (file-relative-name
@@ -1216,11 +1327,12 @@ From a program, any arguments are passed to the `rcs2log' script."
                                   (vc-match-substring 1))))))
                   latest-val))
             (prog1
-                (and (re-search-forward p nil t)
-                     (let ((value (vc-match-substring 1)))
-                       (if file
-                           (vc-file-setprop file (car properties) value))
-                       value))
+                (let ((value nil))
+                  (if (re-search-forward p nil t)
+                      (setq value (vc-match-substring 1)))
+                  (if file
+                      (vc-file-setprop file (car properties) value))
+                  value)
               (setq properties (cdr properties)))))
          patterns)
   )
@@ -1281,17 +1393,18 @@ Return nil if there is no such person."
     ;; control and has -rw-r--r-- is locked by its owner.  This is true
     ;; for both RCS and SCCS, which keep unlocked files at -r--r--r--.
     ;; We have to be careful not to exclude files with execute bits on;
-    ;; scripts can be under version control too.  The advantage of this
-    ;; hack is that calls to the very expensive vc-fetch-properties
+    ;; scripts can be under version control too.  Also, we must ignore
+    ;; the group-read and other-read bits, since paranoid users turn them off.
+    ;; This hack wins because calls to the very expensive vc-fetch-properties
     ;; function only have to be made if (a) the file is locked by someone
     ;; other than the current user, or (b) some untoward manipulation
     ;; behind vc's back has changed the owner or the `group' or `other'
     ;; write bits.
     (let ((attributes (file-attributes file)))
-      (cond ((string-match ".r-.r-.r-." (nth 8 attributes))
+      (cond ((string-match ".r-..-..-." (nth 8 attributes))
             nil)
            ((and (= (nth 2 attributes) (user-uid))
-                 (string-match ".rw.r-.r-." (nth 8 attributes)))
+                 (string-match ".rw..-..-." (nth 8 attributes)))
             (user-login-name))
            (t
             (vc-true-locking-user file))))))
@@ -1423,21 +1536,70 @@ Return nil if there is no such person."
   (message "Registering %s...done" file)
   )
 
-(defun vc-backend-checkout (file &optional writable rev)
+(defun vc-backend-checkout (file &optional writable rev workfile)
   ;; Retrieve a copy of a saved version into a workfile
-  (message "Checking out %s..." file)
-  (vc-backend-dispatch file
-   (progn
-     (vc-do-command 0 "get" file       ;; SCCS
-                   (if writable "-e")
-                   (and rev (concat "-r" (vc-lookup-triple file rev))))
+  (let ((filename (or workfile file)))
+    (message "Checking out %s..." filename)
+    (vc-backend-dispatch file
+     (if workfile ;; SCCS
+        ;; Some SCCS implementations allow checking out directly to a
+        ;; file using the -G option, but then some don't so use the
+        ;; least common denominator approach and use the -p option
+        ;; ala RCS.
+        (let ((vc-modes (logior (file-modes (vc-name file))
+                                (if writable 128 0)))
+              (failed t))
+          (unwind-protect
+              (progn
+                  (vc-do-command
+                     0 "/bin/sh" file "-c"
+                     ;; Some shells make the "" dummy argument into $0
+                     ;; while others use the shell's name as $0 and
+                     ;; use the "" as $1.  The if-statement
+                     ;; converts the latter case to the former.
+                     (format "if [ x\"$1\" = x ]; then shift; fi; \
+                              umask %o; exec >\"$1\" || exit; \
+                              shift; umask %o; exec get \"$@\""
+                             (logand 511 (lognot vc-modes))
+                             (logand 511 (lognot (default-file-modes))))
+                     "" ; dummy argument for shell's $0
+                     filename 
+                     (if writable "-e")
+                     "-p" (and rev (concat "-r" (vc-lookup-triple file rev))))
+                  (setq failed nil))
+            (and failed (file-exists-p filename) (delete-file filename))))
+       (vc-do-command 0 "get" file     ;; SCCS
+                     (if writable "-e")
+                     (and rev (concat "-r" (vc-lookup-triple file rev)))))
+     (if workfile ;; RCS
+        ;; RCS doesn't let us check out into arbitrary file names directly.
+        ;; Use `co -p' and make stdout point to the correct file.
+        (let ((vc-modes (logior (file-modes (vc-name file))
+                                (if writable 128 0)))
+              (failed t))
+          (unwind-protect
+              (progn
+                  (vc-do-command
+                     0 "/bin/sh" file "-c"
+                     ;; See the SCCS case, above, regarding the if-statement.
+                     (format "if [ x\"$1\" = x ]; then shift; fi; \
+                              umask %o; exec >\"$1\" || exit; \
+                              shift; umask %o; exec co \"$@\""
+                             (logand 511 (lognot vc-modes))
+                             (logand 511 (lognot (default-file-modes))))
+                     "" ; dummy argument for shell's $0
+                     filename
+                     (if writable "-l")
+                     (concat "-p" rev))
+                  (setq failed nil))
+            (and failed (file-exists-p filename) (delete-file filename))))
+       (vc-do-command 0 "co" file
+                     (if writable "-l")
+                     (and rev (concat "-r" rev))))
      )
-   (vc-do-command 0 "co" file  ;; RCS
-                 (if writable "-l")
-                 (and rev (concat "-r" rev)))
-   )
-  (vc-file-setprop file 'vc-checkout-time (nth 5 (file-attributes file)))
-  (message "Checking out %s...done" file)
+    (or workfile
+       (vc-file-setprop file 'vc-checkout-time (nth 5 (file-attributes file))))
+    (message "Checking out %s...done" filename))
   )
 
 (defun vc-backend-logentry-check (file)
@@ -1529,22 +1691,27 @@ Return nil if there is no such person."
    )
   )
 
-(defun vc-backend-diff (file oldvers &optional newvers)
-  ;; Get a difference report between two versions
+(defun vc-backend-diff (file &optional oldvers newvers cmp)
+  ;; Get a difference report between two versions of FILE.
+  ;; Get only a brief comparison report if CMP, a difference report otherwise.
   (if (eq (vc-backend-deduce file) 'SCCS)
       (setq oldvers (vc-lookup-triple file oldvers))
       (setq newvers (vc-lookup-triple file newvers)))
-  (apply 'vc-do-command 1
-        (or (vc-backend-dispatch file "vcdiff" "rcsdiff")
-            (vc-registration-error file))
-        file
-        "-q"
-        (and oldvers (concat "-r" oldvers))
-        (and newvers (concat "-r" newvers))
-        (if (listp diff-switches)
-            diff-switches
-          (list diff-switches))
-  ))
+  (let* ((command (or (vc-backend-dispatch file "vcdiff" "rcsdiff")
+                     (vc-registration-error file)))
+        (options (append (list (and cmp "--brief")
+                               "-q"
+                               (and oldvers (concat "-r" oldvers))
+                               (and newvers (concat "-r" newvers)))
+                         (and (not cmp)
+                              (if (listp diff-switches)
+                                  diff-switches
+                                (list diff-switches)))))
+        (status (apply 'vc-do-command 2 command file options)))
+    ;; Some RCS versions don't understand "--brief"; work around this.
+    (if (eq status 2)
+       (apply 'vc-do-command 1 command file (if cmp (cdr options) options))
+      status)))
 
 (defun vc-check-headers ()
   "Check if the current file has any headers in it."
@@ -1572,6 +1739,7 @@ These bindings are added to the global keymap when you enter this mode:
 \\[vc-revert-buffer]           revert buffer to latest version
 \\[vc-cancel-version]          undo latest checkin
 \\[vc-diff]            show diffs between file versions
+\\[vc-version-other-window]            visit old version in another window
 \\[vc-directory]               show all files locked by any user in or below .
 \\[vc-update-change-log]               add change log entry from recent checkins