We Pragmatic Programmers know that putting our source code under version control is important. I use CVS for version control both at work and my open source stuff at sourceforge. But there are times when I work on my laptop without checking in for days or weeks at a time. At these times, I rely on RCS, a pessimistic version control system that ships as part of the Unix (and Linux) operating systems. By pessimistic, I mean that a checkout will lock the file, which cannot be checked out by someone else until the file is checked back in. This is in contrast to the optimistic style favored by CVS and Subversion, where multiple authors can check out the same file at the same time, and any reconciliation is done at the time the changes are checked in.
Unlike CVS or Subversion, RCS does not need a server component, so there is no additional daemon to run on your laptop. Simply create an RCS subdirectory under the directory you want to put under version control , and you can use the standard RCS commands to put files under version control. To checkout a file, use the command:
ci -l MyFile.java
and this will create a version controlled file RCS/MyFile.java,v. The ci (check-in) is slightly misnamed in this example, since it also locks the file and checks it out at the same time in one command. There are other commands like rcsdiff, rlog and ident, which allow you to see the differences between two revisions of a file, display a log of all commit comments and get a display ident information for a file respectively. For a complete list, checkout:
man ci
and follow the links to the other commands.
One thing that was holding me back from using RCS was the need to remember an entirely new set of commands for version control. I have been using CVS for so long that I have developed finger memory. Also it was not clear to me how to apply a command to a hierarchy of files. Since I develop mostly in Java, and Java code is organized into a hierarchy of packages, this is important functionality to me.
For these reasons, I developed a script in Python which provides me functionality which is fairly close to that in CVS. Here is the help output from the script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Usage: rcstool.py [add|diff|log|commit|help] [-m=comment] [filename]
add [dirname] - puts a new directory under RCS control. If no
dirname is specified, then puts the current directory
under RCS control
diff [filename] - Reports differences between all files under
the current directory and the RCS version, or if a filename
is specified, the actual differences between the file and
its RCS version
log filename - Prints the RCS log for the specified file
commit -m=comment [filename]+ - Specifies a list of files that
needs to be checked into RCS. The comment to put in all
files is specified by preceding with -m=. Note that multi-
word comments should be enclosed in quotes
help - print this message
|
The add is analogous to the CVS add subcommand, the diff to -nq update (in the no filename supplied mode) and diff (where the filename is supplied), the log to log and the commit to commit, respectively. More importantly, the script will traverse the file system from the root and apply the command to each file it encounters.
Here is the script (rcstool.py) for those interested. You will notice that rcstool.py has been put under RCS version control as well :-). You will need the Python interpreter installed to runA this. Most Red Hat Linux distributions will have this pre-installed, since Python is used for writing and running the sysadmin GUI tools.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | #!/usr/bin/python
# $Id: rcstool.py,v 1.3 2005/12/24 01:01:24 sujit Exp sujit $
# $Source: /home/sujit/bin/python/RCS/rcstool.py,v $
"""
Provides a CVS like wrapper for RCS with common commands. Please make sure
to modify the commands RLOG, RCSDIFF and COMMIT to conform to your local
system.
"""
import sys
import os
# Specify locations of the various commands you want to use here
RLOG = "/usr/bin/rlog"
RCSDIFF = "/usr/bin/rcsdiff"
COMMIT = "/usr/bin/ci -l"
def main():
"""
This is how we are called.
"""
if (len(sys.argv) == 1 or sys.argv[1] == "help"):
help()
elif (sys.argv[1] == "add"):
adddir = os.getcwd()
if (len(sys.argv) == 3):
adddir = sys.argv[2]
add(adddir)
elif (sys.argv[1] == "diff"):
filename = "*"
if (len(sys.argv) == 3):
filename = sys.argv[2]
diff(filename)
elif (sys.argv[1] == "log"):
if (len(sys.argv) != 3):
help()
log(sys.argv[2])
elif (sys.argv[1] == "commit"):
print len(sys.argv)
if (len(sys.argv) < 4):
help()
comment = sys.argv[2]
if not comment.startswith("-m="):
help()
filenames = sys.argv[3:]
commit(comment, filenames)
else:
help()
def add(adddir):
"""
Creates an RCS directory under the specified directory. If there is
already an RCS directory, it prints an error message and returns.
"""
rcsdir = os.path.join(adddir, "RCS")
if (os.path.isdir(rcsdir)):
print "This directory is already under RCS control"
else:
os.mkdir(rcsdir)
def diff(filename):
"""
Reports on diffs between the actual and RCS version. If no filename is
supplied, it will list the files that are different from the RCS version.
If the filename is supplied, then the actual differences from RCS are
displayed for that file.
"""
if (filename == "*"):
visitFiles(os.getcwd(), RCSDIFF, 0)
else:
diff = os.popen(" ".join([RCSDIFF, filename]), 'r')
for result in diff.readlines():
print result
diff.close()
def log(filename):
"""
Prints the RCS log for the specified filename
"""
log = os.popen(" ".join([RLOG, filename]), 'r')
for result in log.readlines():
print result
log.close()
def commit(comment, filenames):
"""
Commits a list of supplied filenames, with the appropriate commit comment.
Our preferred mode of checking in is "ci -l", which locks the file again
after check-in, so its always writable.
"""
commitMessage = "-m=\"" + comment[3:] + "\""
command = [COMMIT, commitMessage]
for filename in filenames:
command.append(filename)
commit = os.popen(" ".join(command), 'r')
for result in commit.readlines():
print result
def visitFiles(root, operation, level):
"""
Generic recursive directory walk. Applies the operation to each of the
files encountered in the walk. This version is customized to ignore RCS
files and any file which ends with ",v" (RCS file extensions). This
version also ignores files on which the rcs operation is not required.
Since we use this for commit and diff, only the files which are different
from the RCS version.
"""
for filename in os.listdir(root):
fullpath = os.path.join(root, filename)
if (os.path.isfile(fullpath)):
oper = os.popen(" ".join([operation, fullpath, "2>/dev/null"]), 'r')
numlines = 0
for result in oper.readlines():
numlines = numlines + 1
if (numlines > 0):
print fullpath
oper.close()
else:
if filename == "RCS":
continue
visitFiles(fullpath, operation, level + 1)
def help():
"""
Simple usage help text that prints and exits.
"""
print "Usage: rcstool.py [add|diff|log|commit|help] [-m=comment] [filename]"
print " add [dirname] - puts a new directory under RCS control. If no"
print " dirname is specified, then puts the current directory"
print " under RCS control"
print " diff [filename] - Reports differences between all files under"
print " the current directory and the RCS version, or if a filename"
print " is specified, the actual differences between the file and"
print " its RCS version"
print " log filename - Prints the RCS log for the specified file"
print " commit -m=comment [filename]+ - Specifies a list of files that"
print " needs to be checked into RCS. The comment to put in all "
print " files is specified by preceding with -m=. Note that multi-"
print " word comments should be enclosed in quotes"
print " help - print this message"
sys.exit(-1)
if __name__ == "__main__":
main()
|
Update: I dont use this anymore. As pragmatic as it seemed when I started writing this, it turned out to be too inconvenient to learn another set of commands.
No comments:
Post a Comment
Comments are moderated to prevent spam.