changeset 2580:a66f7ed76416

(vc-diff): Get proper error message when you run this with no prefix arg on an empty buffer. (vc-directory): Better directory format --- replace the user and group IDs with locking-user (if any). (vc-finish-logentry, vc-next-comment, vc-previous-comment): Replace *VC-comment-buffer* with a ring vector.
author Eric S. Raymond <esr@snark.thyrsus.com>
date Sun, 25 Apr 1993 22:26:40 +0000
parents 5d55e3b47227
children 839d67a1dc58
files lisp/vc.el
diffstat 1 files changed, 178 insertions(+), 106 deletions(-) [+]
line wrap: on
line diff
--- a/lisp/vc.el	Sun Apr 25 06:15:18 1993 +0000
+++ b/lisp/vc.el	Sun Apr 25 22:26:40 1993 +0000
@@ -58,7 +58,15 @@
 ;;; Code:
 
 (require 'vc-hooks)
+(require 'ring)
 (require 'dired)
+(require 'compile)
+(require 'sendmail)
+
+(if (not (assoc 'vc-parent-buffer minor-mode-alist))
+    (setq minor-mode-alist
+	  (cons '(vc-parent-buffer vc-parent-buffer-name)
+		minor-mode-alist)))
 
 ;; General customization
 
@@ -77,10 +85,12 @@
   "*Display run messages from back-end commands.")
 (defvar vc-mistrust-permissions 'file-symlink-p
   "*Don't assume that permissions and ownership track version-control status.")
-
 (defvar vc-checkin-switches nil
   "*Extra switches passed to the checkin program by \\[vc-checkin].")
 
+(defconst vc-maximum-comment-ring-size 32
+  "Maximum number of saved comments in the comment ring.")
+
 ;;;###autoload
 (defvar vc-checkin-hook nil
   "*List of functions called after a vc-checkin is done.  See `run-hooks'.")
@@ -110,20 +120,34 @@
 (defvar vc-log-after-operation-hook nil)
 (defvar vc-checkout-writeable-buffer-hook 'vc-checkout-writeable-buffer)
 (defvar vc-parent-buffer nil)
+(defvar vc-parent-buffer-name nil)
 
 (defvar vc-log-file)
 (defvar vc-log-version)
 
 (defconst vc-name-assoc-file "VC-names")
 
+(defvar vc-dired-mode nil)
 (make-variable-buffer-local 'vc-dired-mode)
 
+(defvar vc-comment-ring nil)
+(defvar vc-comment-ring-index nil)
+(defvar vc-last-comment-match nil)
+
 ;; File property caching
 
 (defun vc-file-clearprops (file)
   ;; clear all properties of a given file
   (setplist (intern file vc-file-prop-obarray) nil))
 
+(defun vc-clear-context ()
+  "Clear all cached file properties and the comment ring."
+  (interactive)
+  (fillarray vc-file-prop-obarray nil)
+  ;; Note: there is potential for minor lossage here if there is an open
+  ;; log buffer with a nonzero local value of vc-comment-ring-index.
+  (setq vc-comment-ring nil))
+
 ;; Random helper functions
 
 (defun vc-name (file)
@@ -162,8 +186,10 @@
 	(vc-file (and file (vc-name file)))
 	status)
     (set-buffer (get-buffer-create "*vc*"))
-    (make-local-variable 'vc-parent-buffer)
-    (setq vc-parent-buffer camefrom)
+    (set (make-local-variable 'vc-parent-buffer) camefrom)
+    (set (make-local-variable 'vc-parent-buffer-name)
+	 (concat " from " (buffer-name camefrom)))
+    
     (erase-buffer)
 
     ;; This is so that command arguments typed in the *vc* buffer will
@@ -330,11 +356,11 @@
       (if vc-initial-comment
 	  (setq vc-log-after-operation-hook
 		'vc-checkout-writeable-buffer-hook)
-	(vc-checkout-writeable-buffer)))
+	(vc-checkout-writeable-buffer file)))
 
      ;; if there is no lock on the file, assert one and get it
      ((not (setq owner (vc-locking-user file)))
-      (vc-checkout-writeable-buffer))
+      (vc-checkout-writeable-buffer file))
 
      ;; a checked-out version exists, but the user may not own the lock
      ((not (string-equal owner (user-login-name)))
@@ -346,7 +372,7 @@
        owner))
      
      ;; OK, user owns the lock on the file
-     (t (let (file-window)
+     (t
 	  (find-file file)
 
 	  ;; give luser a chance to save before checking in.
@@ -370,7 +396,7 @@
 
 	    ;; OK, let's do the checkin
 	    (vc-checkin file version comment)
-	    ))))))
+	    )))))
 
 (defun vc-next-action-dired (file rev comment)
   ;; We've accepted a log comment, now do a vc-next-action using it on all
@@ -378,7 +404,11 @@
   (set-buffer vc-parent-buffer)
   (dired-map-over-marks
    (save-window-excursion
-     (vc-next-action-on-file (dired-get-filename) nil comment)) nil t)
+     (let ((file (dired-get-filename)))
+       (message "Processing %s..." file)
+       (vc-next-action-on-file file nil comment)
+       (message "Processing %s...done" file)))
+   nil t)
   )
 
 ;; Here's the major entry point.
