Mercurial > hg > xemacs-beta
diff lisp/packages/generic-sc.el @ 155:43dd3413c7c7 r20-3b4
Import from CVS: tag r20-3b4
author | cvs |
---|---|
date | Mon, 13 Aug 2007 09:39:39 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/packages/generic-sc.el Mon Aug 13 09:39:39 2007 +0200 @@ -0,0 +1,1758 @@ +;;; 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