The Anon CVS Bazaar - Part II

From EDM2
Jump to: navigation, search

Written by Henry Sobotka

RCS (Revision Control System) branches are most commonly used for not-ready-for-prime-time code such as a new design for a module or a port to another platform. This allows separate lines of development to proceed without destabilizing the main trunk until a branch is ready for a merger. Conversely, branches can also serve to hold a stable version of the code. For instance, if version 1.7 is stable, you can check it in as a branch (1.7.1) and start working on 1.8. This allows you to fix bugs along the branch and release upgrades that do not include your latest code, which may not yet be buildable. Moreover, you can incorporate those bug fixes into the main trunk at any time by means of a merger.

Merging with a branch does not destroy it. In fact, the rcsmerge command has no immediately effect on the repository file in the RCS subdirectory. It simply creates a combined draft in your working directory which by default overwrites the revision you checked out or, if you use the -p flag, gets piped into a separate file. You can then edited the merged file as needed, and specify either the branch or main trunk when checking it back in, depending on whether you want to update the branch or add branch changes to the trunk.

Moreover, the system makes it very easy to back out of changes, such as a revision with an intolerable bug. You can simply check out previous acceptably stable version and immediately check it back in. This puts it at the top of the tree, thereby restabilizizing the tip, while at the same time allowing you to access and continue debugging the buggy version. Here too you might consider creating a branch for the debug effort.


RCS also provides a set of keywords you can use to capture revision information in your source files. For instance, add the following simple header to a file

 * $Id$

and check it in. The next time you check it out, your header will look something like this:

 * $Id: hello.c 1.1 1999/05/02 16:29:18 sobotka Exp sobotka $

As you can see, the $Id$ keyword expands into a line containing the filename, version number, date, timestamp (as UTC or GMT, not local time), author, state and, if someone has a lock on the file as in this case, the locker's name. Another keyword, $Header$, does exactly the same thing except that the filename also includes the path.

Now check the file out, change $Id$ to $Log$, and after the next checkout, the expansion will resemble this:

 * $Log: hello.c $
 * Revision 1.2  1999/05/02 16:51:54  sobotka
 * Changed keyword Id to Log

The $Log$ keyword produces much the same information as $Id$ plus the log message you typed when checking in the file. These keywords are expanded wherever they appear, which includes log messages. Had the above message read "Changed $Id$ to $Log$", the next checkout would have produced a recursive tangle:

* Revision 1.2  1999/05/02 16:51:54  sobotka
* Changed $Id: hello.c 1.3 1999/05/02 17:08:16 sobotka Exp sobotka $ to $Log: hello.c $
* Changed $Id$ to Revision 1.3  1999/05/02 17:08:16  sobotka
* Changed $Id$ to Changed return to exit.
* Changed $Id$ to

Otherwise, with each successive revision, the $Log$ header grows into a history of the file which lets you see who did what and when to the source:

 * $Log: hello.c $
 * Revision 1.4  1999/05/02 17:34:30  geoffb
 * Modified message.
 * Revision 1.3  1999/05/02 17:25:32  mackenzie
 * Changed return to exit.
 * Revision 1.2  1999/05/02 17:24:19  sobotka
 * Changed keyword Id to Log.

Although in these examples we've been using C-style comments, the same applies to C++ double slashes ("//") or any other programming language's comment specifier. RCS will simply replicate whatever character(s) precede the keyword. For example:

REM $Log$

in hello.cmd becomes:

REM $Log: hello.cmd $
REM Revision 1.1  1999/05/02 18:05:58  sobotka
REM Commandline version

You can also use RCS keywords to embed information about the source in your object code. For instance, put

static char version[] = "$Id$";

at the top of a file and, when you check it out, RCS expands the string just as it does in a comment header:

static char version[] = "$Id: hello1.c 1.5 1999/05/02 17:48:58 sobotka Exp sobotka $";

Other keywords you can use to customize revision data are $Header$, which is exactly the same as $Id$ except that the filename includes the path; $RCSFile$ (filename without path) or $Source$ (filename with full path), $Revision$, $Date$, $Author$, $State$, and $Locker$, representing the six components of the $Id$ or $Header$ string; and lastly $Name$, which expands to the symbolic name given to a particular branch or revision.

Pulling Logs

You can directly access revision information with RCS's rlog command. Listing bellow shows an example of the output of "rlog hello.c". Most of the data it contains should by now be familiar. The first new item is keyword substitution: kv, which refers to the method of keyword substitution as specified by using the -k flag with the co command. Here kv refers to the default keyword:value string. Further details about the -k flag options, which you're unlikely to use except in special circumstances, can be found in the manual that comes with RCS. The second new item, lines: +n -n, simply indicates the number of lines added to and deleted from the previous version.

RCS file: RCS/hello.c
Working file: hello.c
head: 1.5
locks: strict
	sobotka: 1.5
access list:
symbolic names:
keyword substitution: kv
total revisions: 5;	selected revisions: 5
Example hello.c
revision 1.5	locked by: sobotka;
date: 1999/05/02 17:48:58;  author: sobotka;  state: Exp;  lines: +5 -0
Added version static char string.
revision 1.4
date: 1999/05/02 17:34:30;  author: geoffb;  state: Exp;  lines: +4 -1
Modified message.
revision 1.3
date: 1999/05/02 17:25:32;  author: mackenzie;  state: Exp;  lines: +6 -2
Changed return to exit.
revision 1.2
date: 1999/05/02 17:24:19;  author: sobotka;  state: Exp;  lines: +1 -1
Changed keyword Id to Log.
revision 1.1
date: 1999/05/02 17:23:19;  author: sobotka;  state: Exp;
Original version.

