home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / elisp / interfaces / rcs-cks.el < prev    next >
Encoding:
Text File  |  1990-10-09  |  29.9 KB  |  848 lines

  1. ;; Date:     Wed, 10 Oct 90 20:36:12 EDT
  2. ;; From: Chris Siebenmann <cks@white.toronto.edu>
  3. ;; 
  4. ;;  Here's the most recent version:
  5. ;; 
  6. ;;; rcs-cks.el    deal with rcs'd files in a natural way
  7. ;; Copyright (C) 1989 Chris Siebenmann under the terms of the FSF GNU
  8. ;; General Public License, version 1
  9. ;; modified and added to by Ralph Finch (rfinch@water.ca.gov)
  10.  
  11. ;; $Header: /usr/users/cks/share/lisp/RCS/rcs-cks.el,v 1.35 90/05/24 16:05:16 cks Stab $
  12. ; Last edited: Mon Apr  9 16:33:40 1990 by cks (Chris Siebenmann) on grumpy.white
  13.  
  14. (provide 'rcs)
  15.  
  16.  
  17. ;;;;            Some Documentation
  18. ;; This file provides a convenient interface to rcs that allows you
  19. ;; to easily check out, check in, and log changes to files that you
  20. ;; edit, as well as automatically check out a file when you try and
  21. ;; edit it, and record on the mode line who has locked the file.
  22. ;; Please report any bugs found to cks@white.toronto.edu, and send me
  23. ;; a copy of anything interesting you add.
  24. ;;
  25. ;; Some portions of this package assume you are using strict locking
  26. ;; on your files, either by default or having it set explicitly.
  27. ;; In particular, rcs-co-file (and thus rcs-co-buffer) assumes that
  28. ;; a writable file by the same name as the one you are checking out is
  29. ;; a danger sign, and asks if you want to overwrite it.
  30. ;; I recommend the usage of strict locking.
  31. ;;
  32. ;; Suggested usage: enable strict locking, and make all source files
  33. ;; non-writable. Then whenever you try to edit a file that's
  34. ;; read-only, you know you have to check it out; if the file hasn't
  35. ;; already been placed under RCS, you will be prompted for an initial
  36. ;; description. Conversly, a modifiable file is one you have locked,
  37. ;; and can thus change freely.
  38. ;;
  39. ;; Problems when used by root:
  40. ;;  file-writable-p always returns true when your uid is 0, so you
  41. ;; will always be asked if you want to overwrite the file when you
  42. ;; check it out. There are two solutions: 1) use the mode line hook
  43. ;; to show you who has the file locked, if anyone or 2) load an
  44. ;; improved version of file-writeable-p that works a bit better when
  45. ;; you're root (send email to me for a copy of mine).
  46. ;;  The second caution is that if you're su'd to root files you check
  47. ;; out will have the locker recorded as root instead of you, although
  48. ;; who checked them back in will be correctly recorded. This appears
  49. ;; to be an unavoidable problem with rcs; co needs to have an
  50. ;; argument that lets you specify who is locking the file.
  51. ;;
  52. ;;  Rcs itself is free, and can be obtained via ftp from
  53. ;; arthur.cs.purdue.edu from pub/RCS, or from a number of uucp
  54. ;; archive sites. You will also need a version of GNU diff, also
  55. ;; widely available from ftp and uucp archive sites.
  56.  
  57. ;;  To use, bind the available functions to convenient keys. A good
  58. ;; set of functions to start by binding are rcs-ci-buffer,
  59. ;; rcs-co-buffer, and rcs-log-buffer. Note that rcs-co-buffer will
  60. ;; automatically create an RCS directory and check in the current
  61. ;; buffer if the file is not currently under RCS. This makes it
  62. ;; convenient to make all files for a software package read-only; then
  63. ;; when you need to change one, just load it in and do an
  64. ;; rcs-co-buffer.
  65. ;;  Most user-callable functions come in two flavors; one that works
  66. ;; on the current buffer, and one that works on any file
  67. ;; (rcs-log-buffer and rcs-refresh-buffer are the only exceptions).
  68. ;; The versions that operate on the current buffer make it very
  69. ;; convenient to (for example) load in a file, notice that it isn't
  70. ;; locked out, and check it out in order to change it.
  71.  
  72.  
  73. ;;; Functions available:
  74. ;; rcs-co-{file,buffer,tags}-    Check out a file or a buffer, or
  75. ;;                              series of tags files. If the
  76. ;;                file isn't already RCS'd, it will be
  77. ;;                checked in automatically.
  78. ;; rcs-ci-{file,buffer,tags}-    Check in a file or buffer, or series
  79. ;;                              of tags files. The file
  80. ;;                will be left checked out unlocked,
  81. ;;                although by default the buffer will be
  82. ;;                killed (see rcs-ci-buffer-kills-buffer).
  83. ;; rcs-log-buffer    -    Check in a buffer, and then
  84. ;;                immediately check it out locked again.
  85. ;;                Handy for logging intermediate stages
  86. ;;                in changes.
  87. ;; rcs-diff-{file,buffer}    rcsdiff the current version of the
  88. ;;                file against the most recent RCS
  89. ;;                version. Useful for seeing what
  90. ;;                changes you just made.
  91. ;; rcs-show-log-{buffer,file}    Show the RCS change log for a given
  92. ;;                file or buffer.
  93. ;; rcs-revert-{file,buffer}    Revert a file or buffer back to the
  94. ;;                last checked in version. An easy way
  95. ;;                to blow away changes you've decided
  96. ;;                you don't want, or back out of
  97. ;;                checking out locked a file you decide
  98. ;;                not to change.
  99. ;; rcs-refresh-buffer        Reload the current buffer from its file,
  100. ;;                usually because someone else has just
  101. ;;                unlocked it.
  102. ;;  The following two functions are intended to be placed on hooks,
  103. ;; instead of being called directly.
  104. ;; rcs-try-file    -        Put this on your find-file-not-found-hooks
  105. ;;                hook. If you try and edit a file that
  106. ;;                has been RCS'd and not checked out, it
  107. ;;                will automatically check it out for
  108. ;;                you.
  109. ;; rcs-hack-modeline    -    Put this on your find-file-hooks hook;
  110. ;;                when run, it will put a legend up
  111. ;;                about who has locked the file being
  112. ;;                edited in that buffer.
  113. ;;                Caution -- this can be slow.
  114. ;; Eg, to put both on hooks, add the following lisp code to your .emacs.
  115. ;; (setq find-file-not-found-hooks (list 'rcs-try-file))
  116. ;; (setq find-file-hooks (list 'rcs-hack-modeline))
  117.  
  118.  
  119. ;;; Hooks available:
  120. ;; rcs-hook    -    run at the end of this file (ie after all
  121. ;;            the functions have been loaded).
  122. ;; rcs-new-dir-hook    run when a new RCS directory has been created.
  123. ;; rcs-new-file-hook    run when a file is checked in for the first time.
  124. ;; (see rcs-ci-file for more details on the latter two)
  125. ;; All hooks are run with run-hooks.
  126.  
  127.  
  128. ;;; User-changeable variables.
  129. (defvar rcs-use-other-win t
  130.   "*If non-nil, pop to a separate window when doing two-window things
  131. (log entries and displaying diffs).")
  132. (defvar rcs-use-directories t
  133.   "*If non-nil, checkin will make a RCS directory if none exists.")
  134. (defvar rcs-diff-options "-qc"
  135.   "*If non-nil, any additional options rcsdiff will be given.")
  136. (defvar rcs-ci-buffer-kills-buffer nil
  137.   "*If non-nil, when a rcs-ci-buffer is done with no prefix arg, that buffer
  138. is killed.")
  139. (defvar rcs-executable-path nil
  140.   "*If non-nil, the default path to find an RCS command on.")
  141. (defvar rcs-edit-mode nil
  142.   "*If non-nil, the log buffer will be placed in this mode. Otherwise,
  143. the log buffer will be in default-major-mode.")
  144. (defvar rcs-use-login-name t
  145.   "*If non-nil, checkins will use the name you're currently logged in under,
  146. instead of the name for your current UID (eg, if you su to root and use emacs
  147. to check in a file, the RCS log will have your user name in it instead of
  148. 'root').")
  149. (defvar rcs-make-backup-files t
  150.   "*If non-nil, backups of checked-in files are made according to
  151. the make-backup-files variable.  Otherwise, prevents backups being made.")
  152. (defvar rcs-initial-branch "1"
  153.   "*If non-nil, branch number to assign an initial checkin.")
  154. (defvar rcs-initial-rev "0"
  155.   "*If non-nil, revision number to assign an initial checkin.")
  156. (defvar rcs-initial-access nil
  157.   " If non-nil, access list to assign to an initial checkin.")
  158. (defvar rcs-keep-log nil
  159.   "*If non-nil, keeps old log.  Otherwise gives user blank log message
  160. for each checkin.")
  161. (defvar rcs-force-checkin nil
  162.   "*If non-nil, force a checkin even if the file has not changed.")
  163. (defvar rcs-error-time 3
  164.   "*The length of time the rcs package will wait after an error
  165. message has been displayed before proceeding.")
  166.  
  167. ;; Hooks
  168. (defvar rcs-hook nil  "*Hooks run at the end of this file.")
  169. (defvar rcs-new-dir-hook nil
  170.   "*Hooks run when a new ./RCS directory is created.")
  171. (defvar rcs-new-file-hook nil
  172.   "*Hooks run when a file has been checked in for the first time.")
  173.  
  174.  
  175. ;;;; The code
  176. ;; Please, no flames about my Lisp style (constructive suggestions
  177. ;; gleefully welcomed); this is the first large piece of ELisp I've
  178. ;; written, and I'm *sure* there's lots of ways to improve it. Your
  179. ;; help in finding them is appreciated :-).
  180.  
  181.  
  182. ;;; Variables the user won't normally want to change
  183. (defvar rcs-log-buffer "#rcs log#")
  184. (defvar rcs-temp-buffer "#rcs temp#")
  185. (defvar rcs-exec-path nil
  186.   "Path rcs searches to find executables. Built from rcs-executable-path
  187. and exec-path.")
  188. ; set this to a small shell that starts fast.
  189. (defvar rcs-shell-path "/bin/sh"
  190.   "*If non-nil, the file name to load inferior shells for RCS commands from.
  191. If nil, shell-file-name's value is used instead.")
  192. (defvar rcs-use-prev-log nil
  193.   "*If non-nil, rcs-ci-file will not prompt for new log but simply
  194. use old log buffer.")
  195.  
  196.  
  197. ;;; utility functions.
  198.  
  199. ;; kill and reload a buffer from a file.
  200. (defun buf-kill-and-reload (fn)
  201.   "Given FILE, cause the current version of that file to be loaded into a
  202. buffer. If the file was already in a buffer already, the buffer will be
  203. refreshed to contain the latest version of the file. If the buffer has
  204. been modified, the file will NOT be saved.
  205.  Makes some attempt to keep the mark and point the same if the buffer
  206. was around before."
  207.   (let ((buf (get-file-buffer fn)))
  208.     (if buf
  209.     (progn
  210.       (switch-to-buffer buf)
  211.       (let ((curp (point))
  212.         (curm (mark)))
  213.         (set-buffer-modified-p nil)
  214.         (kill-buffer buf)
  215.         (find-file fn)
  216.         (set-mark curm)
  217.         (goto-char curp)))
  218.       (find-file fn))
  219.     ))
  220.  
  221. (defun make-rcs-name (fn)
  222.   "Make the name for an RCS file from the normal file name."
  223.   (if (not (string-match ",v$" fn))
  224.       (concat fn ",v")
  225.     fn))
  226.  
  227. (defun make-normal-name (fn)
  228.   "Make a normal file name from an RCS file name."
  229.   (if (string-match ",v$" fn)
  230.       (setq fn (substring fn 0 (match-beginning 0))))
  231.   (if (string-match "RCS/" fn)
  232.       (setq fn (concat
  233.         (substring fn 0 (match-beginning 0))
  234.         (substring fn (match-end 0)))))
  235.   fn)
  236.  
  237. (defun is-rcs-file-p (fn)
  238.   "Return t if FILE is an RCS file."
  239.   (string= (substring fn -2) ",v"))
  240.  
  241. (defun has-rcs-file-p (fn)
  242.   "Return t if FILE has an RCS file."
  243.   (if (or (file-exists-p (concat (file-name-directory fn) "RCS/"
  244.                  (make-rcs-name (file-name-nondirectory fn))))
  245.       (file-exists-p (make-rcs-name fn)))
  246.       t
  247.     nil))
  248.  
  249. ;; find where a command is.
  250. (defun find-exec-command (cmd paths)
  251.   "Return the full path to CMD from PATHS or just CMD if not found."
  252.   (cond ((not paths) cmd)
  253.     (t (if (file-exists-p (concat (car paths) "/" cmd))
  254.            (concat (car paths) "/" cmd)
  255.          (find-exec-command cmd (cdr paths))))))
  256.  
  257. ;; get a log entry into the log buffer
  258. (defun get-rcs-log (banner buf)
  259.   "Prompting with BANNER, get a RCS log entry into the given BUFFER."
  260.   (save-excursion
  261.     (save-window-excursion
  262.       (if (not rcs-use-other-win)
  263.       (switch-to-buffer buf)
  264.     (switch-to-buffer-other-window buf))
  265.       (if (not rcs-keep-log)
  266.       (erase-buffer))
  267.       (if rcs-edit-mode
  268.       (funcall rcs-edit-mode))
  269.       (message
  270.        (substitute-command-keys
  271.     (concat banner " entry; \\[exit-recursive-edit] to end, \\[abort-recursive-edit] to abort.")))
  272.       (recursive-edit)
  273.       (message "Finished entry"))))
  274.  
  275. (defun buffer-to-list (buffer-name)
  276.   "Return a list, each line in BUFFER-NAME."
  277.   (set-buffer buffer-name)
  278.   (goto-char (point-min))
  279.   (if (re-search-forward "^.+$" (point-max) t)
  280.       (progn
  281.     (setq buf-list
  282.            (list (buffer-substring (match-beginning 0) (match-end 0))))
  283.     (while (re-search-forward "^.+$" (point-max) t)
  284.       (setq buf-list
  285.         (append (list (buffer-substring (match-beginning 0) (match-end 0)))
  286.             buf-list)))
  287.     (reverse buf-list))))
  288.  
  289. ; Nice list-into-string function provided by
  290. ; Sebastian Kremer, Institute for Theoretical Physics,
  291. ; University of Cologne, West Germany
  292. ; BITNET: ab027@dk0rrzk0.bitnet
  293. (defun list-to-string (list-of-strings)
  294.   "Take LIST-OF-STRINGS and return a string composed of all the elements
  295. of the list with spaces between each."
  296.   (mapconcat (function identity) list-of-strings " "))
  297.  
  298. (defun string-to-list (string-of-lists)
  299.   "Given a STRING-OF-LISTS, (characters separated by whitespace),
  300. returns a list with the string elements."
  301.   (let ((i 0)
  302.     (is 0)
  303.     (list-all nil))
  304.     (while (string-match
  305.         "[ \t]*\\([^ \t]+\\)[ \t]*"
  306.         string-of-lists
  307.         is)
  308.       (progn
  309.     (setq list-all (cons (substring string-of-lists
  310.                     (match-beginning 1)
  311.                     (match-end 1))
  312.                  list-all))
  313.     (setq is (match-end 1))
  314.     (setq i (1+ i))))
  315.     (reverse list-all)))
  316.  
  317. (defun list-loc (list target)
  318.   "Given LIST, find TARGET in the list, and return its index (0=first).
  319. If not found, return nil"
  320.   (catch 'match-found
  321.     (let ((i 0))
  322.       (while list
  323.     (if (equal (car list) target)
  324.         (throw 'match-found i)
  325.       (setq list (cdr list)))
  326.     (setq i (+ i 1))))
  327.     nil))
  328.  
  329. (defun rcs-list-files (&optional directory-name)
  330.   "Returns a list of RCS files in current directory, or
  331. optional DIRECTORY-NAME."
  332.   (if (not (and directory-name
  333.        (file-directory-p directory-name)))
  334.       (setq directory-name "./"))
  335.   (setq directory-name (file-name-as-directory directory-name))
  336.   (if (file-directory-p (setq rcs-directory (concat directory-name "RCS")))
  337.       (directory-files rcs-directory t ",v$")
  338.     (directory-files directory-name t ",v$")))
  339.  
  340.  
  341. ;;;; mainline functions.
  342. ;; Most functions have two forms; one which operates on a file, and
  343. ;; one which operates on the current buffer. The latter are obviously
  344. ;; made out the former. Some functions have a third form, one that
  345. ;; operates on a list of files contained in the current tags file.
  346.  
  347.  
  348. ;;; checkout functions
  349. ;; edit an RCS file by checking it out locked.
  350. (defun rcs-co-file (fn)
  351.   "Check out and visit a file. If the file is already in a buffer, it is
  352. refreshed with the latest version from disk. If the file is in a buffer and
  353. the buffer has been modified, it will not be saved (the ice is thin here).
  354. If no RCS file exists for the file, it will go through an initial checkin."
  355.   (interactive "FFile to check out: \n")
  356.   (catch 'co-error
  357.     (if (is-rcs-file-p fn)
  358.     (setq fn (make-normal-name fn)))
  359.     (if (not (has-rcs-file-p fn))
  360.     ;; no RCS file for this file? Create one, then.
  361.     ;; this will wind up with the file locked.
  362.     (if (not (rcs-ci-file fn t))
  363.         (progn
  364.           (message "Cannot create initial file.")
  365.           (sit-for rcs-error-time)
  366.           (throw 'co-error nil))))
  367.     (if (and (file-exists-p fn)
  368.          (file-writable-p fn))
  369.     (if (y-or-n-p (concat "File " fn " is writeable; overwrite? "))
  370.         (call-process "rm" nil nil nil "-f" (expand-file-name fn))
  371.       (progn
  372.         (message " ")
  373.         (throw 'co-error nil))))
  374.     (message "Checking out %s..." fn)
  375.     ;; we must cd to the correct directory, and then execute the rcs
  376.     ;; command. we must expand the directory to its proper name lest
  377.     ;; rcs-shell-path be a shell that doesn't do it for us.
  378.     (call-process rcs-shell-path nil nil nil "-c"
  379.           (concat "cd " (expand-file-name
  380.                  (file-name-directory fn)) "; "
  381.                  (find-exec-command "co" rcs-exec-path) " -l "
  382.                  (file-name-nondirectory fn)))
  383.     (buf-kill-and-reload fn)
  384.     (message "Checkout done")))
  385.  
  386. ;; Edit the current buffer after an RCS checkout
  387. (defun rcs-co-buffer ()
  388.   "Check out the current buffer."
  389.   (interactive)
  390.   (if (buffer-file-name)
  391.       (rcs-co-file (buffer-file-name))
  392.     (message "No file associated with current buffer")))
  393.  
  394. (defun rcs-co-tags (&optional use-1st-log)
  395.   "Perform rcs checkout of current tag table.  If no prefix argument
  396. is given, get initial log file for each newly checked-in tag file;
  397. with prefix, use 1st initial log entry for all new RCS files."
  398.   (interactive "P")
  399.   (save-excursion
  400.     (save-window-excursion
  401.       (let ((next-file-list (tag-table-files))
  402.         (old-keep-log rcs-keep-log)
  403.         (rcs-keep-log t)
  404.         (old-use-prev-log rcs-use-prev-log)
  405.         (rcs-use-prev-log nil))
  406.     (while next-file-list
  407.       (progn
  408.         (setq fn (car next-file-list))
  409.         (find-file fn)
  410.         (rcs-co-file fn)
  411.         (setq next-file-list (cdr next-file-list))
  412.         (if use-1st-log
  413.         (setq rcs-use-prev-log t))))
  414.     (setq rcs-use-prev-log old-use-prev-log)
  415.     (setq rcs-keep-log old-keep-log)
  416.     (message "All files processed.")))))
  417.  
  418.  
  419. ;;; checkin functions
  420. ;; Check back in a file
  421. (defun rcs-ci-file (fn &optional locked)
  422.   "Use RCS to check back in FILE, with a given comment. If a prefix
  423. argument is given, the file is left locked; otherwise, it is left unlocked.
  424. The file will always still exist. A checkin is forced if the variable
  425. rcs-force-checkin is t; otherwise, if the file is unchanged, it is simply
  426. left locked or unlocked.  If the file is in a buffer and has been
  427. modified, it will be saved first.  rcs-ci-file returns nil if it
  428. detected an error.
  429.  
  430.  The hook rcs-new-dir-hook is run after a new RCS directory is created;
  431. the hook rcs-new-file-hook is run after a file is checked in the first
  432. time. When this happens, 'fn' is the file being checked in, and 'dir'
  433. is the just-created directory."
  434.   (interactive "fFile to check in: \nP")
  435.   (if (is-rcs-file-p fn)
  436.       (setq fn (make-normal-name fn)))
  437.   (catch 'ci-error
  438.     (if (not (file-exists-p fn))
  439.     (progn
  440.       (message "File %s doesn't exist to check in." fn)
  441.       (sit-for rcs-error-time)
  442.       (throw 'ci-error nil)))
  443.     (if (and (get-file-buffer fn)
  444.          (buffer-modified-p (get-file-buffer fn)))
  445.     (progn
  446.       (if (not rcs-make-backup-files)
  447.           (progn
  448.         (make-local-variable 'make-backup-files)
  449.         (setq make-backup-files nil)))
  450.       (write-file fn)))
  451.     (let ((log (get-buffer-create rcs-log-buffer))
  452.       (buf (get-file-buffer fn))
  453.       (buftmp (get-buffer-create rcs-temp-buffer))
  454.       (dir (concat (expand-file-name (file-name-directory fn)) "RCS"))
  455.       (inital nil))
  456.       (if (not (has-rcs-file-p fn))
  457.       (progn
  458.         (if (and (not (file-directory-p dir))
  459.              rcs-use-directories)
  460.         (progn
  461.           (message "Creating RCS directory %s..." dir)
  462.           (call-process "mkdir" nil nil nil dir)
  463.           (if (file-directory-p dir)
  464.               t
  465.             (message "Cannot create RCS directory %s" dir)
  466.             (sit-for rcs-error-time)
  467.             (throw 'ci-error nil))
  468.           (run-hooks 'rcs-new-dir-hooks)))
  469.         (if (not rcs-use-prev-log)
  470.         (get-rcs-log "General descriptive text" log))
  471.         (setq initial t))
  472.     (if (not rcs-use-prev-log)
  473.         (get-rcs-log "Description of changes" log))
  474.     (setq initial nil))
  475.       (save-excursion
  476.     (set-buffer buftmp)
  477.     (erase-buffer)
  478.     (save-excursion
  479.       (set-buffer log)
  480.       (message "Checking in %s..." fn)
  481.       (call-process-region
  482.        1 (point-max) rcs-shell-path nil buftmp t "-c"
  483.        (concat "cd " (expand-file-name (file-name-directory fn))
  484.            "; "
  485.            (find-exec-command "ci" rcs-exec-path)
  486.            (if rcs-force-checkin
  487.                " -f "
  488.              " ")
  489.            (if (and rcs-use-login-name
  490.                 (user-login-name))
  491.                (concat " -w" (user-login-name) " "))
  492.            (if locked "-l "
  493.              "-u ")
  494.            (if (and initial rcs-initial-branch rcs-initial-rev)
  495.                (concat "-r"
  496.                    rcs-initial-branch "." rcs-initial-rev " "))
  497.            (file-name-nondirectory fn)))
  498.       (if (not rcs-keep-log)
  499.           (kill-buffer log)))
  500.     ;; check for error in checkin
  501.     (goto-char (point-min))
  502.     (if (re-search-forward "error:" nil t)
  503.         (progn
  504.           (message "Cannot check in RCS file %s" fn)
  505.           (sit-for rcs-error-time)
  506.           (throw 'ci-error nil)))
  507.     ;; check if no change in versions
  508.     (if (not rcs-force-checkin)
  509.         (progn
  510.           (goto-char (point-min))
  511.           (if (re-search-forward "unchanged with respect" nil t)
  512.           (progn
  513.             (message "RCS file %s unchanged; left %s" fn
  514.                  (if locked
  515.                  "locked."
  516.                    "unlocked."))
  517.             (sit-for rcs-error-time)
  518.             (if (not locked)
  519.             (progn
  520.               ;; unlock the file instead
  521.               (erase-buffer)
  522.               (call-process
  523.                rcs-shell-path nil buftmp t "-c"
  524.                (concat "cd "
  525.                    (expand-file-name
  526.                     (file-name-directory fn))
  527.                    "; "
  528.                    (find-exec-command "co" rcs-exec-path)
  529.                    " -f -u "
  530.                    (file-name-nondirectory fn)))))))))
  531.     (if initial
  532.         (progn
  533.           (run-hooks 'rcs-new-file-hook)
  534.           (if (and initial rcs-initial-access)
  535.           (let ((buftmp (get-buffer-create rcs-temp-buffer)))
  536.             (save-excursion
  537.               (set-buffer buftmp)
  538.               (erase-buffer)
  539.               (call-process
  540.                rcs-shell-path nil buftmp t "-c"
  541.                (concat "cd " (expand-file-name
  542.                       (file-name-directory fn))
  543.                    "; "
  544.                    (find-exec-command "rcs" rcs-exec-path)
  545.                    " -a" rcs-initial-access " "
  546.                    (file-name-nondirectory fn)))
  547.               (goto-char (point-min))
  548.               (if (re-search-forward "error:" nil t)
  549.               (progn
  550.                 (message "Cannot set access list on RCS file")
  551.                 (throw 'ci-error nil)))
  552.               (kill-buffer buftmp))))))
  553.     (if buf
  554.         (buf-kill-and-reload fn))
  555.     (message "Checkin done")
  556.     t))))
  557.  
  558. ;; check back in the current buffer
  559. (defun rcs-ci-buffer (&optional flag)
  560.   "Check back in and unlock the current buffer. Saves the current buffer
  561. first. If prefix argument given, inverts the sense of
  562. rcs-ci-buffer-kills-buffer."
  563.   (interactive "P")
  564.   (if (buffer-file-name)
  565.       (progn
  566.     (if (rcs-ci-file (buffer-file-name) nil)
  567.         (if (or (and rcs-ci-buffer-kills-buffer (not flag))
  568.             (and flag (not rcs-ci-buffer-kills-buffer)))
  569.         (kill-buffer (current-buffer)))
  570.       ))
  571.     (message "No file associated with the current buffer")))
  572.  
  573. ;; make a change entry, ie. do a ci but leave the file locked.
  574. (defun rcs-log-buffer ()
  575.   "Record a change to the current buffer, but keep on editing it. Saves the
  576. current buffer first."
  577.   (interactive)
  578.   (if (buffer-file-name)
  579.       (rcs-ci-file (buffer-file-name) 't)
  580.     (message "No file associated with the current buffer")))
  581.  
  582. (defun rcs-ci-tags (&optional use-1st-log)
  583.   "Perform rcs checkin of current tag table.  If no prefix argument
  584. is given, get log file for each tag file; with prefix, use 1st log
  585. entry for all tag files."
  586.   (interactive "P")
  587.   (save-excursion
  588.     (save-window-excursion
  589.       (let ((next-file-list (tag-table-files))
  590.         (old-keep-log rcs-keep-log)
  591.         (rcs-keep-log t)
  592.         (old-use-prev-log rcs-use-prev-log)
  593.         (rcs-use-prev-log nil))
  594.     (while next-file-list
  595.       (progn
  596.         (setq fn (car next-file-list))
  597.         (find-file fn)
  598.         (rcs-ci-file fn)
  599.         (setq next-file-list (cdr next-file-list))
  600.         (if use-1st-log
  601.         (setq rcs-use-prev-log t))))
  602.     (setq rcs-use-prev-log old-use-prev-log)
  603.     (setq rcs-keep-log old-keep-log)
  604.     (message "All files processed.")))))
  605.  
  606.  
  607. ;;; rcsdiff functions
  608. ;; Find differences between the current version of a file and the last RCS
  609. ;; version and display these in a buffer.
  610. (defun rcs-diff-file (fn)
  611.   "Run an rcsdiff on FILE and display the differences in another buffer."
  612.   (interactive "fFile to diff: ")
  613.   (if (not (has-rcs-file-p fn))
  614.       (message (concat "No RCS file exists for " fn))
  615.     (if (not (file-exists-p fn))
  616.     (message "File %s has not been checked out" fn)
  617.       (let ((buf (get-buffer-create (concat "# rcs diff : "
  618.                         fn " #"))))
  619.     (save-excursion
  620.       (set-buffer buf)
  621.       (erase-buffer))
  622.     (message "Diffing %s..." fn)
  623.     (call-process rcs-shell-path nil buf nil "-c"
  624.             (concat "cd " (expand-file-name
  625.                    (file-name-directory fn)) "; "
  626.                 (find-exec-command "rcsdiff" rcs-exec-path) " "
  627.                 rcs-diff-options " "
  628.                 (file-name-nondirectory fn)))
  629.     (if rcs-use-other-win
  630.         (switch-to-buffer-other-window buf)
  631.       (switch-to-buffer buf))
  632.     (goto-char (point-min))
  633.     (message "Done")))))
  634.  
  635. ;; Diff the current buffer.
  636. (defun rcs-diff-buffer ()
  637.   "Run an rcsdiff on the current buffer. The file will not be saved first."
  638.   (interactive)
  639.   (if (buffer-file-name)
  640.       (rcs-diff-file (buffer-file-name))
  641.     (message "No file associated with the current buffer.")))
  642.  
  643.  
  644. ;;; revert things
  645. ;; Nuke a checked-out version, for whatever reason.
  646. (defun rcs-revert-file (fn)
  647.   "Unlock and revert FILE to its last RCS'd version. Handy when you locked
  648. a file that you later decided not to change. If the file was in a buffer,
  649. reload the buffer with the reverted version; otherwise, the file is not
  650. loaded."
  651.   (interactive "fFile to revert: ")
  652.   (let ((inbuf (get-file-buffer fn))
  653.     (buf (get-buffer-create rcs-temp-buffer)))
  654.     (if (not (has-rcs-file-p fn))
  655.     (message (concat "No RCS file exists for " fn))
  656.       (message "Reverting RCS file %s..." fn)
  657.       (call-process rcs-shell-path nil buf nil "-c"
  658.             (concat "cd " (expand-file-name
  659.                    (file-name-directory fn)) "; rcs -u "
  660.                 (file-name-nondirectory fn)
  661.                 " ; rm -f " (file-name-nondirectory fn)
  662.                 " ; " (find-exec-command "co" rcs-exec-path) " "
  663.                 (file-name-nondirectory fn)))
  664.       (if inbuf
  665.       (buf-kill-and-reload fn))
  666.       (message "Done"))))
  667.  
  668. ;; revert the current buffer.
  669. (defun rcs-revert-buffer ()
  670.   "Unlock and revert the current buffer to its last RCS'd version. Does not
  671. save any changes."
  672.   (interactive)
  673.   (if (not (buffer-file-name))
  674.       (message "No file associated with the current buffer.")
  675.     (not-modified)
  676.     (rcs-revert-file (buffer-file-name))))
  677.  
  678.  
  679. ;;; show logfiles
  680. ;; show the rcs logfile for a given file.
  681. (defun rcs-show-log-file (fn)
  682.   "Show the RCS log for FILE."
  683.   (interactive "FFile to show log of: ")
  684.   (if (not (has-rcs-file-p fn))
  685.       (message (concat "No RCS file exists for " fn))
  686.     (let ((buf (get-buffer-create (concat "# rcs log : "
  687.                       fn " #"))))
  688.       (save-excursion
  689.     (set-buffer buf)
  690.     (erase-buffer))
  691.       (message "Getting log for %s..." fn)
  692.     (call-process rcs-shell-path nil buf nil "-c"
  693.               (concat "cd " (expand-file-name
  694.                      (file-name-directory fn)) "; "
  695.                   (find-exec-command "rlog" rcs-exec-path) " "
  696.                   (file-name-nondirectory fn)))
  697.     (if rcs-use-other-win
  698.         (switch-to-buffer-other-window buf)
  699.       (switch-to-buffer buf))
  700.     (goto-char (point-min))
  701.     (if rcs-use-other-win
  702.         (select-window (previous-window)))
  703.     (message "Done"))))
  704.  
  705. ;; show the log for the current buffer.
  706. (defun rcs-show-log-buffer ()
  707.   "Show the current buffer's RCS log."
  708.   (interactive)
  709.   (if (buffer-file-name)
  710.       (rcs-show-log-file (buffer-file-name))
  711.     (message "No file associated with the current buffer.")))
  712.  
  713.  
  714. ;;; refresh the current buffer from the on-disk copy
  715. ;; reload the current file.
  716. (defun rcs-refresh-buffer ()
  717.   "Reload the current buffer."
  718.   (interactive)
  719.   (if (buffer-file-name)
  720.       (buf-kill-and-reload (buffer-file-name))
  721.     (message "No file associated with current buffer")))
  722.  
  723.  
  724. ;;;; hook functions
  725.  
  726. ;;; try a rcs checkout when trying to find a file
  727. ;; This function is suitable for being used as a hook on
  728. ;; find-file-not-found-hooks
  729. (defun rcs-try-file ()
  730.   "Function to check out automatically a RCS file when a find-file fails.
  731. Checks out the file, but does not lock it. Put this on your
  732. find-file-not-found-hooks hook."
  733.   (let ((fn (buffer-file-name)))
  734.     (if (not (has-rcs-file-p fn))
  735.     nil
  736.       (message "Checking out %s..." fn)
  737.       (call-process rcs-shell-path nil nil nil "-c"
  738.             (concat "cd " (expand-file-name
  739.                    (file-name-directory fn)) "; "
  740.                 (find-exec-command "co" rcs-exec-path) " "
  741.                 (file-name-nondirectory fn)))
  742.       (message "Checkout done")
  743.       (insert-file-contents fn t))))
  744.  
  745.  
  746. ;;; utility fn's to find about lockers of an rcs file
  747. ;; this section is rather slow; it's unfortunate that there isn't a faster
  748. ;; way of doing this. Oh well...
  749.  
  750. ;; find out who is locking the various versions of a RCS file.
  751.  
  752. ; ugh, recursive with a gory regexp. ugh blech.
  753. (defun rcs-match-lockers-forward (list-to-date)
  754.   "Adjunct to rcs-list-of-lockers; recursively builds the list of lockers."
  755.   ; look for " <user>: <revision level>"
  756.   (if (not (re-search-forward "[ \t]*\\([^:;]*\\): [0-9]*\\.[0-9]*"
  757.                   (point-max) t))
  758.       list-to-date ; match failed, return what we have
  759.     ; skip to the end of the match, pull out the <user> portion, and recurse.
  760.     (goto-char (match-end 0))
  761.     (rcs-match-lockers-forward (cons (buffer-substring (match-beginning 1)
  762.                                (match-end 1))
  763.                      list-to-date))))
  764.  
  765. ;; who has the file locked
  766. (defun rcs-list-of-lockers (fn)
  767.   "Returns a list (of strings) of the people who have FILE locked
  768. under RCS, or nil if there are no lockers or the file is not under RCS."
  769.   (if (not (has-rcs-file-p fn))
  770.       nil
  771.     (let ((buf (get-buffer-create rcs-temp-buffer)))
  772.       (save-excursion
  773.     (set-buffer buf)
  774.     (erase-buffer))
  775.       (call-process rcs-shell-path nil buf nil "-c"
  776.             (concat "cd " (expand-file-name
  777.                    (file-name-directory fn)) "; "
  778.                 (find-exec-command "rlog" rcs-exec-path) " -h "
  779.                 (file-name-nondirectory fn)))
  780.     (save-excursion
  781.       (set-buffer buf)
  782.       (goto-char (point-min))
  783.       ; find the "locks: ..." line
  784.       (if (not (re-search-forward "^locks:[ \t]*"
  785.                       (point-max) t))
  786.           nil
  787.         (goto-char (match-end 0))
  788.         (save-excursion
  789.           (forward-line)
  790.           (let ((beg (point)))
  791.         (end-of-buffer)
  792.         (delete-region beg (point))))
  793.         (rcs-match-lockers-forward nil))))))
  794.  
  795. ;; occasionally-useful predicate
  796. (defun rcs-file-is-locked-by-you-p (fn)
  797.   "Returns T if FILE is locked by you under RCS, and NIL otherwise."
  798.   (let ((lockers-list (rcs-list-of-lockers fn))
  799.     (your-name (user-login-name)))
  800.     ; undoubtedly there is a find-string-in-list function hiding SOMEWHERE,
  801.     ; and all I have to do is find it. in the mean time, use this hideous
  802.     ; mess.
  803.     (catch 'match-found
  804.       (while lockers-list
  805.     (if (equal (car lockers-list) your-name)
  806.         (throw 'match-found t)
  807.       (setq lockers-list (cdr lockers-list))))
  808.       nil)))
  809.  
  810.  
  811. ;;; show who has a file locked in the modeline
  812. ;; Give us some vaguely useful information in the modeline.
  813. (defun rcs-hack-modeline ()
  814.   "Modify the current modeline to tell whether the file is under RCS, and
  815. who the lockers are. Can be called interactively if you really want to."
  816.   (interactive)
  817.   (if (and buffer-file-name
  818.        (has-rcs-file-p buffer-file-name))
  819.       (progn
  820.     ; since this is a buffer-local thing, we don't want everyone to
  821.         ; share this nice label...
  822.     (make-local-variable 'global-mode-string)
  823.     (or global-mode-string (setq global-mode-string '("")))
  824.     (let ((locker-list (rcs-list-of-lockers buffer-file-name))
  825.           l-string)
  826.       (if (not locker-list)
  827.           (setq global-mode-string
  828.             (append global-mode-string '(" [unlocked]")))
  829.         (setq l-string (list-to-string locker-list))
  830.         (setq global-mode-string
  831.           (append global-mode-string
  832.               (list (concat " [locked by " l-string "]")))))))))
  833.  
  834.  
  835. ;;;; startup actions
  836.  
  837. ; Expand exec-path to include rcs-executable-path if necessary
  838. (if rcs-executable-path
  839.     (setq rcs-exec-path (cons rcs-executable-path exec-path))
  840.   (setq rcs-exec-path exec-path))
  841. ; set rcs-shell-path if necessary
  842. (if (not rcs-shell-path)
  843.     (setq rcs-shell-path shell-file-name))
  844.  
  845. ; Run any startup hooks necessary.
  846. (run-hooks 'rcs-hook)
  847.  
  848.