Published on (
 See this if you're having trouble printing code examples

Big Scary Daemons

BSD Tricks: Introductory Revision Control


Revision control is simply the process of tracking all changes made over time. In the BSD world, this generally means changes to source code or configuration files. Revision control allows a developer to see how a piece of code looked on a specific date, or an administrator to see how a daemon was configured before things stopped working. Even a lowly writer can use revision control to see how a manuscript has changed over time. If you're not using revision control, you're making your work more difficult than it needs to be.

You can think of revision control systems, like BSD's RCS, as putting a file in a "library." To edit a file, you need to check it out. While you have a file checked out, nobody else can save it. Any legitimate user can view, use, compile, or access the file at any time; they simply can't alter it and save their changes to the same file while you have it checked out. When you're done, you check it back in, releasing the file for others to edit.

While you'll encounter many revision control systems, from Unix's SCCS to Microsoft's Visual Source Safe, we'll discuss RCS. All open source BSDs (as well as most Unix systems) include RCS, and the concepts used by other programs are all roughly comparable to RCS. RCS uses three basic commands: ci (check-in), co (check-out), and rcs.

You start the revision control process by checking in a file with the ci command, much like giving a book to the library. For example, my next article is in a file called laptop. To put this under RCS I would do:

ci laptop 
laptop,v <-- laptop 
enter description, terminated with
single '.' or end of file: 
NOTE: This is NOT the log message!
>>FreeBSD pccardd article 
>> .  
initial revision: 1.1 done

When you first check in a file, ci asks you for a description of the file. Any RCS user can view the description you give. When you've finished the description, enter a single period on a line by itself to exit ci.

If you do a ls immediately after checking the file in, you'll notice that it's vanished. Instead, there's a file with the same name, with a trailing ,v. This is an RCS file, where the file and its changes are stored.

For some uses this is fine, but for source code or web pages, you can't have this. To check in a file but leave a copy in the working directory, use ci -u.

If you have lots of stuff in RCS, the ,v files can quickly clutter a directory. You can hide them by creating a directory RCS. The ci program will then put the ,v files in that directory, keeping the working directory cleaner.

If a file is checked in and you want to put a clean copy in the working directory without editing it, you can use the co command.

co laptop
laptop,v  -->  laptop
revision 1.1

Looking closely at the directory, you'll see:

ls -loaF
total 62
drwxr-xr-x 2 mwlucas mwlucas - 512 Oct 4 18:07 ./
drwxr-xr-x 7 mwlucas mwlucas - 512 Sep 8 08:58 ../
-r--r--r-- 1 mwlucas mwlucas - 12663 Oct 4 18:06 laptop
-r--r--r-- 1 mwlucas mwlucas - 12867 Oct 4 17:56 laptop,v

I own this file, but the permissions have been set to 444. I no longer have permission to edit my own files!

This is because the file isn't checked out to me in particular. If I want to edit the file, I need to check out the file and lock it for my personal use.

co -l laptop
laptop,v  -->  laptop
revision 1.1 (locked)

Notice the second line of output, where "locked" is specified. This file is checked out and locked by me. Nobody else can save this file until I unlock it. A ls shows that the permissions on the laptop file are now set back to 644, allowing me to save.

(A warning to vi users: If you or your group owns the file, a w! will force a permission change and allow you to write to the file even without checking it out. Everything will look fine, but the next person who checks out the file will overwrite your changes! Be careful using w! anything; if vi warns you that you don't have permission to save, there's probably a good reason.)

Anyone else who tries to check out this file will get a warning that the file is in use, including the username.

When I'm done editing, I check in the file. Since I want other people to be able to edit the file, I use ci -u to release my lock.

ci -u laptop
laptop,v <-- laptop
new revision: 1.2; previous revision: 1.1
enter log message, terminated with single '.' or end of file:
>> added changes submitted by freebsd-mobile readers
>> .

When you check something in, you are asked for a log message. Enter a brief description of your changes. (These are comparable to the CVS log messages seen on the various BSD's commit mailing lists.) These messages allow other developers to know what changes you made without reading them all -- or, alternately, to see what you were *trying* to do when your change broke something and they have to get out the debugger. Your own RCS logs can be useful for you, months later, when you stare at a chunk of work and wonder just what was going on inside your head when you made that change.

Each time you check in a file, it's assigned a revision number (in the above example, 1.2). You can use the revision number to check out any previous version of the file. If you're, say, trying to track a bug that's just appeared in your program, you can check out earlier versions of your code and see if they also exhibit the bug. You can do this with co using the -r flag. For example, to check out an earlier version of /etc/rc.conf you would do:

co -r1.1 rc.conf
RCS/rc.conf,v  -->  rc.conf
revision 1.1

Always check files in when you've finished editing them, or if you're leaving for some reason. If another user needs to edit a file when you're gone, they'll have to break your lock. Any changes you make since locking the file will be lost.

Use rcs -u to break a lock. RCS will ask you for a message about why you're breaking the lock, which will be mailed to the lock holder. Be careful using this in shared files; if someone is really editing a file when you forcefully break the lock, they'll be justifably upset. If they've gone home for the day, that's another thing.

RCS provides a variety of other functions. My favorites are rlog, rcsdiff, and the ident strings. The rlog command shows you the log messages for the file, for example:

rlog /etc/rc.conf

RCS file: /etc/RCS/rc.conf,v
Working file: /etc/rc.conf
head: 1.4
locks: strict
access list:
symbolic names:
keyword substitution: kv
total revisions: 4; selected revisions: 4
revision 1.4
date: 2000/09/08 17:45:29; author: mwlucas; state: Exp; lines: +2 -0
minor updates
revision 1.3
date: 2000/09/07 19:05:30; author: mwlucas; state: Exp; lines: +1 -1
*** empty log message ***
revision 1.2
date: 2000/09/05 16:09:47; author: mwlucas; state: Exp; lines: +1 -1
enable sendmail
revision 1.1
date: 2000/09/02 14:53:43; author: mwlucas; state: Exp;
Initial revision

All sorts of useful information appears here; the date each check-in was made, the author of the change, the entry's state (which we don't worry about here, see ci(1)), and the number of lines changed. Here we see I didn't bother leaving a log message in revision 1.3. To see what changed, use rcsdiff. The rcsdiff command takes three arguments: two revision numbers and a file name.

rcsdiff -r1.2 -r1.3 /etc/rc.conf
RCS file: /etc/RCS/rc.conf,v
retrieving revision 1.2
retrieving revision 1.3
diff -r1.2 -r1.3
< inetd_enable="NO"
> inetd_enable="YES"

Apparently I turned inetd on. This would have been useful to know in a production system, especially one administered by several people.

You can use rcsdiff between arbitrary revision numbers, allowing you to view the total diffs made between any two entries.

Lastly, ident strings can be embedded in files under RCS protection. They're used to put human-readable RCS information in the file. Each string is in the form $string$. For example, the RCS ident string $Id$ puts information about the last change in the file. When I put #$Id$ in /etc/rc.conf, it appears as:

#$Id: rc.conf,v 1.5 2000/10/05 18:29:49 mwlucas Exp mwlucas $

The pound sign is included so that /etc/rc won't try to run the line. You can use whatever comment is appropriate for the type of file.

For a complete list of ident strings, see ident(1).

RCS is a terribly powerful tool. For more details, check ci(1), co(1), and rcs(1). This should get you started, however. Careful revision control can save you hours of later debugging and troubleshooting.

Michael W. Lucas

Read more Big Scary Daemons columns.

Discuss this article in the Operating Systems Forum.

Return to the BSD DevCenter.


Copyright © 2009 O'Reilly Media, Inc.