Several flags are available to control rlog output.

rlog -r1.3 hello.c

for example, pulls the log data for version 1.3. Or, if you wanted to see all the log entries for a specific author, e.g. Smith, you would use -wsmith.

Another useful flag is -d for specifying one or more semicolon-separated date ranges for the output. For instance,

rlog -d"1998/12/1 < 1998/12/31; March 15 >= March 20; > May 1" hello.c

displays the log entries for revisions between December 1st and 31st (exclusive) 1998, March 15th and 20th (inclusive) of the current year, and after May 1st. Because the date information contains spaces, it must be enclosed in a double quotes. The = sign specifies that the output should include the limit date; the default is exclusive. Times can also be used with the -d flag, e.g.

rlog -d">17:21:00" hello.c

pulls entries made after 17:21 (GMT) on the current date. Needless to say, you can combine dates with times to further narrow down the output. While this may seem an exotic feature in the context of our trivial example, when you have numerous developers racing to meet a production deadline, and the application that ran smoothly last night has been crashing since 10 o'clock this morning, the -d flag can help you narrow down the search for the programmer whose checkin triggered the bug.

Among the other most commonly used rlog flags, -b[name] extracts all entries for the specified branch, -s[name] for a particular state (e.g. experimental, beta, production -- whatever names you chose), and -zLT tells RCS to convert output and input dates to your local timezone.

Tweaking RCS Files

The last of the basic original RCS commands, rcs, enables you to control access to the repository as well as revise the RCS file when necessary. For instance,

rcs -asmith,jones,leary hello.c

restricts file access to Smith, Jones and Leary by adding


to the top of RCS/hello.c. Should you later decide to replace Jones with Brown, you need simply go:

rcs -abrown -ejones hello.c

and the list will be revised to:


You can also change the log message for any revision with rcs -m[rev]:[msg] where rev is the version number and msg the new message. For example:

rcs -m1.4.1.2:"Just playing around" hello.c

changes the descriptive entry for version to "Just playing around". Similarly, the -t flag enables you to change the contents of the description you were prompted for the first time you checked in the file. Here you can use -t-[quoted string] to enter the new description directly, -t[filename] to read it from a file, or -t alone to produce a prompt for entering the text.

Another rcs option lets you set the state of a particular revision (-s[state:rev]). If rev is not specified, the change applies to the latest version of the main trunk or default branch or, if rev is a branch number, to the tip of the branch. Thus

rcs -sbeta:FRENCH hello.c

sets the state of the FRENCH branch of hello.c to "beta". Branches can also be named with -n[name:rev], or -N[name:rev] to override previous names. Branch names are case-sensitive, and renaming a branch does not delete its previous names; rather, the name-revision associations accrue in the symbols section of the repository file:


Here revision 1.4.1 has been given four names, any one of which can still be used to work with the branch. You can also use -b[name] to make a particular branch the default. For example,

rcs -bPierre hello.c

sets the default to revision 1.4.1, so that a simple

co hello.c

now pulls version, the tip of our multinamed FRENCH branch, instead of 2.4, the head of the main trunk.

Moreover, revisions can be deleted from the repository with the -o[range] flag, where range may be either a single number, or colon-separated range. For instance,

rcs -o1.2:2.2 hello.c

would normally delete all versions between 1.2 and 2.2. In our example, however, we have several branches in that range, so the command fails with:

rcs: RCS/hello.c: can't remove branch point 2.1

If you then delete 2.1, the command again fails because of the branch at 1.4; finally, after killing the last intervening branch at 1.3, the revision history of hello.c will have shrunk to:

date;	author sobotka;	state Exp;
next	2.3;

date;	author sobotka;	state Exp;
next	1.1;

date;	author sobotka;	state Exp;
next	;
date;	author sobotka;	state Exp;
next	;

Notice that the original numbering remains, while each version's next field has been appropriately reset. Clearly, this flag should be used with caution as excessive deletion can defeat the purpose of revision control. On the other, in the case of a file with a long history, it may well be preferable to prune the record. Although RCS has no mechanism for restoring deleted content, you need only make a copy of RCS/hello.c to preserve the original details, before using the -o flag to create a shorter version for working purposes.

You can even open a new repository without checking in any source code by commanding rcs -i foo.cpp. This creates a skeleton RCS/foo.cpp which looks like this:

head	;
locks; strict;
comment @// @;

@C++ Goober

The only data in the template is the description of the file you entered at the prompt. Notice here that, based on the filename, RCS has set the comment' specifier to double slashes. Had you initialized or checked in, >comment would be set to @# @;. Try foo.cmd, however, and you'll get comment @:: @;, which you could then change with rcs's -c flag:

rcs -c"REM " foo.cmd

The double quotes here simply ensure a blank space between "REM" and the content of the comment. This allows you to adjust for file extensions that RCS may not recognize, or gets wrong.

We've now covered the basic original RCS commands along with their most commonly used options. Although I originally intended to cover locks in Part II, this installment is already longer than expected so we'll save that topic and the emergence of CVS for next month.