;; Date: Wed, 10 Oct 90 20:36:12 EDT ;; From: Chris Siebenmann ;; ;; This is version 1.35 with some additions by ceder@lysator.liu.se. ;; ;;; rcs-cks.el deal with rcs'd files in a natural way ;; Copyright (C) 1989 Chris Siebenmann under the terms of the FSF GNU ;; General Public License, version 1 ;; modified and added to by Ralph Finch (rfinch@water.ca.gov) (provide 'rcs) ;;;; Some Documentation ;; This file provides a convenient interface to rcs that allows you ;; to easily check out, check in, and log changes to files that you ;; edit, as well as automatically check out a file when you try and ;; edit it, and record on the mode line who has locked the file. ;; Please report any bugs found to cks@white.toronto.edu, and send me ;; a copy of anything interesting you add. ;; ;; Some portions of this package assume you are using strict locking ;; on your files, either by default or having it set explicitly. ;; In particular, rcs-co-file (and thus rcs-co-buffer) assumes that ;; a writable file by the same name as the one you are checking out is ;; a danger sign, and asks if you want to overwrite it. ;; I recommend the usage of strict locking. ;; ;; Suggested usage: enable strict locking, and make all source files ;; non-writable. Then whenever you try to edit a file that's ;; read-only, you know you have to check it out; if the file hasn't ;; already been placed under RCS, you will be prompted for an initial ;; description. Conversly, a modifiable file is one you have locked, ;; and can thus change freely. ;; ;; Problems when used by root: ;; file-writable-p always returns true when your uid is 0, so you ;; will always be asked if you want to overwrite the file when you ;; check it out. There are two solutions: 1) use the mode line hook ;; to show you who has the file locked, if anyone or 2) load an ;; improved version of file-writeable-p that works a bit better when ;; you're root (send email to me for a copy of mine). ;; The second caution is that if you're su'd to root files you check ;; out will have the locker recorded as root instead of you, although ;; who checked them back in will be correctly recorded. This appears ;; to be an unavoidable problem with rcs; co needs to have an ;; argument that lets you specify who is locking the file. ;; ;; Rcs itself is free, and can be obtained via ftp from ;; arthur.cs.purdue.edu from pub/RCS, or from a number of uucp ;; archive sites. You will also need a version of GNU diff, also ;; widely available from ftp and uucp archive sites. ;; To use, bind the available functions to convenient keys. A good ;; set of functions to start by binding are rcs-ci-buffer, ;; rcs-co-buffer, and rcs-log-buffer. Note that rcs-co-buffer will ;; automatically create an RCS directory and check in the current ;; buffer if the file is not currently under RCS. This makes it ;; convenient to make all files for a software package read-only; then ;; when you need to change one, just load it in and do an ;; rcs-co-buffer. ;; Most user-callable functions come in two flavors; one that works ;; on the current buffer, and one that works on any file ;; (rcs-log-buffer and rcs-refresh-buffer are the only exceptions). ;; The versions that operate on the current buffer make it very ;; convenient to (for example) load in a file, notice that it isn't ;; locked out, and check it out in order to change it. (global-set-key "\C-x\"" 'rcs-co-buffer) (global-set-key "\C-x#" 'rcs-diff-and-ci-buffer) (global-set-key "\C-xL" 'rcs-show-locked-files-directory) ;;; Functions available: ;; rcs-co-{file,buffer,tags}- Check out a file or a buffer, or ;; series of tags files. If the ;; file isn't already RCS'd, it will be ;; checked in automatically. ;; rcs-ci-{file,buffer,tags}- Check in a file or buffer, or series ;; of tags files. The file ;; will be left checked out unlocked, ;; although by default the buffer will be ;; killed (see rcs-ci-buffer-kills-buffer). ;; rcs-log-buffer - Check in a buffer, and then ;; immediately check it out locked again. ;; Handy for logging intermediate stages ;; in changes. ;; rcs-diff-{file,buffer} rcsdiff the current version of the ;; file against the most recent RCS ;; version. Useful for seeing what ;; changes you just made. ;; rcs-show-log-{buffer,file} Show the RCS change log for a given ;; file or buffer. ;; rcs-revert-{file,buffer} Revert a file or buffer back to the ;; last checked in version. An easy way ;; to blow away changes you've decided ;; you don't want, or back out of ;; checking out locked a file you decide ;; not to change. ;; rcs-refresh-buffer Reload the current buffer from its file, ;; usually because someone else has just ;; unlocked it. ;; The following two functions are intended to be placed on hooks, ;; instead of being called directly. ;; rcs-try-file - Put this on your find-file-not-found-hooks ;; hook. If you try and edit a file that ;; has been RCS'd and not checked out, it ;; will automatically check it out for ;; you. ;; rcs-hack-modeline - Put this on your find-file-hooks hook; ;; when run, it will put a legend up ;; about who has locked the file being ;; edited in that buffer. ;; Caution -- this can be slow. ;; Eg, to put both on hooks, add the following lisp code to your .emacs. (setq find-file-not-found-hooks (list 'rcs-try-file)) (setq find-file-hooks (list 'rcs-hack-modeline)) ;;; Hooks available: ;; rcs-hook - run at the end of this file (ie after all ;; the functions have been loaded). ;; rcs-new-dir-hook run when a new RCS directory has been created. ;; rcs-new-file-hook run when a file is checked in for the first time. ;; (see rcs-ci-file for more details on the latter two) ;; All hooks are run with run-hooks. ;;; User-changeable variables. (defvar rcs-use-other-win t "*If non-nil, pop to a separate window when doing two-window things (log entries and displaying diffs).") (defvar rcs-use-directories t "*If non-nil, checkin will make a RCS directory if none exists.") (defvar rcs-diff-options "-qc" "*If non-nil, any additional options rcsdiff will be given.") (defvar rcs-ci-buffer-kills-buffer nil "*If non-nil, when a rcs-ci-buffer is done with no prefix arg, that buffer is killed.") (defvar rcs-executable-path nil "*If non-nil, the default path to find an RCS command on.") (defvar rcs-edit-mode nil "*If non-nil, the log buffer will be placed in this mode. Otherwise, the log buffer will be in default-major-mode.") (defvar rcs-use-login-name t "*If non-nil, checkins will use the name you're currently logged in under, instead of the name for your current UID (eg, if you su to root and use emacs to check in a file, the RCS log will have your user name in it instead of 'root').") (defvar rcs-make-backup-files t "*If non-nil, backups of checked-in files are made according to the make-backup-files variable. Otherwise, prevents backups being made.") (defvar rcs-initial-branch "1" "*If non-nil, branch number to assign an initial checkin.") (defvar rcs-initial-rev "0" "*If non-nil, revision number to assign an initial checkin.") (defvar rcs-initial-access nil " If non-nil, access list to assign to an initial checkin.") (defvar rcs-keep-log nil "*If non-nil, keeps old log. Otherwise gives user blank log message for each checkin.") (defvar rcs-force-checkin nil "*If non-nil, force a checkin even if the file has not changed.") (defvar rcs-error-time 3 "*The length of time the rcs package will wait after an error message has been displayed before proceeding.") ;; Hooks (defvar rcs-hook nil "*Hooks run at the end of this file.") (defvar rcs-new-dir-hook nil "*Hooks run when a new ./RCS directory is created.") (defvar rcs-new-file-hook nil "*Hooks run when a file has been checked in for the first time.") ;;;; The code ;; Please, no flames about my Lisp style (constructive suggestions ;; gleefully welcomed); this is the first large piece of ELisp I've ;; written, and I'm *sure* there's lots of ways to improve it. Your ;; help in finding them is appreciated :-). ;;; Variables the user won't normally want to change (defvar rcs-log-buffer "#rcs log#") (defvar rcs-temp-buffer "#rcs temp#") (defvar rcs-exec-path nil "Path rcs searches to find executables. Built from rcs-executable-path and exec-path.") ; set this to a small shell that starts fast. (defvar rcs-shell-path "/bin/sh" "*If non-nil, the file name to load inferior shells for RCS commands from. If nil, shell-file-name's value is used instead.") (defvar rcs-use-prev-log nil "*If non-nil, rcs-ci-file will not prompt for new log but simply use old log buffer.") ;;; utility functions. ;; kill and reload a buffer from a file. (defun buf-kill-and-reload (fn) "Given FILE, cause the current version of that file to be loaded into a buffer. If the file was already in a buffer already, the buffer will be refreshed to contain the latest version of the file. If the buffer has been modified, the file will NOT be saved. Makes some attempt to keep the mark and point the same if the buffer was around before." (let ((buf (get-file-buffer fn))) (if buf (progn (switch-to-buffer buf) (let ((curp (point)) (curm (mark))) (set-buffer-modified-p nil) (kill-buffer buf) (find-file fn) (set-mark curm) (goto-char curp))) (find-file fn)) )) (defun make-rcs-name (fn) "Make the name for an RCS file from the normal file name." (if (not (string-match ",v$" fn)) (concat fn ",v") fn)) (defun make-normal-name (fn) "Make a normal file name from an RCS file name." (if (string-match ",v$" fn) (setq fn (substring fn 0 (match-beginning 0)))) (if (string-match "RCS/" fn) (setq fn (concat (substring fn 0 (match-beginning 0)) (substring fn (match-end 0))))) fn) (defun is-rcs-file-p (fn) "Return t if FILE is an RCS file." (string= (substring fn -2) ",v")) (defun has-rcs-file-p (fn) "Return t if FILE has an RCS file." (if (or (file-exists-p (concat (file-name-directory fn) "RCS/" (make-rcs-name (file-name-nondirectory fn)))) (file-exists-p (make-rcs-name fn))) t nil)) ;; find where a command is. (defun find-exec-command (cmd paths) "Return the full path to CMD from PATHS or just CMD if not found." (cond ((not paths) cmd) (t (if (file-exists-p (concat (car paths) "/" cmd)) (concat (car paths) "/" cmd) (find-exec-command cmd (cdr paths)))))) ;; get a log entry into the log buffer (defun get-rcs-log (banner buf) "Prompting with BANNER, get a RCS log entry into the given BUFFER." (save-excursion (save-window-excursion (if (not rcs-use-other-win) (switch-to-buffer buf) (switch-to-buffer-other-window buf)) (if (not rcs-keep-log) (erase-buffer)) (if rcs-edit-mode (funcall rcs-edit-mode)) (message (substitute-command-keys (concat banner " entry; \\[exit-recursive-edit] to end, \\[abort-recursive-edit] to abort."))) (recursive-edit) (message "Finished entry")))) (defun buffer-to-list (buffer-name) "Return a list, each line in BUFFER-NAME." (set-buffer buffer-name) (goto-char (point-min)) (if (re-search-forward "^.+$" (point-max) t) (progn (setq buf-list (list (buffer-substring (match-beginning 0) (match-end 0)))) (while (re-search-forward "^.+$" (point-max) t) (setq buf-list (append (list (buffer-substring (match-beginning 0) (match-end 0))) buf-list))) (reverse buf-list)))) ; Nice list-into-string function provided by ; Sebastian Kremer, Institute for Theoretical Physics, ; University of Cologne, West Germany ; BITNET: ab027@dk0rrzk0.bitnet (defun list-to-string (list-of-strings) "Take LIST-OF-STRINGS and return a string composed of all the elements of the list with spaces between each." (mapconcat (function identity) list-of-strings " ")) (defun string-to-list (string-of-lists) "Given a STRING-OF-LISTS, (characters separated by whitespace), returns a list with the string elements." (let ((i 0) (is 0) (list-all nil)) (while (string-match "[ \t]*\\([^ \t]+\\)[ \t]*" string-of-lists is) (progn (setq list-all (cons (substring string-of-lists (match-beginning 1) (match-end 1)) list-all)) (setq is (match-end 1)) (setq i (1+ i)))) (reverse list-all))) (defun list-loc (list target) "Given LIST, find TARGET in the list, and return its index (0=first). If not found, return nil" (catch 'match-found (let ((i 0)) (while list (if (equal (car list) target) (throw 'match-found i) (setq list (cdr list))) (setq i (+ i 1)))) nil)) (defun rcs-list-files (&optional directory-name) "Returns a list of RCS files in current directory, or optional DIRECTORY-NAME." (if (not (and directory-name (file-directory-p directory-name))) (setq directory-name "./")) (setq directory-name (file-name-as-directory directory-name)) (if (file-directory-p (setq rcs-directory (concat directory-name "RCS"))) (directory-files rcs-directory t ",v$") (directory-files directory-name t ",v$"))) ;;;; mainline functions. ;; Most functions have two forms; one which operates on a file, and ;; one which operates on the current buffer. The latter are obviously ;; made out the former. Some functions have a third form, one that ;; operates on a list of files contained in the current tags file. ;;; checkout functions ;; edit an RCS file by checking it out locked. (defun rcs-co-file (fn) "Check out and visit a file. If the file is already in a buffer, it is refreshed with the latest version from disk. If the file is in a buffer and the buffer has been modified, it will not be saved (the ice is thin here). If no RCS file exists for the file, it will go through an initial checkin." (interactive "FFile to check out: \n") (catch 'co-error (if (is-rcs-file-p fn) (setq fn (make-normal-name fn))) (if (not (has-rcs-file-p fn)) ;; no RCS file for this file? Create one, then. ;; this will wind up with the file locked. (if (not (rcs-ci-file fn t)) (progn (message "Cannot create initial file.") (sit-for rcs-error-time) (throw 'co-error nil)))) (if (and (file-exists-p fn) (file-writable-p fn)) (if (y-or-n-p (concat "File " fn " is writeable; overwrite? ")) (call-process "rm" nil nil nil "-f" (expand-file-name fn)) (progn (message " ") (throw 'co-error nil)))) (message "Checking out %s..." fn) ;; we must cd to the correct directory, and then execute the rcs ;; command. we must expand the directory to its proper name lest ;; rcs-shell-path be a shell that doesn't do it for us. (call-process rcs-shell-path nil nil nil "-c" (concat "cd " (expand-file-name (file-name-directory fn)) "; " (find-exec-command "co" rcs-exec-path) " -l " (file-name-nondirectory fn))) (buf-kill-and-reload fn) (message "Checkout done"))) ;; Edit the current buffer after an RCS checkout (defun rcs-co-buffer () "Check out and lock the current buffer. Use rcs-revert-buffer to check out this buffer without locking it." (interactive) (if (buffer-file-name) (rcs-co-file (buffer-file-name)) (message "No file associated with current buffer"))) (defun rcs-co-tags (&optional use-1st-log) "Perform rcs checkout of current tag table. If no prefix argument is given, get initial log file for each newly checked-in tag file; with prefix, use 1st initial log entry for all new RCS files." (interactive "P") (save-excursion (save-window-excursion (let ((next-file-list (tag-table-files)) (old-keep-log rcs-keep-log) (rcs-keep-log t) (old-use-prev-log rcs-use-prev-log) (rcs-use-prev-log nil)) (while next-file-list (progn (setq fn (car next-file-list)) (find-file fn) (rcs-co-file fn) (setq next-file-list (cdr next-file-list)) (if use-1st-log (setq rcs-use-prev-log t)))) (setq rcs-use-prev-log old-use-prev-log) (setq rcs-keep-log old-keep-log) (message "All files processed."))))) ;;; checkin functions ;; Check back in a file (defun rcs-ci-file (fn &optional locked) "Use RCS to check back in FILE, with a given comment. If a prefix argument is given, the file is left locked; otherwise, it is left unlocked. The file will always still exist. A checkin is forced if the variable rcs-force-checkin is t; otherwise, if the file is unchanged, it is simply left locked or unlocked. If the file is in a buffer and has been modified, it will be saved first. rcs-ci-file returns nil if it detected an error. The hook rcs-new-dir-hook is run after a new RCS directory is created; the hook rcs-new-file-hook is run after a file is checked in the first time. When this happens, 'fn' is the file being checked in, and 'dir' is the just-created directory." (interactive "fFile to check in: \nP") (if (is-rcs-file-p fn) (setq fn (make-normal-name fn))) (catch 'ci-error (if (not (file-exists-p fn)) (progn (message "File %s doesn't exist to check in." fn) (sit-for rcs-error-time) (throw 'ci-error nil))) (if (and (get-file-buffer fn) (buffer-modified-p (get-file-buffer fn))) (progn (if (not rcs-make-backup-files) (progn (make-local-variable 'make-backup-files) (setq make-backup-files nil))) (write-file fn))) (let ((log (get-buffer-create rcs-log-buffer)) (buf (get-file-buffer fn)) (buftmp (get-buffer-create rcs-temp-buffer)) (dir (concat (expand-file-name (file-name-directory fn)) "RCS")) (inital nil)) (if (not (has-rcs-file-p fn)) (progn (if (and (not (file-directory-p dir)) rcs-use-directories) (progn (message "Creating RCS directory %s..." dir) (call-process "mkdir" nil nil nil dir) (if (file-directory-p dir) t (message "Cannot create RCS directory %s" dir) (sit-for rcs-error-time) (throw 'ci-error nil)) (run-hooks 'rcs-new-dir-hooks))) (if (not rcs-use-prev-log) (get-rcs-log "General descriptive text" log)) (setq initial t)) (if (not rcs-use-prev-log) (get-rcs-log "Description of changes" log)) (setq initial nil)) (save-excursion (set-buffer buftmp) (erase-buffer) (save-excursion (set-buffer log) (message "Checking in %s..." fn) (call-process-region 1 (point-max) rcs-shell-path nil buftmp t "-c" (concat "cd " (expand-file-name (file-name-directory fn)) "; " (find-exec-command "ci" rcs-exec-path) (if rcs-force-checkin " -f " " ") (if (and rcs-use-login-name (user-login-name)) (concat " -w" (user-login-name) " ")) (if locked "-l " "-u ") (if (and initial rcs-initial-branch rcs-initial-rev) (concat "-r" rcs-initial-branch "." rcs-initial-rev " ")) (file-name-nondirectory fn))) (if (not rcs-keep-log) (kill-buffer log))) ;; check for error in checkin (goto-char (point-min)) (if (re-search-forward "error:" nil t) (progn (message "Cannot check in RCS file %s" fn) (sit-for rcs-error-time) (throw 'ci-error nil))) ;; check if no change in versions (if (not rcs-force-checkin) (progn (goto-char (point-min)) (if (re-search-forward "unchanged with respect" nil t) (progn (message "RCS file %s unchanged; left %s" fn (if locked "locked." "unlocked.")) (sit-for rcs-error-time) (if (not locked) (progn ;; unlock the file instead (erase-buffer) (call-process rcs-shell-path nil buftmp t "-c" (concat "cd " (expand-file-name (file-name-directory fn)) "; " (find-exec-command "co" rcs-exec-path) " -f -u " (file-name-nondirectory fn))))))))) (if initial (progn (run-hooks 'rcs-new-file-hook) (if (and initial rcs-initial-access) (let ((buftmp (get-buffer-create rcs-temp-buffer))) (save-excursion (set-buffer buftmp) (erase-buffer) (call-process rcs-shell-path nil buftmp t "-c" (concat "cd " (expand-file-name (file-name-directory fn)) "; " (find-exec-command "rcs" rcs-exec-path) " -a" rcs-initial-access " " (file-name-nondirectory fn))) (goto-char (point-min)) (if (re-search-forward "error:" nil t) (progn (message "Cannot set access list on RCS file") (throw 'ci-error nil))) (kill-buffer buftmp)))))) (if buf (buf-kill-and-reload fn)) (message "Checkin done") t)))) ;; check back in the current buffer (defun rcs-ci-buffer (&optional flag) "Check back in and unlock the current buffer. Saves the current buffer first. If prefix argument given, inverts the sense of rcs-ci-buffer-kills-buffer." (interactive "P") (if (buffer-file-name) (progn (if (rcs-ci-file (buffer-file-name) nil) (if (or (and rcs-ci-buffer-kills-buffer (not flag)) (and flag (not rcs-ci-buffer-kills-buffer))) (kill-buffer (current-buffer))) )) (message "No file associated with the current buffer"))) ;; make a change entry, ie. do a ci but leave the file locked. (defun rcs-log-buffer () "Record a change to the current buffer, but keep on editing it. Saves the current buffer first." (interactive) (if (buffer-file-name) (rcs-ci-file (buffer-file-name) 't) (message "No file associated with the current buffer"))) (defun rcs-ci-tags (&optional use-1st-log) "Perform rcs checkin of current tag table. If no prefix argument is given, get log file for each tag file; with prefix, use 1st log entry for all tag files." (interactive "P") (save-excursion (save-window-excursion (let ((next-file-list (tag-table-files)) (old-keep-log rcs-keep-log) (rcs-keep-log t) (old-use-prev-log rcs-use-prev-log) (rcs-use-prev-log nil)) (while next-file-list (progn (setq fn (car next-file-list)) (find-file fn) (rcs-ci-file fn) (setq next-file-list (cdr next-file-list)) (if use-1st-log (setq rcs-use-prev-log t)))) (setq rcs-use-prev-log old-use-prev-log) (setq rcs-keep-log old-keep-log) (message "All files processed."))))) ;;; rcsdiff functions ;; Find differences between the current version of a file and the last RCS ;; version and display these in a buffer. (defun rcs-diff-file (fn) "Run an rcsdiff on FILE and display the differences in another buffer." (interactive "fFile to diff: ") (if (not (has-rcs-file-p fn)) (message (concat "No RCS file exists for " fn)) (if (not (file-exists-p fn)) (message "File %s has not been checked out" fn) (let ((buf (get-buffer-create (concat "# rcs diff : " fn " #")))) (save-excursion (set-buffer buf) (erase-buffer)) (message "Diffing %s..." fn) (call-process rcs-shell-path nil buf nil "-c" (concat "cd " (expand-file-name (file-name-directory fn)) "; " (find-exec-command "rcsdiff" rcs-exec-path) " " rcs-diff-options " " (file-name-nondirectory fn))) (if rcs-use-other-win (switch-to-buffer-other-window buf) (switch-to-buffer buf)) (goto-char (point-min)) (message "Done"))))) ;; Diff the current buffer. (defun rcs-diff-buffer () "Run an rcsdiff on the current buffer. The file will not be saved first." (interactive) (if (buffer-file-name) (rcs-diff-file (buffer-file-name)) (message "No file associated with the current buffer."))) (defun rcs-diff-and-ci-buffer () "Save current buffer, run rcs-diff in other window and check in buffer." (interactive) (save-buffer) (let ((cur-win (selected-window)) (rcs-use-other-win t)) (rcs-diff-buffer) (select-window cur-win) (let ((rcs-use-other-win nil)) (rcs-ci-buffer)))) ;;; revert things ;; Nuke a checked-out version, for whatever reason. (defun rcs-revert-file (fn) "Unlock and revert FILE to its last RCS'd version. Handy when you locked a file that you later decided not to change. If the file was in a buffer, reload the buffer with the reverted version; otherwise, the file is not loaded." (interactive "fFile to revert: ") (let ((inbuf (get-file-buffer fn)) (buf (get-buffer-create rcs-temp-buffer))) (if (not (has-rcs-file-p fn)) (message (concat "No RCS file exists for " fn)) (if (not (y-or-n-p "Revert RCS file %s? " fn)) (message "aborted.") (message "Reverting RCS file %s..." fn) (call-process rcs-shell-path nil buf nil "-c" (concat "cd " (expand-file-name (file-name-directory fn)) "; rcs -u " (file-name-nondirectory fn) " ; rm -f " (file-name-nondirectory fn) " ; " (find-exec-command "co" rcs-exec-path) " " (file-name-nondirectory fn))) (if inbuf (buf-kill-and-reload fn)) (message "Done"))))) ;; revert the current buffer. (defun rcs-revert-buffer () "Unlock and revert the current buffer to its last RCS'd version. Does not save any changes." (interactive) (if (not (buffer-file-name)) (message "No file associated with the current buffer.") (not-modified) (rcs-revert-file (buffer-file-name)))) ;;; show logfiles ;; show the rcs logfile for a given file. (defun rcs-show-log-file (fn) "Show the RCS log for FILE." (interactive "FFile to show log of: ") (if (not (has-rcs-file-p fn)) (message (concat "No RCS file exists for " fn)) (let ((buf (get-buffer-create (concat "# rcs log : " fn " #")))) (save-excursion (set-buffer buf) (erase-buffer)) (message "Getting log for %s..." fn) (call-process rcs-shell-path nil buf nil "-c" (concat "cd " (expand-file-name (file-name-directory fn)) "; " (find-exec-command "rlog" rcs-exec-path) " " (file-name-nondirectory fn))) (if rcs-use-other-win (switch-to-buffer-other-window buf) (switch-to-buffer buf)) (goto-char (point-min)) (if rcs-use-other-win (select-window (previous-window))) (message "Done")))) ;; show the log for the current buffer. (defun rcs-show-log-buffer () "Show the current buffer's RCS log." (interactive) (if (buffer-file-name) (rcs-show-log-file (buffer-file-name)) (message "No file associated with the current buffer."))) ;;; refresh the current buffer from the on-disk copy ;; reload the current file. (defun rcs-refresh-buffer () "Reload the current buffer." (interactive) (if (buffer-file-name) (buf-kill-and-reload (buffer-file-name)) (message "No file associated with current buffer"))) ;;;; hook functions ;;; try a rcs checkout when trying to find a file ;; This function is suitable for being used as a hook on ;; find-file-not-found-hooks (defun rcs-try-file () "Function to check out automatically a RCS file when a find-file fails. Checks out the file, but does not lock it. Put this on your find-file-not-found-hooks hook." (let ((fn (buffer-file-name))) (if (not (has-rcs-file-p fn)) nil (message "Checking out %s..." fn) (call-process rcs-shell-path nil nil nil "-c" (concat "cd " (expand-file-name (file-name-directory fn)) "; " (find-exec-command "co" rcs-exec-path) " " (file-name-nondirectory fn))) (message "Checkout done") (insert-file-contents fn t)))) ;;; utility fn's to find about lockers of an rcs file ;; this section is rather slow; it's unfortunate that there isn't a faster ;; way of doing this. Oh well... ;; find out who is locking the various versions of a RCS file. ; ugh, recursive with a gory regexp. ugh blech. (defun rcs-match-lockers-forward (list-to-date end) "Adjunct to rcs-list-of-lockers; recursively builds the list of lockers." ; look for " : " (if (not (re-search-forward "^[ \t]+\\([^:;]+\\): [0-9.]+$" end t)) list-to-date ; match failed, return what we have ; skip to the end of the match, pull out the portion, and recurse. (goto-char (match-end 0)) (rcs-match-lockers-forward (cons (buffer-substring (match-beginning 1) (match-end 1)) list-to-date) end))) ;; who has the file locked (defun rcs-list-of-lockers (fn) "Returns a list (of strings) of the people who have FILE locked under RCS, or nil if there are no lockers or the file is not under RCS." (if (not (has-rcs-file-p fn)) nil (let ((buf (get-buffer-create rcs-temp-buffer))) (save-excursion (set-buffer buf) (erase-buffer)) (call-process rcs-shell-path nil buf nil "-c" (concat "cd " (expand-file-name (file-name-directory fn)) "; " (find-exec-command "rlog" rcs-exec-path) " -h " (file-name-nondirectory fn))) (save-excursion (set-buffer buf) (goto-char (point-min)) ; find the "locks: ..." line (if (not (re-search-forward "^locks:[ \t]*" (point-max) t)) nil (goto-char (match-end 0)) (let ((end (point-max))) (save-excursion (if (not (re-search-forward "^access list:" (point-max) t)) nil (setq end (match-beginning 0)))) (rcs-match-lockers-forward nil end))))))) ;; occasionally-useful predicate (defun rcs-file-is-locked-by-you-p (fn) "Returns T if FILE is locked by you under RCS, and NIL otherwise." (let ((lockers-list (rcs-list-of-lockers fn)) (your-name (user-login-name))) ; undoubtedly there is a find-string-in-list function hiding SOMEWHERE, ; and all I have to do is find it. in the mean time, use this hideous ; mess. (catch 'match-found (while lockers-list (if (equal (car lockers-list) your-name) (throw 'match-found t) (setq lockers-list (cdr lockers-list)))) nil))) (defun rcs-show-locked-files-directory () "List all files that are locked by you in current directory." (interactive) (let ((buf (get-buffer-create rcs-temp-buffer))) (save-excursion (set-buffer buf) (erase-buffer)) (call-process rcs-shell-path nil buf nil "-c" (concat "cd " (expand-file-name default-directory) "; " (find-exec-command "rlog" rcs-exec-path) " -L -R -l" (user-login-name) " RCS/*,v | " (find-exec-command "sed" rcs-exec-path) " -n 's:RCS/\\(.*\\),v:\\1:pg' " )) (if rcs-use-other-win (switch-to-buffer-other-window buf) (switch-to-buffer buf)) (goto-char (point-min)) (message "Done"))) ;;; show who has a file locked in the modeline ;; Give us some vaguely useful information in the modeline. (defun rcs-hack-modeline () "Modify the current modeline to tell whether the file is under RCS, and who the lockers are. Can be called interactively if you really want to." (interactive) (if (and buffer-file-name (has-rcs-file-p buffer-file-name)) (progn ; since this is a buffer-local thing, we don't want everyone to ; share this nice label... (make-local-variable 'global-mode-string) (setq global-mode-string (default-value 'global-mode-string)) (or global-mode-string (setq global-mode-string '(""))) (let ((locker-list (rcs-list-of-lockers buffer-file-name)) l-string) (if (not locker-list) (setq global-mode-string (append global-mode-string '(" [unlocked]"))) (setq l-string (list-to-string locker-list)) (setq global-mode-string (append global-mode-string (list (concat " [" l-string "]"))))))))) ;;;; startup actions ; Expand exec-path to include rcs-executable-path if necessary (if rcs-executable-path (setq rcs-exec-path (cons rcs-executable-path exec-path)) (setq rcs-exec-path exec-path)) ; set rcs-shell-path if necessary (if (not rcs-shell-path) (setq rcs-shell-path shell-file-name)) ; Run any startup hooks necessary. (run-hooks 'rcs-hook)