@@ -408,13 +438,15 @@
 or checkin operations, but ignored when doing checkouts.  Attempted
 lock steals will raise an error."
   (interactive "P")
-  (if vc-dired-mode
-      (let ((files (dired-get-marked-files)))
-	(if (null files)
-	    (find-file-other-window (dired-get-filename))
-	  (vc-start-entry nil nil nil
-			  "Enter a change comment."
-			  'vc-next-action-dired)))
+  (catch 'nogo
+    (if vc-dired-mode
+	(let ((files (dired-get-marked-files)))
+	  (if (= (length files) 1)
+	      (find-file-other-window (dired-get-filename))
+	    (vc-start-entry nil nil nil
+			    "Enter a change comment for the marked files."
+			    'vc-next-action-dired)
+	    (throw 'nogo))))
     (while vc-parent-buffer
       (pop-to-buffer vc-parent-buffer))
     (if buffer-file-name
@@ -423,9 +455,9 @@
 
 ;;; These functions help the vc-next-action entry point
 
-(defun vc-checkout-writeable-buffer ()
+(defun vc-checkout-writeable-buffer (&optional file)
   "Retrieve a writeable copy of the latest version of the current buffer's file."
-  (vc-checkout (buffer-file-name) t)
+  (vc-checkout (or file (buffer-file-name)) t)
   )
 
 ;;;###autoload
@@ -473,8 +505,9 @@
     (if comment
 	(set-buffer (get-buffer-create "*VC-log*"))
       (pop-to-buffer (get-buffer-create "*VC-log*")))
-    (make-local-variable 'vc-parent-buffer)
-    (setq vc-parent-buffer parent)
+    (set (make-local-variable 'vc-parent-buffer) parent)
+    (set (make-local-variable 'vc-parent-buffer-name)
+	 (concat " from " (buffer-name vc-parent-buffer)))
     (vc-mode-line (if file (file-name-nondirectory file) " (no file)"))
     (vc-log-mode)
     (setq vc-log-operation action)
@@ -483,9 +516,10 @@
     (if comment
 	(progn
 	  (erase-buffer)
-	  (if (not (eq comment t))
-	      (insert comment))
-	  (vc-finish-logentry))
+	  (if (eq comment t)
+	      (vc-finish-logentry t)
+	    (insert comment)
+	    (vc-finish-logentry nil)))
       (message "%s  Type C-c C-c when done." msg))))
 
 (defun vc-admin (file rev &optional comment)
@@ -514,7 +548,6 @@
       (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."))
-  (require 'sendmail)
   (pop-to-buffer (get-buffer-create "*VC-mail*"))
   (setq default-directory (expand-file-name "~/"))
   (auto-save-mode auto-save-default)
@@ -547,7 +580,7 @@
 ;;; Here is a checkin hook that may prove useful to sites using the
 ;;; ChangeLog facility supported by Emacs.
 (defun vc-comment-to-change-log (&optional file)
-  "Update change log from comments entered into VC for the current file.
+  "Update change log from VC change comments entered for the current file.
 Optional FILE specifies the change log file name; see `find-change-log'.
 See `vc-update-change-log'."
   (interactive)
@@ -558,24 +591,22 @@
 	  (vc-update-change-log
 	   (file-relative-name buffer-file-name))))))
 
-(defun vc-finish-logentry ()
+(defun vc-finish-logentry (&optional nocomment)
   "Complete the operation implied by the current log entry."
   (interactive)
-  (goto-char (point-max))
-  (if (not (bolp)) (newline))
-  ;; Append the contents of the log buffer to the comment ring
-  (save-excursion
-    (set-buffer (get-buffer-create "*VC-comment-ring*"))
-    (goto-char (point-max))
-    (set-mark (point))
-    (insert-buffer-substring "*VC-log*")
-    (if (and (not (bobp)) (not (= (char-after (1- (point))) ?\f)))
-	(insert-char ?\f 1))
-    (if (not (bobp))
-	(forward-char -1))
-    (exchange-point-and-mark)
-    ;; Check for errors
-    (vc-backend-logentry-check vc-log-file))
+  ;; Check and record the comment, if any.
+  (if (not nocomment)
+      (progn
+	(goto-char (point-max))
+	(if (not (bolp))
+	    (newline))
+	;; Comment too long?
+	(vc-backend-logentry-check vc-log-file)
+	;; Record the comment in the comment ring
+	(if (null vc-comment-ring)
+	    (setq vc-comment-ring (make-ring vc-maximum-comment-ring-size)))
+	(ring-insert vc-comment-ring (buffer-string))
+	))
   ;; OK, do it to it
   (if vc-log-operation
       (save-excursion
@@ -589,7 +620,6 @@
   (vc-error-occurred
    (delete-window (get-buffer-window "*VC-log*")))
   (kill-buffer "*VC-log*")
-  (bury-buffer "*VC-comment-ring*")
   ;; Now make sure we see the expanded headers
   (if buffer-file-name
 	(vc-resynch-window buffer-file-name vc-keep-workfiles t))
@@ -597,57 +627,65 @@
 
 ;; Code for access to the comment ring
 
-(defun vc-next-comment ()
-  "Fill the log buffer with the next message in the msg ring."
-  (interactive)
-  (erase-buffer)
-  (save-excursion
-    (set-buffer "*VC-comment-ring*")
-    (forward-page)
-    (if (= (point) (point-max))
-	(goto-char (point-min)))
-    (mark-page)
-    (append-to-buffer "*VC-log*" (point) (1- (mark)))
-    ))
+(defun vc-previous-comment (arg)
+  "Cycle backwards through comment history."
+  (interactive "*p")
+  (let ((len (ring-length vc-comment-ring)))
+    (cond ((<= len 0)
+	   (message "Empty comment ring")
+	   (ding))
+	  (t
+	   (erase-buffer)
+	   ;; Initialize the index on the first use of this command
+	   ;; so that the first M-p gets index 0, and the first M-n gets
+	   ;; index -1.
+	   (if (null vc-comment-ring-index)
+	       (setq vc-comment-ring-index
+		     (if (> arg 0) -1
+			 (if (< arg 0) 1 0))))
+	   (setq vc-comment-ring-index
+		 (ring-mod (+ vc-comment-ring-index arg) len))
+	   (message "%d" (1+ vc-comment-ring-index))
+	   (insert (ring-ref vc-comment-ring vc-comment-ring-index))))))
 
-(defun vc-previous-comment ()
-  "Fill the log buffer with the previous message in the msg ring."
-  (interactive)
-  (erase-buffer)
-  (save-excursion
-    (set-buffer "*VC-comment-ring*")
-    (if (= (point) (point-min))
-	(goto-char (point-max)))
-    (backward-page)
-    (mark-page)
-    (append-to-buffer "*VC-log*" (point) (1- (mark)))
-    ))
+(defun vc-next-comment (arg)
+  "Cycle forwards through comment history."
+  (interactive "*p")
+  (vc-previous-comment (- arg)))
 
-(defun vc-comment-search-backward (regexp)
-  "Fill the log buffer with the last message in the msg ring matching REGEXP."
-  (interactive "sSearch backward for: ")
-  (erase-buffer)
-  (save-excursion
-    (set-buffer "*VC-comment-ring*")
-    (if (= (point) (point-min))
-	(goto-char (point-max)))
-    (re-search-backward regexp nil t)
-    (mark-page)
-    (append-to-buffer "*VC-log*" (point) (1- (mark)))
-    ))
+(defun vc-comment-search-reverse (str)
+  "Searches backwards through comment history for substring match."
+  (interactive "sComment substring: ")
+  (if (string= str "")
+      (setq str vc-last-comment-match)
+    (setq vc-last-comment-match str))
+  (if (null vc-comment-ring-index)
+      (setq vc-comment-ring-index -1))
+  (let ((str (regexp-quote str))
+        (len (ring-length vc-comment-ring))
+	(n (1+ vc-comment-ring-index)))
+    (while (and (< n len) (not (string-match str (ring-ref vc-comment-ring n))))
+      (setq n (+ n 1)))
+    (cond ((< n len)
+	   (vc-previous-comment (- n vc-comment-ring-index)))
+	  (t (error "Not found")))))
 
-(defun vc-comment-search-forward (regexp)
-  "Fill the log buffer with the next message in the msg ring matching REGEXP."
-  (interactive "sSearch forward for: ")
-  (erase-buffer)
-  (save-excursion
-    (set-buffer "*VC-comment-ring*")
-    (if (= (point) (point-max))
-	(goto-char (point-min)))
-    (re-search-forward regexp nil t)
-    (mark-page)
-    (append-to-buffer "*VC-log*" (point) (1- (mark)))
-    ))
+(defun vc-comment-search-forward (str)
+  "Searches forwards through comment history for substring match."
+  (interactive "sComment substring: ")
+  (if (string= str "")
+      (setq str vc-last-comment-match)
+    (setq vc-last-comment-match str))
+  (if (null vc-comment-ring-index)
+      (setq vc-comment-ring-index 0))
+  (let ((str (regexp-quote str))
+        (len (ring-length vc-comment-ring))
+	(n vc-comment-ring-index))
+    (while (and (>= n 0) (not (string-match str (ring-ref vc-comment-ring n))))
+      (setq n (- n 1)))
+    (cond ((>= n 0)
+	   (vc-next-comment (- n vc-comment-ring-index)))
+	  (t (error "Not found")))))
 
 ;; Additional entry points for examining version histories
 
@@ -661,14 +699,23 @@
       (pop-to-buffer vc-parent-buffer))
   (if historic
       (call-interactively 'vc-version-diff)
+    (if (or (null buffer-file-name) (null (vc-name buffer-file-name)))
+	(error "There is no version-control master associated with this buffer."))
     (let ((file buffer-file-name)
 	  unchanged)
       (vc-buffer-sync)
       (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)
+	;; 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.
+	;; Unfortunately, this is just too painful to do.  The basic
+	;; problem is that the `old' file doesn't exist to be
+	;; visited.  This plays hell with numerous assumptions in
+	;; the diff.el and compile.el machinery.
 	(pop-to-buffer "*vc*")
-	(vc-backend-diff file nil)
 	(vc-shrink-to-fit)
 	(goto-char (point-min))
 	)
@@ -687,8 +734,9 @@
   (if (file-directory-p file)
       (let ((camefrom (current-buffer)))
 	(set-buffer (get-buffer-create "*vc-status*"))
-	(make-local-variable 'vc-parent-buffer)
-	(setq vc-parent-buffer camefrom)
+	(set (make-local-variable 'vc-parent-buffer) camefrom)
+	(set (make-local-variable 'vc-parent-buffer-name)
+	     (concat " from " (buffer-name camefrom)))
 	(erase-buffer)
 	(insert "Diffs between "
 		(or rel1 "last version checked in")
@@ -773,6 +821,24 @@
   (setq vc-dired-mode t)
   (setq vc-mode " under VC"))
 
+(defun vc-dired-reformat-line (x)
+  ;; Hack a directory-listing line, plugging in locking-user info in
+  ;; place of the user and group info. Should have the beneficial
+  ;; side-effect of shortening the listing line. Each call starts with
+  ;; point immediately following the dired mark area on the line to be
+  ;; hacked.
+  ;;
+  ;; Simplest possible one:
+  ;; (insert (concat x "\t")))
+  ;;
+  ;; This code, like dired, assumes UNIX -l format.
+  (forward-word 1)	;; skip over any extra field due to -ibs options
+  (if x (setq x (concat "(" x ")")))
+  (if (re-search-forward "\\([0-9]+ \\).................\\( .*\\)" nil 0)
+      (let ((rep (substring (concat x "                 ") 0 9)))
+	(replace-match (concat "\\1" rep "\\2") t)))
+  )
+
 ;;;###autoload
 (defun vc-directory (verbose)
   "Show version-control status of all files under the current directory."
@@ -780,7 +846,8 @@
   (let (nonempty
 	(dl (length default-directory))
 	(filelist nil) (userlist nil)
-	dired-buf)
+	dired-buf
+	dired-buf-mod-count)
     (vc-file-tree-walk
      (function (lambda (f)
 		 (if (vc-registered f)
@@ -789,22 +856,26 @@
 			    (setq filelist (cons (substring f dl) filelist))
 			    (setq userlist (cons user userlist))))))))
     (save-excursion
-     (dired (cons default-directory (nreverse filelist)))
-     (setq dired-buf (current-buffer))
-     (setq nonempty (not (zerop (buffer-size)))))
+      ;; This uses a semi-documented featre of dired; giving a switch
+      ;; argument forces the buffer to refresh each time.
+      (dired
+       (cons default-directory (nreverse filelist))
+       dired-listing-switches)
+      (setq dired-buf (current-buffer))
+      (setq nonempty (not (zerop (buffer-size)))))
     (if nonempty
 	(progn
 	  (pop-to-buffer dired-buf)
 	  (vc-dired-mode)
 	  (goto-char (point-min))
 	  (setq buffer-read-only nil)
+	  (forward-line 1)	;; Skip header line
 	  (mapcar
-	   (function (lambda (x)
-		       (forward-char 2)	;; skip dired's mark area
-		       (if x (insert x))
-		       (insert "\t")
-		       (forward-line 1)))
-	   (cons "\t" (nreverse userlist)))
+	   (lambda (x)
+	     (forward-char 2)	;; skip dired's mark area
+	     (vc-dired-reformat-line x)
+	     (forward-line 1))	;; go to next line
+	   (nreverse userlist))
 	  (setq buffer-read-only t)
 	  (goto-char (point-min))
 	  )
@@ -1269,7 +1340,7 @@
 
 (defun vc-backend-logentry-check (file)
   (vc-backend-dispatch file
-   (if (>= (- (region-end) (region-beginning)) 512)	;; SCCS
+   (if (>= (buffer-size) 512)	;; SCCS
        (progn
 	 (goto-char 512)
 	 (error
@@ -1414,8 +1485,8 @@
 
 \\[vc-next-comment]	replace region with next message in comment ring
 \\[vc-previous-comment]	replace region with previous message in comment ring
-\\[vc-search-comment-reverse]	search backward for regexp in the comment ring
-\\[vc-search-comment-forward]	search backward for regexp in the comment ring
+\\[vc-comment-search-reverse]	search backward for regexp in the comment ring
+\\[vc-comment-search-forward]	search backward for regexp in the comment ring
 
 Entry to the change-log submode calls the value of text-mode-hook, then
 the value of vc-log-mode-hook.
@@ -1457,6 +1528,7 @@
   (setq mode-name "VC-Log")
   (make-local-variable 'vc-log-file)
   (make-local-variable 'vc-log-version)
+  (make-local-variable 'vc-comment-ring-index)
   (set-buffer-modified-p nil)
   (setq buffer-file-name nil)
   (run-hooks 'text-mode-hook 'vc-log-mode-hook)
@@ -1468,7 +1540,7 @@
   (setq vc-log-entry-mode (make-sparse-keymap))
   (define-key vc-log-entry-mode "\M-n" 'vc-next-comment)
   (define-key vc-log-entry-mode "\M-p" 'vc-previous-comment)
-  (define-key vc-log-entry-mode "\M-r" 'vc-comment-search-backward)
+  (define-key vc-log-entry-mode "\M-r" 'vc-comment-search-reverse)
   (define-key vc-log-entry-mode "\M-s" 'vc-comment-search-forward)
   (define-key vc-log-entry-mode "\C-c\C-c" 'vc-finish-logentry)
   )