;;; generic-sc.el --- generic interface to source control systems

;; Copyright (C) 1992, 1993, 1994, 1997 Free Software Foundation, Inc.

;; Author: devin@lucid.com
;; Maintainer: Unmaintained
;; Keywords: tools, unix

;; This file is part of XEmacs.

;; XEmacs is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; XEmacs is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with XEmacs; see the file COPYING.  If not, write to the 
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Synched up with: Not in FSF.

;;; Commentary:

;; The generic interface provide a common set of functions that can be
;; used to interface with a source control system like SCCS, RCS or
;; CVS.  
;; 
;; You chose which source control system to use by calling sc-mode
;; 
;; The module is based on the sccs.el mode of Eric S. Raymond
;; (eric@snark.thyrsus.com) which was distantly derived from an rcs
;; mode written by Ed Simpson ({decvax, seismo}!mcnc!duke!dukecdu!evs)
;; in years gone by and revised at MIT's Project Athena.

;;; Code:

;; This can be customized by the user

(defgroup generic-sc nil
  "Generic interface to source control systems"
  :prefix "sc-"
  :group 'tools)


(defcustom sc-diff-command '("diff")
  "*The command/flags list to be used in constructing diff commands."
  :type '(repeat string)
  :group 'generic-sc)

;; Duplicated from pcl-cvs.
(defvar cvs-program "cvs"
  "*The command name of the cvs program.")

(defcustom sc-mode-expert ()
  "*Treat user as expert; suppress yes-no prompts on some things."
  :type 'boolean
  :group 'generic-sc)

(defcustom sc-max-log-size 510
  "*Maximum allowable size of a source control log message."
  :type 'integer
  :group 'generic-sc)

(defcustom sc-ccase-comment-on '(checkout checkout-dir checkin-dir rename
				       new-brtype new-branch checkin-merge
				       create-label label-sources)
  "*Operations on which comments would be appreciated.
We check the values checkout, checkout-dir, checkin-dir,
rename, new-brtype, new-branch, create-label,
and label-sources as symbols."
  :type '(repeat symbol)
  :group 'generic-sc)

(defvar sc-ccase-reserve nil
  "Whether to reserve checkouts or not. By default, this is nil - don't.
Other values are t - do, and anything else, eg. 'ask - ask.")

;; default keybindings
(defvar sc-prefix-map (lookup-key global-map "\C-xv"))
(if (not (keymapp sc-prefix-map))
    (progn
      (setq sc-prefix-map (make-sparse-keymap))
      (define-key global-map "\C-xv" sc-prefix-map)
      (define-key sc-prefix-map "v" 'sc-next-operation)
      (define-key sc-prefix-map "=" 'sc-show-changes)
      (define-key sc-prefix-map "l" 'sc-show-history)
      (define-key sc-prefix-map "p" 'sc-visit-previous-revision)
      (define-key sc-prefix-map "u" 'sc-revert-file)
      (define-key sc-prefix-map "d" 'sc-list-registered-files)
      (define-key sc-prefix-map "\C-d" 'sc-update-directory)
      (define-key sc-prefix-map "\C-r" 'sc-rename-file)
      ))


;;; The user does not change these
(defvar sc-generic-name ""
  "Name of the source control system used.  Is displayed in the modeline.")

(defvar sc-mode-line-string ()
  "Revision number to show in the mode line")

(defvar sc-generic-log-buf ()
  "Buffer for entering log message")

(defvar sc-log-entry-keymap ()
  "Additional keybindings used when entering the log message")

(defvar sc-can-hack-dir ()
  "Does the SC system allow users to play directly with directories")

(defvar sc-ccase-mfs-prefixes ()
  "Prefixes known to the system to be MFS ... ignore all others")

(defmacro sc-chmod (perms file)
  (list 'call-process "chmod" nil nil nil perms file))

(defmacro error-occurred (&rest body)
  (list 'condition-case nil (cons 'progn (append body '(nil))) '(error t)))


;;; User level functions
(defun sc-next-operation (verbose)
  "Do the next logical source-control operation on the file in the current buffer.
The current subdirectory must be under source control.
   If the file is not already registered with the source control, this registers it 
and checks it out.
   If the file is registered and not locked by anyone, this checks it out.
   If the file is registered and locked by the calling user, this pops up a
buffer for creation of a log message, then checks the file in.
A read-only copy of the changed file is left in place afterwards.
   If the file is registered and locked by someone else, an error message is
returned indicating who has locked it."
  (interactive "P")
  (if (not buffer-file-name)
      (error "There is no file associated with buffer %s" (buffer-name)))
  (let* (revision
	 (file buffer-file-name)
	 (lock-info (sc-lock-info file))
	 (sc-generic-log-buf
	  (get-buffer-create (format "*%s-Log*" sc-generic-name))))
    (if (eq lock-info 'na)
	(error "The file associated with buffer %s is not registered" (buffer-name)))
	
    ;; if file is not registered register it and set lock-info to show it's not locked
    (if (not lock-info)
	(progn
	  (sc-register-file verbose)
	  (setq lock-info (list () ()))))
	
    (cond ((not (car lock-info))
	   ;; if there is no lock on the file, assert one and get it
	   (sc-check-out file t)
	   (revert-buffer nil t)
	   (sc-mode-line))
	      
	  ((and (not (equal sc-generic-name "CCase"))
	       (not (equal (car lock-info) (user-login-name))))
	   ;; file is locked by someone else
	   (error "Sorry, %s has that file locked." (car lock-info)))

	  (t 
	   ;; OK, user owns the lock on the file 
	   ;; if so, give user a chance to save before delta-ing.
	   (if (and (buffer-modified-p)
		    (or
		     sc-mode-expert
		     (y-or-n-p (format "%s has been modified. Write it out? "
				       (buffer-name)))))
	       (save-buffer))
	       
	   (setq revision (car (cdr lock-info)))
	       
	   ;; user may want to set nonstandard parameters
	   (if verbose
	       (if (or sc-mode-expert
		       (y-or-n-p 
			(format "revision: %s  Change revision level? "
				revision)))
		   (setq revision (read-string "New revision level: "))))
	       
	   ;; OK, let's do the delta
	   (let ((buffer (sc-temp-buffer)))
	     (if (save-window-excursion
		   ;; this excursion returns t if the new version was saved OK
		   (pop-to-buffer buffer)
		   (erase-buffer)
		   (set-buffer-modified-p nil)
		   (sc-log-entry-mode)
		   (message 
		    "Enter log message. Type C-c C-c when done, C-c ? for help.")
		   (prog1
		       (and (not (error-occurred (recursive-edit)))
			    (not (error-occurred
				  (sc-check-in file revision
					       (buffer-string)))))
		     (setq buffer-file-name nil)
		     (bury-buffer buffer)))
		   
		 ;; if the save went OK do some post-checking
		 (if (buffer-modified-p)
		     (error
		      "Checked-in version of file does not match buffer!")
		   (revert-buffer nil t)
		   (sc-mode-line)
		   (run-hooks 'sc-check-in-ok))))))))

(defun sc-insert-last-log ()
  "Insert the log message of the last check in at point."
  (interactive)
  (insert-buffer sc-generic-log-buf))

(defun sc-abort-check-in ()
  "Abort a source control check-in command."
  (interactive)
  (if (or sc-mode-expert (y-or-n-p "Really Abort Check-in? "))
      (progn
	(delete-window)
	(abort-recursive-edit))))

(defun sc-log-exit ()
  "Proceed with checkin with the contents of the current buffer as message."
  (interactive)
  (if (< (buffer-size) sc-max-log-size)
      (progn
	(copy-to-buffer sc-generic-log-buf (point-min) (point-max))
	(exit-recursive-edit)
	(delete-window))
    (goto-char sc-max-log-size)
    (error
     "Log must be less than %d characters. Point is now at char %d."
     sc-max-log-size (point))))


;;; Functions to look at the edit history
(defun sc-show-changes (arg)
  "Compare the version being edited with the last checked-in revision.
With a prefix argument prompt for revision to compare with."
  (interactive "P")
  ;; check that the file is not modified
  (if (and (buffer-modified-p)
	   (or
	    sc-mode-expert
	    (y-or-n-p (format "%s has been modified. Write it out? "
			      (buffer-name)))))
      (save-buffer))
  (let* ((revision (and arg (read-string "Revision to compare against: ")))
	 (file buffer-file-name)
	 (name (file-name-nondirectory file))
	 (old (sc-get-version-in-temp-file file revision))
	 (buffer (sc-temp-buffer))
	 status)
    (save-excursion
      (set-buffer buffer)
      (erase-buffer)
      (setq default-directory (file-name-directory file))
      (setq status
	    (apply 'call-process (car sc-diff-command) () t ()
		   (append (cdr sc-diff-command) (list old) (list file)))))
    (if (not (or (eq 0 status) (eq 1 status))) ; see man diff.1
	(progn
	  (display-buffer buffer)
	  (error "diff FAILED")))
    (delete-file old)
    (save-excursion
      (set-buffer buffer)
      (goto-char (point-min))
      (if (equal (point-min) (point-max))
	  (insert
	   (format "No changes to %s since last update."
		   (file-name-nondirectory file)))
	(insert "==== Diffs for " file "\n")
	(insert "==== ")
	(mapcar '(lambda (i) (insert i " ")) sc-diff-command)
	(insert name "<" (or revision "current") ">" " " name "\n\n")))
    (display-buffer buffer)))

(defun sc-show-revision-changes ()
  "Prompt for a revision to diff against."
  (interactive)
  (sc-show-changes 4))

(defun sc-version-diff-file (file rel1 rel2)
  "For FILE, report diffs between two revisions REL1 and REL2 of it."
  (interactive "fFile: \nsOlder version: \nsNewer version: ")
  (if (string-equal rel1 "") (setq rel1 nil))
  (if (string-equal rel2 "") (setq rel2 nil))
  (let ((buffer (sc-temp-buffer)))
    (set-buffer buffer)
    (erase-buffer)
    (let ((v1 (sc-get-version-in-temp-file file rel1))
	  (v2 (if rel2 (sc-get-version-in-temp-file file rel2) file)))
      (and v1
	   v2
	   (unwind-protect
	       (apply 'call-process (car sc-diff-command) nil t t
		      (append (cdr sc-diff-command) (list v1) (list v2)))))
      (condition-case () (delete-file v1) (error nil))
      (if rel2
	  (condition-case () (delete-file v2) (error nil)))
      (set-buffer-modified-p nil)
      (goto-char (point-min))
      (if (equal (point-min) (point-max))
	  (message
	   (format "No changes to %s between %s and %s." file rel1 rel2))
	(display-buffer buffer)))))

(defun sc-show-history ()
  "List the edit history of the current buffer."
  (interactive)
  (let ((file buffer-file-name))
    (if (not file)
	(error "There is no file associated with buffer %s" (buffer-name)))
    (if (not (sc-lock-info file))
	(error "The file is not registered in the source control system"))
    (let ((buffer (sc-temp-buffer)))
      (save-excursion
	(set-buffer buffer)
	(erase-buffer)
	(sc-history file)
	(goto-char (point-min)))
      (display-buffer buffer))))

(defun sc-visit-previous-revision (revision)
  "Show a previous revision of the current file"
  (interactive "sShow previous revision number: ")
  (let ((file buffer-file-name))
    (if (not file)
	(error "There is no file associated with buffer %s" (buffer-name)))
    (let ((other-file (sc-get-version-in-temp-file file revision))
	  (buffer-name (concat (file-name-nondirectory file)
			       "<" sc-generic-name " " revision ">")))
      (pop-to-buffer (get-buffer-create buffer-name))
      (erase-buffer)
      (insert-file other-file)
      ;; get the same major mode as the original file
      (setq buffer-file-name file)
      (normal-mode)
      (setq buffer-file-name ())
      (set-buffer-modified-p ())
      (toggle-read-only)
      (delete-file other-file))))

(defun sc-revert-file ()
  "Revert the current buffer's file back to the last saved version."
  (interactive)
  (let ((file buffer-file-name))
    (if (y-or-n-p (format "Revert file %s to last checked-in revision?" file))
	(progn
	  (sc-revert file)
	  (revert-buffer nil t)
	  (sc-mode-line)))))

;; Functions to get directory level information

(defun sc-list-all-locked-files (arg)
  "List all files currently locked under the revision control system.
With prefix arg list only the files locked by the user."
  (interactive "P")
  (let* ((locker (and arg (user-login-name)))
	 (buffer (sc-tree-walk 'sc-list-file-if-locked locker)))
    (save-excursion
      (set-buffer buffer)
      (goto-char (point-min))
      (if (= (point-min) (point-max))
	  (insert "No files locked ")
	(insert "Files locked "))
      (if locker
	  (insert "by " locker " "))
      (insert "in " default-directory "\n\n"))
    (display-buffer buffer)))
      
(defun sc-list-locked-files ()
  "List all files currently locked by me"
  (interactive)
  (sc-list-all-locked-files 4))

(defun sc-list-registered-files ()
  "List all files currently registered under the revision control system."
  (interactive)
  (let ((buffer (sc-tree-walk 'sc-list-file)))
    (save-excursion
      (set-buffer buffer)
      (if (= (point-min) (point-max))
	  (insert "No files registered in " sc-generic-name
		  " in " default-directory)
	(goto-char (point-min))
	(insert "Files registered in " sc-generic-name " in " default-directory
		"\n\n")))
    (display-buffer buffer)))
       
(defun sc-update-directory ()
  "Updates the current directory by getting the latest copies of the files"
  (interactive)
  (save-some-buffers)
  (let ((buffer (sc-tree-walk 'sc-update-file)))
    (save-excursion
      (set-buffer buffer)
      (goto-char (point-min))
      (if (= (point-min) (point-max))
	  (insert "No files needed to be updated in " default-directory "\n\n")
	(insert "Files updated in " default-directory "\n\n")))
    (display-buffer buffer)))

;; Miscellaneous other entry points

(defun sc-register-file (verbose)
  "Register the file visited by the current buffer into source control.
Prefix argument register it under an explicit revision number."
  (interactive "P")
  (let ((file buffer-file-name))
    (if (not file)
	(error "There is no file associated with buffer %s" (buffer-name)))
    (let ((lock-info (sc-lock-info file))
	  (revision ()))
      (if lock-info
	  (error "This file is already registered into %s" sc-generic-name))
      ;; propose to save the file if it's modified
      (if (and (buffer-modified-p)
	       (or
		sc-mode-expert
		(y-or-n-p (format "%s has been modified. Write it out? "
				  (buffer-name)))))
	  (save-buffer))
      ;; get the revision number
      (if verbose
	  (setq revision (read-string "Initial Revision Number: ")))
      (sc-register file revision)
      (revert-buffer nil t)
      (sc-mode-line))))

(defun sc-rename-file (old new)
  "Rename a file, taking its source control archive with it."
  (interactive "fOld name: \nFNew name: ")
  (let ((owner (sc-locking-user old)))
    (if (and owner (not (string-equal owner (user-login-name))))
	(error "Sorry, %s has that file checked out" owner)))
  (if sc-can-hack-dir
      (rename-file old new t))
  (sc-rename old new))

(defun sc-rename-this-file (new)
  "Rename the file of the current buffer, taking its source control archive with it"
  (interactive "FNew name: ")
  (if (and (buffer-modified-p)
	   (y-or-n-p (format "%s has been modified. Write it out? "
			     (buffer-name))))
      (save-buffer))
  (sc-rename-file buffer-file-name new)
  (let ((old-buffer (current-buffer))
	(new-buffer (find-file-noselect new)))
    (set-window-buffer (selected-window) new-buffer)
    (pop-to-buffer (current-buffer))
    (bury-buffer old-buffer)))


;;; Mode independent functions 
;;; All those sc-... functions FUNCALL the corresponding sc-generic-... function.  
;;; The variables are set to functions that do the SCCS, RCS or CVS commands 
;;; depending on the mode chosen.

(defvar sc-generic-lock-info ()
  "Function to implement sc-lock-info")

(defun sc-lock-info (file)
  "Return a list of the current locker and current locked revision for FILE.
Returns NIL if FILE is not registered in the source control system.
Return (NIL NIL) if FILE is registered but not locked.
Return (locker revision) if file is locked."
  (funcall sc-generic-lock-info file))


(defvar sc-generic-register ()
  "Function to implement sc-register")

(defun sc-register (file revision)
  "Register FILE under source control with initial revision REVISION."
  (funcall sc-generic-register file revision))


(defvar sc-generic-check-out ()
  "Function to implement sc-check-out")

(defun sc-check-out (file lockp)
  "Checks out the latest version of FILE.  
If LOCKP is not NIL, FILE is also locked."
  (funcall sc-generic-check-out file lockp))


(defvar sc-generic-get-version ()
  "Function to implement sc-get-version")

(defun sc-get-version (file buffer revision)
  "Insert a previous revison of FILE in BUFFER.  
REVISION is the revision number requested."
  (funcall sc-generic-get-version file buffer revision))


(defvar sc-generic-check-in ()
  "Function to implement sc-check-in")

(defun sc-check-in (file revision message)
  "Check in FILE with revision REVISION.
MESSAGE is a string describing the changes."
  (funcall sc-generic-check-in file revision message))


(defvar sc-generic-history ()
  "Function to implement sc-history")

(defun sc-history (file)
  "Insert the edit history of FILE in the current buffer."
  (funcall sc-generic-history file))


(defvar sc-generic-tree-list ()
  "Function to implement sc-tree-list")

(defun sc-tree-list ()
  "List in the current buffer the files registered in the source control system"
  (funcall sc-generic-tree-list))
  

(defvar sc-generic-new-revision-p ()
  "Function to implement sc-new-revision-p")

(defun sc-new-revision-p (file)
  "True if a new revision of FILE was checked in since we last got a copy of it"
  (funcall sc-generic-new-revision-p file))


(defvar sc-generic-revert ()
  "Function to implement sc-revert")

(defun sc-revert (file)
  "Cancel a check out of FILE and get back the latest checked in version"
  (funcall sc-generic-revert file))


(defvar sc-generic-rename ()
  "Function to implement sc-rename")

(defun sc-rename (old new)
  "Rename the source control archives for OLD to NEW"
  (funcall sc-generic-rename old new))


(defvar sc-menu ()
  "Menu to use")
  

;;; Utilities functions
(defun sc-do-command (buffer message command file sc-file &rest flags)
  "Execute a command, notifying the user and checking for errors."
  (setq file (expand-file-name file))
  (message "Running %s on %s..." message file)
  (let ((status
	 (save-excursion
	   (set-buffer (get-buffer-create buffer))
	   (erase-buffer)
	   (setq flags (append flags (and file (list sc-file))))
	   (setq flags (delq () flags))
	   (let ((default-directory (file-name-directory (or file "./"))))
	     (eq (apply 'call-process command nil t nil flags) 0)))))
    (if status
	(message "Running %s...OK" message)
      (save-excursion
	(set-buffer buffer)
	(goto-char (point-min))
	(insert command)
	(mapcar '(lambda (i) (insert " " i)) flags)
	(insert "\n\n")
	(goto-char (point-min)))
      (display-buffer buffer)
      (error "Running %s...FAILED" message))))

(defun sc-enter-comment ()
  "Enter a comment. Return it as a string."
  (let ((buffer (sc-temp-buffer)))
    (setq sc-generic-log-buf
	  (get-buffer-create (format "*%s-Log*" sc-generic-name)))
    (save-window-excursion
      ;; this excursion returns t if the new version was saved OK
      (pop-to-buffer buffer)
      (erase-buffer)
      (set-buffer-modified-p nil)
      (sc-log-entry-mode)
      (message 
	   "Enter log message. Type C-c C-c when done, C-c ? for help.")
      (prog1
	  (and (not (error-occurred (recursive-edit)))
	       (let ((bs (buffer-string)))
		 (if (> (length bs) 0) bs)))
	(setq buffer-file-name nil)
	(bury-buffer buffer)))))

(defun sc-locking-user (file)
  "Return the login name of the locker of FILE.  Return nil if FILE is not locked"
  (car (sc-lock-info file)))

(defun sc-locked-revision (file)
  "Return the revision number currently locked for FILE, nil if FILE is not locked."
  (car (cdr (sc-lock-info file))))

(defun sc-mode-line ()
  "Set the mode line for the current buffer.
FILE is the file being visited."
  (let* ((file buffer-file-name)
	 (lock-info (sc-lock-info file)))
    ;; ensure that the global mode string is not NIL
    (or global-mode-string (setq global-mode-string '("")))
    ;; ensure that our variable is in the global-mode-string
    (or (memq 'sc-mode-line-string global-mode-string)
	(setq global-mode-string
	      (append global-mode-string '(sc-mode-line-string))))
    (make-local-variable 'sc-mode-line-string)
    (setq sc-mode-line-string
	  (cond ((or
		  (eq lock-info 'na)
		  (null lock-info))     ())
		((null (car lock-info))
		 (format " <%s:>" sc-generic-name))
		((equal (car lock-info) (user-login-name))
		 (format " <%s: %s>" sc-generic-name (car (cdr lock-info))))
		(t
		 (format " <%s: %s>" sc-generic-name (car lock-info)))))))

(defun sc-temp-buffer ()
  "Return a temporary buffer to use for output"
  (get-buffer-create (format "*%s*" sc-generic-name)))

(defun sc-tree-walk (func &rest args)
  "Apply FUNC to the files registered in the source control system.
FUNC is passed the file path and ARGS."
  (let* ((buffer-name (format "*%s directory*" sc-generic-name))
	 (buffer (get-buffer-create buffer-name))
	 (dir default-directory)
	 files)
    ;; recreate the directory buffer in the right directory
    (save-excursion
      (set-buffer buffer)
      (erase-buffer)
      (setq default-directory dir)
      ;; get a list of all the registered files
      (sc-tree-list)
      ;; remove the "not found" messages
      (goto-char (point-min))
      (while (search-forward "not found" () t)
	(beginning-of-line 1)
	(kill-line 1))
      ;; check if any file is listed
      (if (= (point-min) (point-max))
	  (error "No registered files under %s" default-directory))
      ;; build the list of files
      (goto-char (point-min))
      (setq files ())
      (while (not (eobp))
	(let ((file
	       (buffer-substring (point) (progn (end-of-line) (point)))))
	  (setq files (cons file files)))
	(forward-line 1))
      (setq files (nreverse files))
      ;; let the function output information in the buffer
      (erase-buffer))
    (display-buffer buffer)
    ;; apply the function
    (save-excursion
      (set-buffer buffer)
      (while files
	(apply func (car files) args)
	(setq files (cdr files)))
      buffer)))
  
(defun sc-get-version-in-temp-file (file revision)
  "For the given FILE, retrieve a copy of the version with given REVISION.
The text is retrieved into a tempfile.  Return the tempfile name."
  (let* ((oldversion
	  (make-temp-name
	   (concat (or (ccase-protect-expanded-name revision) "current")
		   "-"
		   (file-name-nondirectory file)
		   "-")))
	 (vbuf (get-buffer-create oldversion)))
    (sc-get-version file vbuf revision)
    (save-excursion
      (set-buffer vbuf)
      (write-region (point-min) (point-max) oldversion t 0))
    (kill-buffer vbuf)
    (sc-chmod "-w" oldversion)
    oldversion))

;; Functions used to get directory level information

(defun sc-insert-file-lock-info (file lock-info)
  (insert (car lock-info) ":" (car (cdr lock-info)))
  (indent-to-column 16 1)
  (insert (file-name-nondirectory file) "\n"))
  
(defun sc-list-file-if-locked (file &optional arg)
   "List all files underneath the current directory matching a prefix type."
   (let ((lock-info (sc-lock-info file)))
     (if (and lock-info
	      (car lock-info)
	      (or (null arg) (equal arg (car lock-info))))
	 (progn
	   (sc-insert-file-lock-info file lock-info)
	   (sit-for 0)))))

(defun sc-list-file (file)
  (let ((lock-info (sc-lock-info file)))
    (cond ((eq lock-info 'na)
	   (indent-to-column 16 1)
	   (insert (file-name-nondirectory file) "\n"))
	  ((car lock-info)
	   (sc-insert-file-lock-info file lock-info))
	  ((sc-new-revision-p file)
	   (insert "needs update")
	   (indent-to-column 16 1)
	   (insert (file-name-nondirectory file) "\n"))
	  (t
	   (indent-to-column 16 1)
	   (insert (file-name-nondirectory file) "\n")))
    (sit-for 0)))

;;; Function to update one file from the archive
(defun sc-update-file (file)
  "get the latest version of the file if a new one was checked-in"
  (if (sc-new-revision-p file)
      (let ((file-name (file-name-nondirectory file)))
	;; get the latest copy
	(rename-file (sc-get-version-in-temp-file file nil) file t)
	(let ((b (get-file-buffer file)))
	  (if b
	      (save-excursion
		(set-buffer b)
		(revert-buffer nil t)
		(sc-mode-line))))
	;; show the file was updated
	(insert "updated")
	(indent-to-column 16 1)
	(insert file-name "\n")
	(sit-for 0))))

;; Set up key bindings for use while editing log messages

(if sc-log-entry-keymap
    nil
  (setq sc-log-entry-keymap (make-sparse-keymap))
  (define-key sc-log-entry-keymap "\C-ci" 'sc-insert-last-log)
  (define-key sc-log-entry-keymap "\C-c\C-i" 'sc-insert-last-log)
  (define-key sc-log-entry-keymap "\C-ca" 'sc-abort-check-in)
  (define-key sc-log-entry-keymap "\C-c\C-a" 'sc-abort-check-in)
  (define-key sc-log-entry-keymap "\C-c\C-c" 'sc-log-exit)
  (define-key sc-log-entry-keymap "\C-x\C-s" 'sc-log-exit))

(defvar sc-mode-hook nil
  "*Function or functions to run on entry to sc-mode.")

(defvar sc-mode ()
  "The currently active source control mode.  Use M-x sc-mode to set it")

;;;###autoload
(defun sc-mode (system)
  "Toggle sc-mode.
SYSTEM can be sccs, rcs or cvs.
Cvs requires the pcl-cvs package.

The following commands are available
\\[sc-next-operation]	perform next logical source control operation on current file
\\[sc-show-changes]	compare the version being edited with an older one
\\[sc-version-diff-file]	compare two older versions of a file
\\[sc-show-history]		display change history of current file
\\[sc-visit-previous-revision]	display an older revision of current file
\\[sc-revert-file]		revert buffer to last checked-in version
\\[sc-list-all-locked-files]		show all files locked in current directory
\\[sc-list-locked-files]		show all files locked by you in current directory
\\[sc-list-registered-files]		show all files under source control in current directory
\\[sc-update-directory]		get fresh copies of files checked-in by others in current directory
\\[sc-rename-file]		rename the current file and its source control file


While you are entering a change log message for a check in, sc-log-entry-mode
will be in effect.

Global user options:
    sc-diff-command	A list consisting of the command and flags
			to be used for generating context diffs.
    sc-mode-expert	suppresses some conformation prompts,
			notably for delta aborts and file saves.
    sc-max-log-size	specifies the maximum allowable size
			of a log message plus one.


When using SCCS you have additional commands and options

\\[sccs-insert-headers]		insert source control headers in current file

When you generate headers into a buffer using \\[sccs-insert-headers],
the value of sc-insert-headers-hook is called before insertion. If the
file is recognized a C or Lisp source, sc-insert-c-header-hook or
sc-insert-lisp-header-hook is called after insertion respectively.

    sccs-headers-wanted	which %-keywords to insert when adding
			headers with C-c h
    sccs-insert-static	if non-nil, keywords inserted in C files
			get stuffed in a static string area so that
			what(1) can see them in the compiled object code.

When using CVS you have additional commands

\\[sc-cvs-update-directory]	update the current directory using pcl-cvs
\\[sc-cvs-file-status]		show the CVS status of current file
"
  (interactive
   (if sc-mode
       '(())
     (list
      (intern
	(read-string "Turn on source control mode on for: " "SCCS")))))
  (cond ((eq system ())
	 (remove-hook 'find-file-hooks 'sc-mode-line)
	 (delete-menu-item (list sc-generic-name))
	 (remove-hook 'activate-menubar-hook 'sc-sensitize-menu)
	 (setq sc-mode ()))
	(sc-mode
	 (sc-mode ())
	 (sc-mode system))
	(t
	 (setq system (intern (upcase (symbol-name system))))
	 (let ((f (intern (format "sc-set-%s-mode" system))))
	   (if (not (fboundp f))
	       (error
		"No source control interface for \"%s\".  \
Please use SCCS, RCS, CVS, or Atria."
		system)
	     (funcall f)
	     (add-hook 'find-file-hooks 'sc-mode-line)
	     (add-submenu '() (cons sc-generic-name sc-menu))
	     (add-hook 'activate-menubar-hook 'sc-sensitize-menu)
	     (run-hooks 'sc-mode-hook)
	     (setq sc-mode system))))))

(defun sc-log-entry-mode ()
  "Major mode for editing log message.

These bindings are available when entering the log message
\\[sc-log-exit]		proceed with check in, ending log message entry
\\[sc-insert-last-log]		insert log message from last check-in
\\[sc-abort-check-in]		abort this check-in

Entry to the change-log submode calls the value of text-mode-hook, then
the value sc-log-entry-mode-hook.
"
  (interactive)
  (set-syntax-table text-mode-syntax-table)
  (use-local-map sc-log-entry-keymap)
  (setq local-abbrev-table text-mode-abbrev-table)
  (setq major-mode 'sc-log-entry-mode)
  (setq mode-name "Source Control Change Log Entry")
  (run-hooks 'text-mode-hook 'sc-log-entry-mode-hook))



;;; SCCS specific part

;; Find a reasonable default for the SCCS bin directory
(defvar sccs-bin-directory
  (cond ((file-executable-p "/usr/sccs/unget") "/usr/sccs")
	((file-executable-p "/usr/bin/unget") "/usr/bin")
	((file-directory-p "/usr/sccs") "/usr/sccs")
	((file-directory-p "/usr/bin/sccs") "/usr/bin/sccs")
	(t "/usr/bin"))
  "*Directory where to find the sccs executables")

(defvar sccs-headers-wanted '("\%\W\%")
  "*SCCS header keywords to be inserted when sccs-insert-header is executed.")

(defvar sccs-insert-static t
  "*Insert a static character string when inserting source control headers in C mode.
Only relevant for the SCCS mode.")

;; Vars the user doesn't need to know about.

(defvar sccs-log-entry-mode nil)
(defvar sccs-current-major-version nil)

;; Some helper functions

(defun sccs-name (file &optional letter)
  "Return the sccs-file name corresponding to a given file."
  (if (null file)
      ()
    (let ((expanded-file (expand-file-name file)))
      (format "%sSCCS/%s.%s"
	      (concat (file-name-directory expanded-file))
	      (or letter "s")
	      (concat (file-name-nondirectory expanded-file))))))

(defun sccs-lock-info (file)
  "Lock-info method for SCCS.  See sc-generic-lock-info"
  (let ((sccs-file (sccs-name file "s"))
	(lock-file (sccs-name file "p")))
    (cond ((or (null file) (not (file-exists-p sccs-file)))
	   ())
	  ((not (file-exists-p lock-file))
	   (list () ()))
	  (t
	   (save-excursion
	     (set-buffer (get-buffer-create "*SCCS tmp*"))
	     (insert-file lock-file)
	     (while (search-forward " " () t)
	       (replace-match "\n" () t))
	     (goto-char (point-min))
	     (forward-line 1)
	     (let ((revision
		    (buffer-substring (point) (progn (end-of-line) (point))))
		   (name
		    (progn (forward-line 1)
			   (buffer-substring (point)
					     (progn (end-of-line) (point))))))
	       (kill-buffer (current-buffer))
	       (list name revision)))))))


(defun sccs-do-command (buffer command file &rest flags)
  "Execute an SCCS command, notifying the user and checking for errors."
  (let ((exec-path (cons sccs-bin-directory exec-path)))
    (apply 'sc-do-command buffer command command file (sccs-name file) flags)))

(defun sccs-admin (file sid)
  "Checks a file into sccs.
FILE is the unmodified name of the file.  SID should be the base-level sid to
check it in under."
  ;; give a change to save the file if it's modified
  (if (and (buffer-modified-p)
	   (y-or-n-p (format "%s has been modified. Write it out? "
			     (buffer-name))))
      (save-buffer))
  (sccs-do-command "*SCCS*" "admin" file
		   (concat "-i" file) (concat "-r" sid))
  (sc-chmod "-w" file)
  ;; expand SCCS headers
  (sccs-check-out file nil))

(defun sccs-register (file revision)
  (sccs-load-vars)
  (if (and (not (file-exists-p "SCCS"))
	   (y-or-n-p "Directory SCCS does not exist, create it?"))
      (make-directory "SCCS"))
  (sccs-admin file
	      (cond 
	       (revision revision)
	       ((error-occurred (load-file "SCCS/emacs-vars.el")) "1")
	       (t sccs-current-major-version))))

(defun sccs-check-out (file lockp)
  "Retrieve a copy of the latest version of the given file."
  (sccs-do-command "*SCCS*" "get" file (if lockp "-e")))

(defun sccs-get-version (file buffer revision)
  (sccs-do-command buffer "get" file
		   (and revision (concat "-r" revision))
		   "-p" "-s"))

(defun sccs-check-in (file revision comment)
  "Check-in a given version of the given file with the given comment."
  (sccs-do-command "*SCCS*" "delta" file "-n"
		   (format "-r%s" revision)
		   (format "-y%s" comment))
  (sc-chmod "-w" file)
  ;; sccs-delta already turned off write-privileges on the
  ;; file, let's not re-fetch it unless there's something
  ;; in it that get would expand
  (save-excursion
    (let ((buffer (get-file-buffer file)))
      (if buffer
	  (progn
	    (set-buffer buffer)
	    (sccs-check-out file nil))))))

(defun sccs-history (file)
  (sccs-do-command (current-buffer) "prs" file))

;; There has *got* to be a better way to do this...

(defun sccs-save-vars (sid)
  (save-excursion
    (find-file "SCCS/emacs-vars.el")
    (erase-buffer)
    (insert "(setq sccs-current-major-version \"" sid "\")")
    (basic-save-buffer)))

(defun sccs-load-vars ()
  (if (error-occurred (load-file "SCCS/emacs-vars.el"))
      (setq sccs-current-major-version "1")))

;; SCCS header insertion code

(defun sccs-insert-headers ()
  "*Insert headers for use with the Source Code Control System.
Headers desired are inserted at the start of the buffer, and are pulled from 
the variable sccs-headers-wanted"
  (interactive)
  (save-excursion
    (save-restriction
      (widen)
      (if (or (not (sccs-check-headers))
	      (y-or-n-p "SCCS headers already exist.  Insert another set?"))
	  (progn
	     (goto-char (point-min))
	     (run-hooks 'sccs-insert-headers-hook)
	     (cond ((eq major-mode 'c-mode) (sccs-insert-c-header))
		   ((eq major-mode 'lisp-mode) (sccs-insert-lisp-header))
		   ((eq major-mode 'emacs-lisp-mode) (sccs-insert-lisp-header))
		   ((eq major-mode 'scheme-mode) (sccs-insert-lisp-header))
		   ((eq major-mode 'nroff-mode) (sccs-insert-nroff-header))
		   ((eq major-mode 'plain-tex-mode) (sccs-insert-tex-header))
		   ((eq major-mode 'texinfo-mode) (sccs-insert-texinfo-header))
		   (t (sccs-insert-generic-header))))))))



(defun sccs-insert-c-header ()
  (insert "/*\n")
  (mapcar '(lambda (s)
	     (insert " *\t" s "\n"))
	  sccs-headers-wanted)
  (insert " */\n\n")
  (if (and sccs-insert-static 
	   (not (string-match "\\.h$" buffer-file-name)))
      (progn
	(insert "#ifndef lint\n"
		"static char *sccsid")
;;	(setq st (point))
;;	(insert (file-name-nondirectory buffer-file-name))
;;	(setq en (point))
;;	(subst-char-in-region st en ?. ?_)
	(insert " = \"\%\W\%\";\n"
		"#endif /* lint */\n\n")))
  (run-hooks 'sccs-insert-c-header-hook))

(defun sccs-insert-lisp-header ()
  (mapcar '(lambda (s) 
		  (insert ";;;\t" s "\n"))
	  sccs-headers-wanted)
  (insert "\n")
  (run-hooks 'sccs-insert-lisp-header-hook))

(defun sccs-insert-nroff-header ()
  (mapcar '(lambda (s) 
		  (insert ".\\\"\t" s "\n"))
	  sccs-headers-wanted)
  (insert "\n")
  (run-hooks 'sccs-insert-nroff-header-hook))

(defun sccs-insert-tex-header ()
  (mapcar '(lambda (s) 
		  (insert "%%\t" s "\n"))
	  sccs-headers-wanted)
  (insert "\n")
  (run-hooks 'sccs-insert-tex-header-hook))

(defun sccs-insert-texinfo-header ()
  (mapcar '(lambda (s) 
		  (insert "@comment\t" s "\n"))
	  sccs-headers-wanted)
  (insert "\n")
  (run-hooks 'sccs-insert-texinfo-header-hook))

(defun sccs-insert-generic-header ()
  (let* ((comment-start-sccs (or comment-start "#"))
	 (comment-end-sccs (or comment-end ""))
	 (dont-insert-nl-p (string-match "\n" comment-end-sccs)))
    (mapcar '(lambda (s)
	       (insert comment-start-sccs "\t" s ""
		       comment-end-sccs (if dont-insert-nl-p "" "\n")))
	  sccs-headers-wanted)
  (insert comment-start-sccs comment-end-sccs (if dont-insert-nl-p "" "\n"))))

(defun sccs-check-headers ()
  "Check if the current file has any SCCS headers in it."
  (save-excursion
    (goto-char (point-min))
    (let ((case-fold-search ()))
      (re-search-forward  "%[MIRLBSDHTEGUYFPQCZWA]%" (point-max) t))))

(defun sccs-tree-list ()
  "List all the registered files in the current directory"
  (call-process "/bin/sh" () t () "-c"
		(concat "/bin/ls -1 " default-directory "SCCS/s.*"))
  (goto-char (point-min))
  (while (search-forward "SCCS/s." () t)
    (replace-match "" () t)))

(defun sccs-new-revision-p (file)
  "True if the SCCS archive is more recent than the file itself"
  (file-newer-than-file-p (sccs-name file) file))

(defun sccs-revert (file)
  "Cancel a check-out and get a fresh copy of the file"
  (delete-file (sccs-name file "p"))
  (delete-file file)
  (sccs-do-command "*SCCS*" "get" file "-s"))

(defun sccs-rename (old new)
  "Rename the SCCS archives for OLD to NEW"
  (if (file-exists-p (sccs-name old "p"))
      (rename-file (sccs-name old "p") (sccs-name new "p") t))
  (if (file-exists-p (sccs-name old "s"))
      (rename-file (sccs-name old "s") (sccs-name new "s") t)))


;;; RCS specific part

;; Some helper functions

(defun rcs-name (file)
  "Return the rcs-file corresponding to a given file."
  (if (null file)
      ()
    (let* ((name (expand-file-name file))
	   (rcs-file (concat name ",v")))
      (if (and (not (file-exists-p rcs-file))
	       (file-exists-p (concat (file-name-directory name) "RCS")))
	  (setq rcs-file 
		(format "%sRCS/%s,v" (file-name-directory name)
			(file-name-nondirectory name))))
      rcs-file)))

(defun rcs-lock-info (file)
  "Lock-info method for RCS.  See sc-generic-lock-info"
  (let ((rcs-file (rcs-name file))
	locks-regexp)
    (if (or (null rcs-file) (not (file-exists-p rcs-file)))
	()
      (save-excursion
	(set-buffer (get-buffer-create "*RCS tmp*"))
	(erase-buffer)
	(call-process "rlog" () t () "-L" "-h" rcs-file)
	(goto-char (point-min))
	(if (looking-at "\n.*Working file")
	    ;; RCS 4.x
	    (setq locks-regexp "^locks:")
	  ;; RCS 5.x
	  (setq locks-regexp "^locks:.*$\n"))
	(if (not (re-search-forward locks-regexp () t))
	    (list () ())
	  (if (not (looking-at (concat "[\t ]*\\([^:]*\\): \\([0-9\\.]*\\)")))
	      (list () ())
	    (list (buffer-substring (match-beginning 1) (match-end 1))
		  (buffer-substring (match-beginning 2) (match-end 2)))))))))


(defun rcs-register (file revision)
  (if (and (not (file-exists-p "RCS"))
	   (y-or-n-p "Directory RCS does not exist, create it?"))
      (make-directory "RCS"))
  (sc-do-command "*RCS*" "ci" "ci" file (rcs-name file) "-u"))

(defun rcs-check-out (file lockp)
  (sc-do-command "*RCS*" "co" "co" file (rcs-name file) (if lockp "-l")))

(defun rcs-get-version (file buffer revision)
  (sc-do-command buffer "co" "co" file (rcs-name file)
		 (if revision (concat "-p" revision) "-p")
		 "-q"))

(defun rcs-check-in (file revision comment)
  "Check-in a given version of the given file with the given comment."
  (sc-do-command "*RCS*" "ci" "ci" file (rcs-name file) "-f"
		 (format "-m%s" comment)
		 (if (equal revision (sc-locked-revision file))
		     "-u"
		   (format "-u%s" revision))))

(defun rcs-history (file)
  (sc-do-command (current-buffer) "rlog" "rlog" file (rcs-name file)))

(defun rcs-tree-list ()
  "List all the registered files in the current directory"
  (call-process "/bin/sh" () t () "-c"
		(concat "/bin/ls -1 " default-directory "RCS/*,v"))
  (call-process "/bin/sh" () t () "-c"
		(concat "/bin/ls -1 " default-directory "*,v"))
  (goto-char (point-min))
  (while (search-forward "RCS/" () t)
    (replace-match "" () t))
  (goto-char (point-min))
  (while (search-forward ",v" () t)
    (replace-match "" () t)))

(defun rcs-new-revision-p (file)
  "True if the archive is more recent than the file itself"
  (file-newer-than-file-p (rcs-name file) file))

(defun rcs-revert (file)
  "Cancel a check-out and get a fresh copy of the file"
  (sc-do-command "*RCS*" "rcs" "rcs" file (rcs-name file) "-u")
  (delete-file file)
  (sc-do-command "*RCS*" "co" "co" file (rcs-name file)))

(defun rcs-rename (old new)
  "Rename the archives for OLD to NEW"
  (if (file-exists-p (rcs-name old))
      (rename-file (rcs-name old) (rcs-name new) t)))


;;; CVS specific part

;;; As we rely on pcl-cvs for the directory level functions the menu is
;;; much shorter in CVS mode


(defun cvs-lock-info (file)
  "Lock-info method for CVS, different from RCS and SCCS modes.
File are never locked in CVS."
  (list () ()))

(defun cvs-register (file revision)
  (sc-do-command "*CVS*" "cvs add" cvs-program file
		 (file-name-nondirectory file)
		 "add" "-mInitial revision"))

(defun cvs-check-out (file lockp)
  )

(defun cvs-get-version (file buffer revision)
  (sc-do-command buffer "cvs update" cvs-program file file "update" 
		 (if revision (concat "-r" revision))
		 "-p" "-q"))

(defun cvs-check-in (file revision comment)
  "Check-in a given version of the given file with the given comment."
  (sc-do-command "*CVS*" "cvs commit" cvs-program file file "commit"
		 (and revision (format "-r%s" revision))
		 (format "-m%s" comment)))

(defun cvs-history (file)
  (sc-do-command (current-buffer) "cvs log" cvs-program file file "log"))

(defun cvs-revert (file)
  "Cancel a check-out and get a fresh copy of the file"
  (delete-file file)
  (sc-do-command "*CVS*" "cvs update" cvs-program file file "update"))

(defun sc-cvs-update-directory ()
  "Update the current directory by calling cvs-update from pcl-cvs"
  (interactive)
  (cvs-update default-directory))

(defun sc-cvs-file-status ()
  "Show the CVS status of the current file"
  (interactive)
  (if (not buffer-file-name)
      (error "There is no file associated with buffer %s" (buffer-name)))
  (let ((file buffer-file-name))
    (sc-do-command "*CVS*" "cvs status" cvs-program file file "status" "-v"))
  (save-excursion
    (set-buffer "*CVS*")
    (goto-char (point-min)))
  (display-buffer "*CVS*"))


;;; ClearCase specific part

(defun ccase-is-registered-3 (fod)
  (if (or (not fod)
	  (not (file-readable-p fod)))
      'na
    (let ((dirs sc-ccase-mfs-prefixes)
	  (f nil)
	  (file (expand-file-name fod)))
      (while (and (null f) dirs)
	(if (string-match (car dirs) file)
	    (setq f t)
	  (setq dirs (cdr dirs))))
      (if (null f)
	  'na
	(sc-do-command "*CCase*" "describe" "cleartool" fod fod "describe")
	(save-excursion
	  (set-buffer "*CCase*")
	  (let ((s (buffer-string)))
	    (cond
	     ((string-match "@@" s) t)
	     ((string-match "^Unix" s) 'na)
	     (t nil)
	     )))))))

(defun ccase-is-registered (fod)
  (eq (ccase-is-registered-3 fod) t))

(defun ccase-lock-info (file)
  (let ((cc (ccase-is-registered-3 file))
	s)
    (if (eq cc 't)
	(progn
	  (save-excursion
	    (set-buffer "*CCase*")
	    (setq s (buffer-string)))
	  (if (string-match "@@[^\n]*CHECKEDOUT\" from \\([^ ]*\\)[^\n]*\n[^\n]* by \\([^(\n]*\\) (" s)
	      (list
	       (substring s (match-beginning 1) (match-end 1))
	       (substring s (match-beginning 2) (match-end 2)))
	    (list nil nil)))
      cc)))

(defun ccase-maybe-comment (tag)
  (if (memq tag sc-ccase-comment-on)
      (sc-enter-comment)))

(defun ccase-register (file revision)
  "Registers the file. We don't support the revision argument.
Also, we have to checkout the directory first."
  ;; probably need proper error handling to catch the 
  ;; cases where we co the directory, but don't get to
  ;; ci it back (want to uco in this case)
  (let ((dpath (file-name-directory file)))
    (if (not (ccase-is-registered dpath))
	(error "Cannot register file outside of VOB")
      (sc-do-command "*CCase*" "co - dir" "cleartool" dpath dpath "co")
      (sc-do-command "*CCase*" "register" "cleartool" file file "mkelem")
      (sc-do-command "*CCase*" "ci - dir" "cleartool" dpath dpath "ci"))))

(defun ccase-check-out (file lockp)
  "Checks out the latest version of FILE.  
If LOCKP is not NIL, FILE is also locked."
  (let ((comment (ccase-maybe-comment 'checkout)))
    (sc-do-command "*CCase*" "co" "cleartool" file file "co"
		   (if comment "-c" "-nc")
		   (if comment comment)
  ;; this locking does not correspond to what we actually want. It's a
  ;; hack from the days when this was SCCS-only
		   (if (ccase-reserve-p) "-res" "-unr"))
))

(defun ccase-reserve-p ()
  "Determine whether the user wants a reserved or unreserved checkout"
  (cond
   ((eq sc-ccase-reserve t)   t)
   ((eq sc-ccase-reserve nil) nil)
   (t (y-or-n-p "Reserve Checkout? "))))
   
(defun ccase-get-version (file buffer revision)
  "Insert a previous revison of FILE in BUFFER.  
REVISION is the revision number requested."
  (save-excursion
    (set-buffer buffer)
    (delete-region (point-min) (point-max))
    (insert-file-contents (concat file "@@/" revision)))
)

(defun ccase-check-in (file revision message)
  "Check in FILE with revision REVISION.
MESSAGE is a string describing the changes."
  ;; we ignore revision since we can't use it
  (sc-do-command "*CCase*" "ci" "cleartool" file file "ci" "-c" message (if sc-mode-expert "-ide"))
)

(defun ccase-history (file)
  "Insert the edit history of FILE in the current buffer."
  (sc-do-command (buffer-name) "history" "cleartool" file file "lsh")
)

(defun ccase-tree-list ()
  "List in the current buffer the files registered in the source control system"
  ;;; This isn't going to fly as a practicality. We abstract everything out.
  ;;  (sc-do-command (buffer-name) "listing" "cleartool" (default-directory) (default-directory) "ls" "-r" "-short" "-vis" "-nxname")
)
  
(defun ccase-new-revision-p (file)
  "True if a new revision of FILE was checked in since we last got a copy of it"
  (save-excursion
  (let (newfile res br1 br2)
    (sc-do-command "*CCase*" "Describe" "cleartool" file file "des")
    (set-buffer "*CCase*")
    (goto-char (point-min))
    (if (setq pos (search-forward-regexp "@@\\([^ \"]*\\)CHECKEDOUT\" from \\([^ ]*\\) (\\([a-z]*\\))" nil t))
;;    (if (setq pos (search-forward-regexp "@@\\([^ \"]*\\)CHECKEDOUT\"" nil t))
	(progn
	  (setq res (buffer-substring (match-beginning 3) (match-end 3)))
	  (if (equal res "unreserved")
	      (progn
		(setq newfile (concat file "@@"
				      (buffer-substring (match-beginning 1)
							(match-end 1))
				      "LATEST"))
		(setq br1 (buffer-substring (match-beginning 2) (match-end 2)))
		(sc-do-command "*CCase*" "Describe" "cleartool" file newfile
			       "des")
		(search-forward-regexp "@@\\([^ \"]*\\)" nil t)
		(setq br2 (buffer-substring (match-beginning 1) (match-end 1)))
		(not (equal br1 br2)))
	    nil))
      (error "%s not currently checked out" file)))))

(defun ccase-revert (file)
  "Cancel a check out of FILE and get back the latest checked in version"
  (sc-do-command "*CCase*" "uco" "cleartool" file file "unco")
)

(defun ccase-rename (old new)
  "Rename the source control archives for OLD to NEW"
  (let ((dpath (file-name-directory old))
	(comment (ccase-maybe-comment 'rename)))
    (if (not (ccase-is-registered dpath))
	(error "Cannot rename file outside of VOB")
      (sc-do-command "*CCase*" "co - dir" "cleartool" dpath dpath "co"
		   (if comment "-c" "-nc")
		   (if comment comment))
      (sc-do-command "*CCase*" "mv" "cleartool" new new "mv" 
		   (if comment "-c" "-nc")
		   (if comment comment)
		   old)
      (sc-do-command "*CCase*" "ci - dir" "cleartool" dpath dpath "ci" 
		     (if comment "-c" "-nc")
		     (if comment comment)))))

(defun sc-ccase-checkout-dir ()
  "Checkout the directory this file is in"
  (interactive)
  (let ((dpath default-directory)
	(comment (ccase-maybe-comment 'checkout-dir)))
    (if (not (ccase-is-registered dpath))
	(error "Cannot checkout directory outside of VOB")
      (sc-do-command "*CCase*" "co - dir" "cleartool" dpath dpath "co"
		   (if comment "-c" "-nc")
		   (if comment comment)))))

(defun sc-ccase-checkin-dir ()
  "Checkin the directory this file is in"
  (interactive)
  (let ((dpath default-directory)
	(comment (ccase-maybe-comment 'checkin-dir)))
    (if (not (ccase-is-registered dpath))
	(error "Cannot checkout directory outside of VOB")
      (sc-do-command "*CCase*" "ci - dir" "cleartool" dpath dpath "ci"
		   (if comment "-c" "-nc")
		   (if comment comment)))))

(defun sc-ccase-editcs ()
  "Edit Config Spec for this view"
  (interactive)
  (sc-do-command "*CCase-cs*" "catcs" "cleartool" "" nil "catcs")
  (switch-to-buffer-other-window "*CCase-cs*")
  (local-set-key "\C-c\C-c" 'exit-recursive-edit)
  (recursive-edit)
  (set-buffer "*CCase-cs*")
  (let ((name (make-temp-name "/tmp/configspec")))
    (write-region (point-min) (point-max) name)
    (kill-buffer "*CCase-cs*")
    (sc-do-command "*CCase*" "setcs" "cleartool" name name "setcs"))
)

(defun sc-ccase-new-brtype (brt)
  "Create a new branch type"
  (interactive "sBranch Name: ")
  (let ((comment (ccase-maybe-comment 'new-brtype)))
    (sc-do-command "*CCase*" "mkbrt" "cleartool" brt brt "mkbrtype"
		   (if comment "-c" "-nc")
		   (if comment comment))))

(defun sc-ccase-new-branch (brch)
  "Create a new branch for element"
  (interactive "sBranch: ")
  (let ((file (buffer-file-name))
	(comment (ccase-maybe-comment 'new-branch)))
    (sc-do-command "*CCase*" "mkbrch" "cleartool" file file "mkbranch" 
		   (if comment "-c" "-nc")
		   (if comment comment)
		   brch)))

(defun sc-ccase-checkin-merge ()
  "Merge in changes to enable checkin"
  (interactive)
  (save-excursion
  (let ((file (buffer-file-name))
	(buf (current-buffer))
	(comment (ccase-maybe-comment 'checkin-merge)))
    (sc-do-command "*CCase*" "Describe" "cleartool" file file "des")
    (set-buffer "*CCase*")
    (goto-char (point-min))
    (if (search-forward-regexp "@@\\([^ \"]*\\)CHECKEDOUT\" from \\([^ ]*\\) (\\([a-z]*\\))" nil t)
	(progn
	  (sc-do-command "*CCase*" "Merging" "cleartool" file
			 (concat (buffer-substring (match-beginning 1)
						   (match-end 1)) "LATEST")
			 "merge"
			 (if comment "-c" "-nc")
			 (if comment comment)
			 "-abort" "-to" file "-ver")
	  (set-buffer buf)
	  (revert-buffer t t)
	  (display-buffer "*CCase*"))
      (error "File %s not checked out" file)))))
	  
(defun sc-ccase-version-tree ()
  "List version tree for file"
  (interactive)
  (let ((p (buffer-file-name)))
    (sc-do-command "*CCase*" "lsvtree" "cleartool" p p "lsvtree")
    (display-buffer "*CCase*")))

(defun ccase-protect-expanded-name (revision)
  "Protect ccase extended names from being used as temp names. Munge /s into :s"
  (if (equal sc-generic-name "CCase")
      (progn
	(if (string-match "/" revision)
	    (let ((str (substring revision 0)) ;; copy string
		  i)
	      (while (setq i (string-match "/" str))
		(aset str i 58)) ; 58 is for :
	      str)))))

(defun sc-ccase-list-locked-files ()
  (interactive)
  (sc-do-command "*CCase directory*" "listing" "cleartool" (default-directory) nil "lsco" "-cview"))

(defun sc-ccase-list-all-locked-files ()
  (interactive)
  (sc-do-command "*CCase directory*" "listing" "cleartool" (default-directory) nil "lsco"))

(defun sc-ccase-list-registered-files ()
  "List files registered in clearcase"
  (interactive)
  (sc-do-command "*CCase directory*" "listing" "cleartool" (default-directory) nil "ls" "-r" "-vis" "-nxname"))

;;; Instantiation and installation of the menus

;;; Set the menubar for Lucid Emacs
(defvar sc-default-menu
  '(["NEXT-OPERATION"	sc-next-operation	t	nil]
    ["Update Current Directory"		sc-update-directory	t]
    "----"
    ["Revert File"		sc-revert-file	t	nil]
    ["Rename File"		sc-rename-this-file		t	nil]
    "----"
    ["Show Changes"		sc-show-changes		t]
    ["Show Changes Since Revision..."	sc-show-revision-changes	t]
    ["Visit Previous Revision..."	sc-visit-previous-revision	t]
    ["Show Edit History"		sc-show-history		t]
    "----"
    ["List Locked Files"	sc-list-locked-files	t]
    ["List Locked Files Any User"	sc-list-all-locked-files	t]
    ["List Registered Files"	sc-list-registered-files	t])
  "Menubar entry for using the revision control system.")

(defvar sc-cvs-menu
  '(["Update Current Directory"		sc-cvs-update-directory	t]
    ["Revert File"		sc-revert-file	t	nil]
    "----"
    ["Show Changes"		sc-show-changes		t]
    ["Show Changes Since Revision..."	sc-show-revision-changes	t]
    ["Visit Previous Revision..."	sc-visit-previous-revision	t]
    ["Show File Status"		sc-cvs-file-status		t]
    ["Show Edit History"		sc-show-history		t])
  "Menubar entry for using the revision control system with CVS.")

(defvar sc-ccase-menu
  '(["NEXT-OPERATION"			sc-next-operation		t nil]
    ["Revert File"			sc-revert-file			t nil]
    ["Checkin Merge"			sc-ccase-checkin-merge  	t]
    "----"
    ["Show Changes"			sc-show-changes			t]
    ["Show Changes Since Revision..."	sc-show-revision-changes	t]
    ["Visit Previous Revision..."	sc-visit-previous-revision	t]
    ["Show Edit History"		sc-show-history			t]
    "----"
    ("Directories" 
     ["Checkout Directory"		sc-ccase-checkout-dir		t]
     ["Checkin Directory"		sc-ccase-checkin-dir		t]
     ["Rename File..."			sc-rename-this-file		t nil])
    ("Configs"
     ["Edit Config Spec..."		sc-ccase-editcs			t]
     ["Create New Branch..."		sc-ccase-new-brtype		t]
     ["Make New Branch..."		sc-ccase-new-branch		t])
    ("Listings"
     ["List Version Tree"		sc-ccase-version-tree		t]
     ["List Locked Files"		sc-ccase-list-locked-files	t]
     ["List Locked Files Any User"	sc-ccase-list-all-locked-files	t]
     ["List Registered Files"		sc-ccase-list-registered-files	t]
     ))
  "Menubar entry for using the revision control system.")

(defun sc-sensitize-menu ()
  (let* ((rest (cdr (car
		     (find-menu-item current-menubar (list sc-generic-name)))))
	 (case-fold-search t)
	 (file (if buffer-file-name
		   (file-name-nondirectory buffer-file-name)
		 (buffer-name)))
	 (lock-info (sc-lock-info buffer-file-name))
	 command
	 nested-rest
	 item)
    (while rest
      (setq item (car rest))
      (if (listp item)
	  (progn
	    (setq nested-rest (cons (cdr rest) nested-rest))
	    (setq rest (cdr item)))
	(if (vectorp item)
	    (progn
	      (setq command (aref item 1))
	      (cond ((eq 'sc-next-operation command)
		     (aset item 0
			   (cond ((eq lock-info 'na) "Not Available")
				 ((not lock-info) "Register File")
				 ((not (car lock-info)) "Check out File")
				 (t "Check in File")))
		     ;; if locked by somebody else disable the next-operation
		     (if (or (not buffer-file-name)
			     (eq lock-info 'na)
			     (and (car lock-info)
				  (not (equal sc-generic-name "CCase"))
				  (not (equal (car lock-info) (user-login-name)))))
			 (aset item 2 ())
		       (aset item 2 t)))
		    ((eq lock-info 'na) (aset item 2 ()))
		    ((> (length item) 3)
		     (aset item 3 file))
		    (t nil))
	      (if (not (eq lock-info 'na))
		  (let ((enable-file-items
			 (if (member sc-generic-name '("CVS" "CCase"))
			     buffer-file-name
			   (if lock-info t ()))))
		    (if (memq command
			      '(sc-force-check-in-file
				sc-register-file
				sc-revert-file
				sc-rename-this-file
				sc-show-history
				sc-show-changes
				sc-show-revision-changes
				sc-visit-previous-revision
				sc-cvs-file-status
				sc-ccase-checkout-dir
				sc-ccase-checkin-dir
				sc-ccase-editcs
				sc-ccase-new-brtype
				sc-ccase-new-branch
				sc-ccase-checkin-merge
				sc-ccase-needs-merge
				sc-ccase-merge-changes
				sc-ccase-create-label
				sc-ccase-label-sources
				sc-ccase-version-tree
				sc-list-locked-files
				sc-list-all-locked-files
				sc-ccase-list-registered-files
				))
			(aset item 2 enable-file-items))))))
	(if (not (setq rest (cdr rest)))
	    (if nested-rest
		(progn
		  (setq rest (car nested-rest))
		  (setq nested-rest (cdr nested-rest)))))))
    nil))


;;; Function to decide which Source control to use
(defun sc-set-SCCS-mode ()
  (setq sc-generic-name "SCCS")
  (setq sc-can-hack-dir t)
  (setq sc-generic-lock-info 'sccs-lock-info)
  (setq sc-generic-register 'sccs-register)
  (setq sc-generic-check-out 'sccs-check-out)
  (setq sc-generic-get-version 'sccs-get-version)
  (setq sc-generic-check-in 'sccs-check-in)
  (setq sc-generic-history 'sccs-history)
  (setq sc-generic-tree-list 'sccs-tree-list)
  (setq sc-generic-new-revision-p 'sccs-new-revision-p)
  (setq sc-generic-revert 'sccs-revert)
  (setq sc-generic-rename 'sccs-rename)
  (setq sc-menu
	(cons (car sc-default-menu)
	      (cons ["Insert Headers"	sccs-insert-headers	t]
		    (cdr sc-default-menu))))
  (define-key sc-prefix-map "h" 'sccs-insert-headers)
  (define-key sc-prefix-map "\C-d" 'sc-update-directory))

(defun sc-set-RCS-mode ()
  (setq sc-generic-name "RCS")
  (setq sc-can-hack-dir t)
  (setq sc-generic-lock-info 'rcs-lock-info)
  (setq sc-generic-register 'rcs-register)
  (setq sc-generic-check-out 'rcs-check-out)
  (setq sc-generic-get-version 'rcs-get-version)
  (setq sc-generic-check-in 'rcs-check-in)
  (setq sc-generic-history 'rcs-history)
  (setq sc-generic-tree-list 'rcs-tree-list)
  (setq sc-generic-new-revision-p 'rcs-new-revision-p)
  (setq sc-generic-revert 'rcs-revert)
  (setq sc-generic-rename 'rcs-rename)
  (setq sc-menu sc-default-menu)
  (define-key sc-prefix-map "\C-d" 'sc-update-directory))

(defun sc-set-CVS-mode ()
  (require 'pcl-cvs)
  (setq sc-generic-name "CVS")
  (setq sc-can-hack-dir t)
  (setq sc-generic-lock-info 'cvs-lock-info)
  (setq sc-generic-register 'cvs-register)
  (setq sc-generic-check-out 'cvs-check-out)
  (setq sc-generic-get-version 'cvs-get-version)
  (setq sc-generic-check-in 'cvs-check-in)
  (setq sc-generic-history 'cvs-history)
  (setq sc-generic-tree-list 'cvs-tree-list)
  (setq sc-generic-new-revision-p 'cvs-new-revision-p)
  (setq sc-generic-revert 'cvs-revert)
  (setq sc-generic-rename 'cvs-rename)
  (setq sc-menu sc-cvs-menu)
  (define-key sc-prefix-map "\C-d" 'sc-cvs-update-directory)
  (define-key sc-prefix-map "s" 'sc-cvs-file-status))

(defun sc-set-CLEARCASE-mode ()
  (setq sc-generic-name "CCase")
  (setq sc-can-hack-dir nil)
  (setq sc-generic-lock-info 'ccase-lock-info)
  (setq sc-generic-register 'ccase-register)
  (setq sc-generic-check-out 'ccase-check-out)
  (setq sc-generic-get-version 'ccase-get-version)
  (setq sc-generic-check-in 'ccase-check-in)
  (setq sc-generic-history 'ccase-history)
  (setq sc-generic-tree-list 'ccase-tree-list)
  (setq sc-generic-new-revision-p 'ccase-new-revision-p)
  (setq sc-generic-revert 'ccase-revert)
  (setq sc-generic-rename 'ccase-rename)
  (setq sc-menu sc-ccase-menu)

  ;; caching for file directory types
  (save-excursion
    (set-buffer (get-buffer-create "*CCase*"))
    (shell-command-on-region (point-min) (point-max) "df -t mfs | sed -n 's%.*[       ]\\(/[^ ]*\\)$%\\1%p'" t)
    (goto-char (point-min))
    (let (x l)
      (while (condition-case nil (setq x (read (current-buffer)))
	       (error nil))
	(setq l (cons (prin1-to-string x) l)))
      (setq sc-ccase-mfs-prefixes (nreverse l))))
)

(defun sc-set-ATRIA-mode ()
  (sc-set-CLEARCASE-mode))

(defun sc-set-CCASE-mode ()
  (sc-set-CLEARCASE-mode))


;; the module is sucessfully loaded!
(provide 'generic-sc)

;;; generic-sc.el ends here
