First published on November 8, 2004
Last updated on March 16, 2005
RCS is a Revision Control System. It manages changes in text documents, automating the storage, retrieval, logging, and logging of file revisions. It’s a great tool for any sort of file that passes for source code: HTML files, published documents, programming code, etc.
The RCS system consists of several binary applications:
rlog. The Fedora Core, Red
Hat, Gentoo, and Debian distributions all include RCS in a package
called (oddly enough) “rcs.”
The typical recipe for using RCS is quite short.
# get to where you're going cd /path/to/workingdirectory # check out and lock your file co -l workfile # use editor of choice to work on file $EDITOR workfile # see what's changed in your latest version rcsdiff workfile # check in the new version of your file ci -u workfile
The rest of this article will explain and expand upon that simple procedure.
An example session
I think the best way to show you how to use RCS is to run through a relatively complete example session. This example won’t by any means use all of RCS’s capabilities, but it’ll provide a decent base of knowledge and practice.
Create a work directory
For the purposes of this example, I’m going to call the directory in
which we’ll do our work
~/src. There’s nothing
important about that directory name; feel free to adjust it to your
So, first off, we’ll create it and make it our working directory.
mkdir $HOME/src cd $HOME/src
Next, we’ll create a directory with a name that is important and that shouldn’t be changed.
Having a directory named
RCS in your work directory isn’t mandatory,
but if it’s there RCS will use it to store is working files. I find it
much cleaner to have RCS do its housekeeping in a subdirectory rather
than in my work directory.
A sample file
Now we need a sample file. Let’s create a short shell script that acts like the world’s favorite example file.
#!/bin/sh # # foo.sh -- a silly example that acts in an oddly familiar way # # $Id$ # $Source$ # echo "Hello, world!"
You’ll notice the three non-standard lines, one with the RCS keyword
$Id$, the other with
$Source$. The co(1) man page has a KEYWORD
SUBSTITUTION section that lists all the keywords you can use, but
these will give you a flavor for what’s available.
A listing of our work directory now reveals one file and one subdirectory:
$ /bin/ls -og total 8 drwxr-xr-x 2 4096 Nov 5 14:09 RCS -rw-r--r-- 1 118 Nov 5 14:14 foo.sh
We’ll check the file into RCS using the
ci application. The default
check-in process makes the working file “disappear.” There’s the
revision-control version in the
RCS directory, but otherwise the file
is invisible. I usually like to be able to see my working files (since I
rarely maintain multiple working versions of the same file), so I almost
always check files in using the
ci -u foo.sh
RCS will spring to life and ask you to provide an overall description of the file you’re checking in. The text of the description can span multiple lines; you signal the end of the text with line that consists of nothing but a period.
$ ci -u foo.sh RCS/foo.sh,v <-- foo.sh enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> a silly, yet familiar, example >> . initial revision: 1.1 done
There are three file changes now worth observing.
The working file is now marked read-only by the filesystem.
$ /bin/ls -og total 8 drwxr-xr-x 2 4096 Nov 7 20:19 RCS -r--r--r-- 1 200 Nov 7 20:16 foo.sh
RCSdirectory is a file named
foo.sh,v. It is the source file that will (eventually, at least) keep track of all the changes made to your working file.
The RCS keyword tags in your working file now contain revision information.
$ cat foo.sh #!/bin/sh # # foo.sh -- a silly example that acts in an oddly familiar way # # $Id: foo.sh,v 1.1 2004/11/08 04:19:33 heinlein Exp $ # $Source: /home/heinlein/src/RCS/foo.sh,v $ # echo "Hello, world!"
$Id$tag now contains the number of the revision (1.1), the date and time that revision was checked in (expressed in UTC unless you say otherwise), and the username of the person who checked in that version.
$Source$tag contains the full path to the source file.
Check out and edit
After some time goes by, we become dissatisfied with our little script.
We’re not always ready to send the world our hearty greeting. It’s time
to check the file out and work on it a bit. You’ll almost always want to
lock the files you check out, using the
co -l foo.sh
When you ckeck out a file, RCS will report the version on the file you’re editing.
$ co -l foo.sh RCS/foo.sh,v --> foo.sh revision 1.1 (locked) done
When the file is checked out in this manner, the filesystem will once again report that it’s writable.
$ /bin/ls -og total 8 drwxr-xr-x 2 4096 Nov 7 20:20 RCS -rw-r--r-- 1 209 Nov 7 20:20 foo.sh
We decide we need to rule out happy greetings on our least favorite day
of the week, so we adjust our script accordingly. At the end of our
editing session, we use the
rcsdiff utility to make sure we introduced
no unwanted changes into our working file.
$ rcsdiff foo.sh =================================================================== RCS file: RCS/foo.sh,v retrieving revision 1.1 diff -r1.1 foo.sh 8c8,12 < echo "Hello, world!" --- > if [ $(date +%A) = 'Monday' ]; then > echo "Is it Friday yet?" > else > echo "Hello, world!" > fi
Checking in the changes
Satisfied with our latest editing efforts, we check the file back in,
once again using the
-u option so ensure that our script is still
visible after check-in.
ci -u foo.sh
During the check-in process, RCS will prompt us for a log message that describes the current changes.
$ ci -u foo.sh RCS/foo.sh,v <-- foo.sh new revision: 1.2; previous revision: 1.1 enter log message, terminated with single '.' or end of file: >> "Hello, world!" on Monday? Feh. >> . done
That’s it. Just rinse, lather, and repeat as you continue to improve and
enhance your work file. If you ever want to recall the file’s revision
history, just use the
Incrementing version numbers
By default, RCS will only increment the minor numbers in your version
number: 1.1, 1.2, 1.3, 1.4, … At some point, however, you’ll want to
release version two. Just tell
ci which version number you want to
start using the
$ ci -r2 -u foo.sh RCS/foo.sh,v <-- foo.sh new revision: 2.1; previous revision: 1.2 enter log message, terminated with single '.' or end of file: >> rev 2 objectives met. customer happy. >> . done
You’ll note that if you don’t supply a minor number for your release,
RCS will assume that it’s
.1. You can, if you’re so inclined, start
.0 by specifying it, e.g.,
ci -r2.0 -u foo.sh.
I often find myself wanting to check out an entire set of files to make a similar change in all of them, like altering a directory path or URL they all reference.
Checking out a bunch of files is easy to automate since the
requires no user intervention.
Likewise, making a common change in a set of files is easy using
awk, or dozens of other scripting tools.
The more obscure part is automating the check-in process, since
ci utility requires a log entry for each file.
The answer is to use the
-m option or to pipe the log message to RCS.
Either method will do the trick.
# using -m; no space allowed between option and the message ci -u -m'altered name of main image file' *.html # using stdin echo 'altered name of main image file' | ci -u *.html
RCS stores datestamps in UTC (aka GMT) time—and by default displays them that way—which can be confusing at first. Here on the American west coast, we’re seven or eight hours behind UTC, depending on the time of year. If I check in a file today at 5:30 p.m., the RCS date stamp will show the file checked in tomorrow at 12:30 a.m. (during daylight savings time) or 1:30 a.m. (if we’re on standard time).
RCS provides the
-z option to override the time zone displayed in the
date stamp. The ci(1) man page has the full details, but essentially you
can specify either a time offset or the string
LT (indicating local
# check in a file using the local timezone ci -u -zLT workfile # the same, using an explicit offset ci -u -z-08:00 workfile
Also by default, RCS uses the working file’s last modification time as
the stamped date and time. That can be overridden using the
See the co(1) man page for several more examples and a list of
acceptable date formats.
# using a full ISO 8601 UTC timestamp ci -u -d'2004-11-17 06:30:00+00' workfile # using date(1) format ci -u -d'Sun Nov 14 06:30:23 PST 2004' workfile # the same, using GNU date and command substitution ci -u -d"$(date -d '3 days ago')" workfile
RCS in emacs
I divide my editing time between
emacs; I’m pretty neutral
in the Text Editor Wars. One handy feature of emacs, however, is its
integrated RCS support. It’s possible to check files in and out of RCS
from within the editor itself.
I use this feature constantly, but I’ll admit that I don’t use the the full breadth of emacs’ version-control features. Instead, I typically just check files out and back in again.
If you use emacs to open a file currently under RCS control, you should
see “RCS” and the file’s version number in the mode line near the bottom
of your buffer, e.g.,
After that, the recipe is pretty easy.
C-x v v (
vc-next-action) to check out the file. By default, this
implies locking the file as you’d do with
After you’ve edited the file, you check in your changes using the same
C-x v v. You get a new buffer in which to add a log
comment. Once you’re finished with the comment, type
C-c C-c to commit
the changes. By default, this implies checking in the file without a
It’s worth noting that emacs won’t save the customary backup file
foo.c~) when working in RCS mode. If something goes haywire,
you’ll have to revert to the last good checked-in version of your
Migrating from RCS to CVS
I recently had the occasion to move a large number of files from RCS to CVS. Since both systems use the same underlying libraries—indeed, CVS is built on top of RCS—they can use the same source files.
The biggest migration issues are getting rid of anything in your directory tree that’s not a source file and making sure that all your source files are currently checked in. The following script will handle the migration, though it assumes shell-friendly directory and filenames (it’ll fail if directory names include spaces).
#!/bin/sh # # warning: this script does precious little error checking. bwahahaa! # season these variables to taste SRCDIR="/path/to/rcs/root" DESTDIR="/path/to/destdir" cd "$SRCDIR" # identify any unlocked files UNLOCKED=$(grep '^locks' $(find . -type f -name '*,v') | grep -v 'locks;') if test -n "$UNLOCKED"; then printf \ "%d files are unlocked and should be checked in:\n" \ $(echo "$UNLOCKED" | wc -l) echo "$UNLOCKED" | sed 's!:locks$!!' exit 1 else printf "All existing source files are checked in. Excellent!\n\n" fi # copy all the ,v source files to destination; identify RCS # directories, move source files out of them up a directory, and # delete the RCS directories. test -d "$DESTDIR" || mkdir -p "$DESTDIR" printf "Copying source files to %s\n" "$DESTDIR" tar -cf - $(find . -type f -name '*,v') | (cd "$DESTDIR" && tar -xvf -) cd "$DESTDIR" printf "Removing any RCS directories\n" for dir in $(find . -type d -name RCS); do (cd $dir && mv *,v ..) rmdir -v $dir done printf "\n%s is now CVS-ready\n" "$DESTDIR"
If the script is successful, your destination directory can now be copied as-is into your CVSROOT.