;;; 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.
;; 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.
(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
(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
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)
(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))
(forward-line -1)
(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.
;; 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)))
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)
(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*"))
(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)
"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."
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.
(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.
(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
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)
- (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
+ ;; 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
;; 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.
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.
(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))
))
(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)
(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
"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.
(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
(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)
)
;; 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))))))
(let ((filename (or workfile file)))
(message "Checking out %s..." filename)
(vc-backend-dispatch file
- (vc-do-command 0 "get" file ;; SCCS
- (if writable "-e")
- (if workfile (concat "-G" workfile))
- (and rev (concat "-r" (vc-lookup-triple file rev))))
+ (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"
+ (format "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 ((default-modes (default-file-modes))
- (vc-modes (logior (file-modes (vc-name file))
+ (let ((vc-modes (logior (file-modes (vc-name file))
(if writable 128 0)))
(failed t))
(unwind-protect
(progn
- (set-default-file-modes vc-modes)
(vc-do-command
0 "/bin/sh" file "-c"
- "filename=$1; shift; exec co \"$@\" >$filename"
+ (format "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))
- (set-default-file-modes default-modes)
(and failed (file-exists-p filename) (delete-file filename))))
(vc-do-command 0 "co" file
(if writable "-l")
)
)
-(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."