Lccs User Guide

Beta Version 125

  1. What is it?
  2. Conventions
  3. Installation
  4. A Quick Demo
  5. SETUP: Creating a New Archive
  6. CI: Checking Everything In
  7. CO: Checking Out a Version
  8. MERGE: Merging a Version into Another
  9. VERSION: Tagging an existing version
  10. LOCK & UNLOCK: Enabling Ad Hoc Operations
  11. Binary File Handling
  12. Command Line Details
  13. Random Details
  14. Vitally Important Boilerplate

What is it?

Lccs, the Lonesome Cowboy Configuration System, is a simple non-graphical front end to RCS, intended to provide a minimal level of configuration management for a very small group of experienced and cooperative programmers who are administering their (his, her) own stuff.

Like, you know how to program. You don't need no stinkin' Maturity Model to tell you the value of archiving your source as you go, and being able to get back a specific previous version. You have more than a couple of source files to manage, maybe in more than one directory, but you can put them all in one tree. You know something of RCS, or are willing to get it and learn about it.

And you see that checking in all the source files at once, in a consistent version, can be messy when they're scattered across multiple directories. And you may know how to label revisions in RCS, but keeping track of the labels gets messy too, turning into something too much like a pencil-and-paper task. And as for making a branch: how likely are you to get all the files that you need, in a consistent form, checking them in at intervals on the correct branch, not to mention getting the eventual merge right? Of course you can do wonders with Make files, but then you could do wonders with Turing machines, too, if you had to.

If you recognize yourself here, you may want to use Lccs. Then again, why aren't you using CVS? I had my reasons for not wanting to, but I don't know what yours are. I do think that if you're not already a CVS user, you will find a quick installation and demo of Lccs so much easier than beginning to learn CVS administration that it will be worth your while to look into it.

Lccs is written in supposedly pure, system-independent Java. It embodies a lot of assumptions about how RCS is to be used; I'll be glad to discuss these with anyone who cares, and maybe even put in some more flexibility.


In general, lines that you type in will look like this:
java foo.Bar baz

Output messages and screen displays will look similar:

You goofed. Try again.

Where input lines are shown, <text in angle brackets> shows the type of input field that's being entered. [Text in square brackets] is optional. Everything else is literal text that you type.

In most of the text it will be assumed that you've created a command file or shell script or alias or something for the name lccs, so that you can type
lccs some operation
rather than
java lccs.Lccs some operation
The quick demo section will be an exception, since at that stage you haven't decided to make use of the package at all.

The source that you're working on for a project is in a directory subtree called the working tree. The parallel directory subtree that holds the RCS archive files is the archive tree. At the bases of these subtrees are the working root and the archive root, which are not to be confused with an actual root directory.

A checked-in state of the system, with a name attached to it, will be called a version, as distinct from an RCS revision of a particular file. A branch is a branch for all that, however. The general name for either a branch or a version is a label.

Every RCS archive has a main line with revision numbers like 1.1, 1.93, 2.6, whatever. I refer to this as the trunk. At present all revision numbers start with 1, but that will easily be changed when there's any reason to.

The fact that your project has branches, and the branches have trees (working trees, one per branch) is something I just can't be held responsible for.


System requirements

You must have a system that can run Java 1.0 programs. Version 1.1 is supposed to be compatible enough to do it.

Unpacking the distribution file

Put the file in a directory that's on your classpath. Unzip it. (It was made with InfoZip; here's where to get the unzip program if you don't have one that will work. When using an unzip program other than InfoZip, remember to specify that the directory structure is to be preserved.) This will give you four files:
This documentation, in which you will find the rest of the installation instructions.
The Lccs package, in an uncompressed zip file, including the pat package.
The copyright statement for the pat package.
A small demo of how Lccs works.

The Lccs package includes a fairly old version of Steven Brandt's regular expression package, an excellent piece of shareware. If you do any text processing at all in Java, you should look into getting the latest version. Some day even Lccs will be upgraded to use it properly.

One installation

Unzip the Lccs zip file. Now you have directories lccs, ddlib, and pat. The package is ready to run.

I know it's a violation of Javaquette to take up that much of your namespace instead of making package names that spell my domain name backwards. When I make this a commercial package and get a domain name, I'll fix that; in the interim, let no one hold his breath.

(Shucks, by this time I've got a domain name. But I'm shipping the damn package, not going back to tinker with it.)

An alternative installation

You could also leave the file zipped, and put it on your classpath, as in
(All examples need to be corrected for the syntax of your own system.)

Yet another installation

A third possibility, I suppose, is to use the -classpath option when executing lccs, as in
java -classpath;%classpath% lccs.Lccs <etc.>
As far as I can tell, -classpath doesn't work at all on my system, but it may work on yours, and it's hardly too awkward, because you'll be making a command file (shell script, whatever) to invoke the program anyway.

A Quick Demo

For a quick and crude demonstration of some Lccs features, unpack the file which came with the package. It doesn't matter where you do this. You will get a directory structure

Now take a look at files.lcc. It has two Directory lines, one for the current directory (.) and one for sub1. There is no entry for the stuff directory, which contains nothing that we want Lccs to track. Each directory section is ended by a line containing nothing but a period.

Now go to the archive directory. Enter
java lccs.Lccs setup ..\source
java lccs.Lccs setup ../source
or whatever syntax your favorite shell accepts.

Lccs will first experiment with your file system to determine whether naming is case sensitive and whether names longer than the DOS 8.3 convention are allowed. It reports the results as a sanity check. Next it will solicit a description for one of the source files:

Enter description for ..\source\sub1\foo, terminated by a single '.'
As the saying goes, this is NOT the log message!
Enter a description, just as you would when initially checking a file in to RCS. Repeat this when the next file comes up. Then you get
Enter global wildcard match patterns, one to a line, terminated by .
Just enter dot and carriage return; we want an empty list.

Lccs will then ask

Track file ..\source\.\foo? (Y/n)
As this filename matches the expression in the watch statement, Lccs is giving you the chance to track it. Enter
Now you will be prompted for the description of the file.

When Lccs has cleaned up its files and archived everything, it exits. The archive directory now mirrors all the directories and files that were declared relevant.

Now go to the source directory and make some change in file goo. Go to the sub1 subdirectory and make a change in file foo. While still in the subdirectory, enter
java lccs.Lccs ci -mWow -vtag00
You will be prompted

Enter description of version tag00, terminated by a single '.'
Here you enter a description, just as you would for a new file, but you are describing the current version of the program, which you are giving the label tag00. For the sake of a later step, do enter at least one line of actual text.

Lccs will then check in everything. The files that were changed will have the checkin comment "Wow" in their RCS archives. All the files will have the name tag00 attached to the now-current version. You can confirm the existence of the new version:
java lccs.Lccs ci -vn:.
The odd construct after the -v option is a request for a list of every version name that matches the regular expression /./ -- that is, every version name. You will get something like

Enter name for new version
1. tag00  V  Thu Sep 18 17:05:53 PDT 1997
  one line of actual text
2. *Trunk*  B  Thu Sep 18 16:53:08 PDT 1997
3. *Junk*  B  Thu Sep 18 16:53:09 PDT 1997

Enter the name or a *command: *Restart list, *Cancel:

Or maybe you won't. If the screen is a mess, then you don't have ANSI terminal emulation active, and this feature will be useless until you install it.

Anyway, you can see your tag, with a V to show that it refers to a version, and with its first line of descriptive text showing. Pay no attention to those other lines. When you've finished admiring the display, enter
to cancel the whole thing.

Ready to create a branch? All right, then go to directory branch. Enter
java lccs.Lccs co -bfork00 -a..\archive d:.
With the d:. you have requested a list of every label with a description in which at least one line matches the regular expression /./, which gives you a shorter listing

Select source version or branch
1. tag00  V  Thu Sep 18 17:05:53 PDT 1997
  one line of actual text

Enter number of desired name, or
Enter the name or a *command: *Restart list, *Cancel:

because the labels with no description are omitted. The two-line prompt at the bottom means that you can select a version by number as well as by name, so give it a 1 to select the one and only item. Lccs will respond
Enter description of branch fork00, terminated by a single '.'
Now enter the description as usual. Lccs will check out a complete copy of the system into the branch directory, as of the time you checked in the version tag00. You can now make source changes in either of the directory trees and check them in on the proper branch with an Lccs ci operation.

This ends the demo.

SETUP: Creating a New Archive

Before creating an archive for a project, you need to take care of three things:

Put the files for the project in a single directory tree.

The working directory tree can be as simple or as complicated as you like, from a single directory to a huge structure in which not all of the directories will contain any files that need to be archived. It can be anywhere in your file system, and its root directory may or may not contain any archivable files; however, a working tree must not be nested within another working tree.

Make a list of the directories in the tree and the files that are to be archived.

The initial list of directories and files goes in a file called files.lcc in the root of the working tree. Lccs will maintain this file once the archive is set up; it will be the only essential part of the archiving system outside the archive tree.

Details of this file are given later.

Decide on a place to put the archive

The archive tree can go anywhere, though putting it inside the working tree is a dubious practice. Be sure that the archive directory is completely empty (no files or subdirectories in its root directory).

Setting up a Toy Archive

Congratulations on not choosing to entrust your valuable data to some untried system. Set up a little directory somewhere with at least one file in it, and create a file named files.lcc containing the two lines
Directory .
The second line has a period and nothing else, just like the terminating lines in RCS input. This doesn't specify any files, but we'll take care of that later. If you're just doing a trial run, you can now skip the detailed description of files.lcc

Setting up a Live Archive

For live data you need a realistic listing in files.lcc. You can save a lot of work in preparing this list because The file consists of one or more directory sections, each of which can contain any number of track, ignore, and watch statements. Each section begins with directory <name> and ends with a line that has only a single period character (no whitespace or anything). Each track or ignore statement lists the name (not the full path) of one file. Each match statement lists one wildcard pattern (not a regular expression).

In this example we assume that all the files you want to track are ASCII stream files, consisting of a sequence of lines separated by the system's end-of-line convention. For binary files you should use the trackb and watchb statements. And see the section on binary files.

Here is an example:
directory csource
ignore foo.c
watch *.c
watch *.h
directory include
watch *.h
directory simple
directory doc
watch *.doc
directory include\foo\random
track goofus.h
Naming conventions for files and directories are whatever applies in your system, with one exception: No blanks are permitted within names. The Lccs commands themselves are case-independent. Blank lines and comment lines (starting with the # character) are ignored and will be deleted from the file when Lccs cleans up before terminating. There can be multiple sections for a single directory. There can be multiple entries for a single file; the last one rules. Directory paths must be relative, not absolute.

In the example, the directory simple has no names listed at all. Apparently anything interesting in that directory will be picked up by global wildcard patterns.

Note that the directory include\foo is not named anywhere, though it has a subdirectory random that is named. Apparently it contains nothing that we'll ever want to archive. Global wildcards will not match anything in include\foo.

Making the archive

Now, working in the archive root directory, establish the archive:
java lccs.Lccs setup <working root directory>
lccs setup <working root directory>
assuming that you've made a suitable command file named lccs. We'll assume this from now on.

Before making the archive, Lccs needs information on your file system. Therefore, it executes some simple experiments at this point and then displays a couple of messages with its answers to these questions:

Please examine these messages when you first run Lccs, and report any errors: that is, any cases in which Lccs misinterprets the working of your system. Any test for buggy Java libraries will, of course, return a fatal error rather than try to go on.

If you have any track statements in your files.lcc, the program will now set up archive files accordingly. In doing so, it will solicit a description for each file, just as RCS does. If there are no track statements, this won't happen now.

The program will then ask,
Enter global wildcard match patterns, one to a line, terminated by .

If you're running a toy example, you'll certainly want to enter a pattern that will match something in your directory. If you're running live data, it's up to you. Anyway, enter as many patterns as you like, one per line. Finally, enter a line with a single period.

For each file that matches a wildcard pattern, you will be prompted,
Track file <some filename>? (Y/n)

Each file that you decide to track will have an archive file created, and you will be prompted for the description.

That's it. You can now try checkin and checkout.

CI: Checking Everything In

The ci command checks in all the files that appear in track statements anywhere in your current list. It can be executed from any directory within a working tree; files will be checked in on the corresponding branch.

It also examines each directory to find new files that match a wildcard pattern for that directory or for the system as a whole. For each matching file it offers the choice of tracking or ignoring it in the future.

The system creates new archive files as necessary. When it does so, it asks for a multi-line description of the file, just as RCS does.

There are two options that ci accepts:

-m"checkin comment"
Each file that is actually checked in, having been modified since the previous checkin, will have this simple one-line comment attached to the new version. Depending on your shell and how your Java implementation works with it, you may have to use the quotation marks differently, like "-mcheckin comment". Experiment if necessary.

This is not as good as the raw RCS facility. Maybe it can be fixed; maybe the oddities of Java implementations will prevent a fix.

The latest version of every file in the system will be tagged with this label, regardless of whether the file has been recently modified. That is, this label marks a version of the entire system, and you can check out the same version later.

Lccs will solicit a multi-line description of this level of your system. As usual, you terminate this input with a line containing nothing but a period character. The name, time, and description will be stored by Lccs in its own file config.lcc.

You can use label generation to make a unique version name: anywhere in the label, put one or more ? characters. Lccs will substitute a number, zero-filled to the length of the ? string.

On request, Lccs will display the names of existing versions and let you type in the name for the new version. See label reporting and selection.

lccs ci -m"Fixed the last bug" -vbeta??
Lccs will assign a level number from beta00 to beta99 and read a description of this level from keyboard input.

Checking in a huge number of files, most of which are unchanged and won't generate a new version in the archive, can be pretty slow. Perhaps a later version of Lccs will try to optimize by looking at time stamps before calling the RCS checkin routine. Perhaps not.

CO: Checking Out a Version

The co command checks out files from the archive to populate a working tree with a complete, self-consistent version. It takes several forms:

Make a branch: co -b

lccs co -b<new branch> -a<archive root> [version]

To make a new branch, create a new, empty working directory. From that directory, execute the co operation, giving the new branch name and the location of the root directory of the archive.

For the [version] you can specify any existing named version or branch; the files will be taken from that version or the last checked-in version on that branch, respectively. If you don't specify the version, the files will come from the end of the main line (the trunk). In each RCS archive file Lccs will create a new revision number that represents the correct branching, and will label it with the branch name; it will then force a checkin on the new branch.

Of course, there is no limit to the number of branches you can create from a single point, nor any need to keep track of the numbers assigned. In fact, the internal numbers will in general be different in each file.

You will be prompted for a description of the new branch. When the operation is complete, you will have a fully populated working directory tree. Changes checked in from this directory subtree will be entered on the proper branch.

Since this is pretty much useless unless you have the ability to merge code from different development streams, see the merge operation.

Examine a state: co -x

lccs co -x -a<archive root> [version]

To check out a state of the system without making a new branch, create an empty working directory, go there, and execute the command as shown.

If the [version] is not given, Lccs will get the end of the main line (the trunk).

Any number of copies can be checked out into directories anywhere in the file system. Lccs does not remember these copies, and there is no way to check in changes. The idea is to use them for printing, making diffs, and general hacking.

Restore a working tree: co -f -a

co -f -a<archive root>

When work on a branch is complete, it makes sense to check in the final state and then remove the whole working tree. Then, when the customer comes back needing one more little change (or for many other reasons), it may be necessary to bring the branch back to life.

Re-create the working directory for the branch in exactly the same location as before. You may want to consult config.lcc to get this right. Leave the directory absolutely empty, and execute the command as shown. When the disk-thrashing is finished, you will have the last known state of the branch, ready to work on.

Another possible use for this operation: a working tree gets so badly clobbered that it's best to go back to the previous state and start over. You can zap the entire tree and reload it with this option. On the other hand, if only a few files are ruined, you can get them back with the other version of this operation, described next.

Fetch new files from another branch: co -f version

lccs co -f [version]

In the course of development, people will add new files to the system. What happens when files have been added on another branch, and you want to incorporate them in the branch you're working on?

The un-preferred method is to get copies of the files and put them in your working directory. To track them on your branch, you will have to add new track statements in your files.lcc, or be sure that the new files are caught by watch statements on the next checkin.

The recommended method is to modify the files.lcc in the working directory of your branch so that it has track statements for all the desired files, and then execute the co command as given. Note that there is no -a option: your files.lcc specifies where the archive is.

As with other forms of checkout, you can specify the version from which things will be loaded, or the branch from the end of which they'll be loaded. In this case, if a file tracked in files.lcc is not found in the specified version or branch, or none is specified, then Lccs will try two other places before giving up: the end of the current branch, and the end of the trunk.

As noted in the previous section, you can use this operation also to restore files to some checked-in state if they have been clobbered. Just delete the files and fetch the archived version with this operation.

MERGE: Merging Versions

From within the working tree for any branch you can call for a merge of any other version or branch. The command is
merge [-c<common ancestor>] [<version to merge>]

That is, you specify another version, and Lccs executes a series of rcsmerge operations to merge that version into your code. If the version is not specified, Lccs merges the end of the trunk.

The list of files to be merged comes from your own files.lcc. Files known only to the other version will not be merged; files missing from the other version will not be merged, but will generate a harmless diagnostic message. If you need to get new files from the other branch, see the fetch operation.

If you do not specify the common ancestor of two versions, Lccs will not default to the last version on the branch, as RCS does. Instead, it will search each archive file for the version that really is the last common ancestor of the two, according to the i.j.k.l... tree structure. This feature is clever and sometimes convenient, but dangerous. When used over a period of time in merges between a couple of branches, it will at least give rise to massive merge conflicts, which are always error-prone; it may also cause silent errors, but I forget how.

To find out how to merge properly between two branches, see the information on sidelines in the Random Details section.

When you do specify a common ancestor, it must be an Lccs version or branch. Thus, you can use the old trick of backing off all changes between version good and version bad by specifying
lccs merge -cbad good
At least, this is supposed to work. It's possible that it has not yet been tested with any rigor, or maybe at all.

Binary files will not be merged. Rcsmerge won't do since it makes no sense; and Lccs won't ask for it.

Lccs will display on stdout the output from each rcsmerge operation that reports a merge conflict or other problem.

VERSION: Tagging a Version

The   ci -v   operation allows you to assign a version name to the state of the system as you check it in. The version operation lets you assign a version name retroactively to the state as of a given time, such as the time at which you checked it in and forgot to use the -v option. To use it, go to the working tree for the branch on which you want to put the label, and enter
version -v<new version name> [date and time]

Caution: Java has a very clever date input routine that lets you use a variety of formats, with time zone specifications and all. If you're familiar with this routine, you'll be aware of the stupefyingly incredible fact that in the month/day/year format it counts months from ZERO! The caution is this: Lccs does not do anything so inconceivably idiotic, so well calculated to cause unnoticed errors that will blow things up later. Well, maybe it does something that bad, but it doesn't do this one.

The date and time input can be in any of the formats accepted by the Java 1.0 library. If the first field of the date and time spec contains a slash / or colon : character, the input will be interpreted by the following rules, which are not those of Java:
[month/day[/year]] [hour:minute[:second]] [GMT|UMT]

If you omit the date and time spec entirely, the time stamp is right now.

LOCK and UNLOCK: Ad-Hoc-ery

Random ad-hoc operations, like running a bunch of rcsdiffs, are awkward when Lccs is running the archives, because the archives are generally far from the source. Also, setting a default branch is not a hot idea when there are multiple users. To do some direct RCS operations on files that you've set up for Lccs, you are expected to go through this sequence:
lccs lock
some operation
some other operation
lccs unlock

In the scope of the lock-unlock sequence there are several conditions that make RCS operations easier and safer.

Thus, if you have RCS 5.7 or later, you can get the differences between your last checked-in version and your source with a simple
and compare it with the latest version on another branch with
rcsdiff -rThatBranch
This is still no fun. In particular, rcsdiff is such an important operation that Lccs ought to support it directly, and in the process handle multiple-file diffs more reasonably than raw rcsdiff does. My best excuse for not doing it is that Java implementations are not up to handling non-trivial cases of running a program with two output streams and capturing them.

The lock and unlock operations may generate some spurious error messages to the effect that certain archive files do not exist. This is because Lccs, in an excess of caution, tries to set the default branch in every file that files.lcc has heard of, including those which are currently being ignored.

Binary Files

RCS has a facility for handling binary files: files that are not a succession of lines of ASCII text. Weirdly enough, the checkin process apparently tries to do line-by-line comparisons even on binaries, but of course you'll generally wind up saving copies of the whole file when it's updated and checked in. It still may be worth while to retain such archives.

An example of a binary file worth saving in a source control system is the database in which Foxbase saves the definition of an input screen. (Actually, this is two or three separate files). It's messy to save lots of these files, as you would in a project of modest size, and not even be able to do a diff on two versions, but it beats losing the old versions. Maybe.

In Unix systems you probably can perform the initial checkin without specifying that a file is binary, so long as you mark it binary before checking in any further revision. In DOS-based systems, and perhaps in others, this is not so. RCS converts all ASCII files into Unix end-line conventions before storing them and converts back when checking out; therefore, checking in a binary file without properly flagging it as such will in the general case clobber the file. (That is, a single byte with the value 10 becomes two bytes 10 13 when checked in and back out. And so does a pair of bytes 10 13 in the original file.)

Lccs provides the trackb and watchb commands for binary files. For the reasons just noted, letting a file be picked up with a watch statement and then modifying its files.lcc entry to trackb is bad practice.

At the time that it creates the RCS archive for a file, Lccs examines the start of the file and applies some simple tests of whether it looks like ASCII. If the file appears to be of the wrong type, Lccs asks for confirmation. For instance,

File foo looks binary.
Are you sure you want it archived as Ascii? <Yn>
Depending on your answer, the file will be archived in one way or the other and maintained with the proper track or trackb statement.

In a merge operation there will be a diagnostic message, and no other action, for each binary file.

Command Line Details

This section tries to give full details of the command line input, in several pieces. Let the Lccs invocation be
java java-options lccs.Lccs lccs-options lccs-operation operation-options operands
Then the items covered are

Java options

Execute Java with whatever options work. Lccs doesn't care.

Lccs options

An Lccs option applies to whatever operation is performed. Only one is implemented now:
Verbose: List every RCS command issued, and other arbitrarily selected diagnostic data.

Lccs operation options

These options are specific to one or more Lccs operations. There has been some attempt to make them consistent from one operation to another.

Note that when an option takes text as input (e.g., -vSomeVersion), the text comes immediately after the option letter, with no intervening space.

As usual, when an option requires input that contains whitespace, it's necessary to use quote marks, as in -m"this is a checkin comment". The exact syntax for such constructions depends on your Java implementation, your command shell, and how they interact. Examples given in this document have been tested with the 4OS2 shell under OS/2.

-a<archive root path>
Path to the archive tree. There are a few operations, notably the common cases of co, which have no way of figuring out where the archive tree is and must be told. The name given here can be absolute or relative or anything that works from wherever you currently are in the file system.
-b<branch name>
Branch to be created. Instead of giving the exact name of the branch, you can use label generation or label reporting.
-c<common ancestor>
The branch or version to be used as an artificial common ancestor for the merging of two versions. If the option is omitted, the actual common ancestor will be used, computed specifically for each file. If -c is used with no text, the end of the trunk will be used. Label reporting can be used instead of an explicit label.
-d<date option>
For the setup operation, this specifies how the date of the source files will be maintained. See the Mud option.
A flag to tell the co operation to fetch files from an existing branch or version. The -a option will also be required if and only if this working tree is empty and needs to be repopulated entirely.
-m<log string>
Checkin comment. The text will be entered as the checkin comment for each file which, having been actually changed, generates a new revision in RCS. If the text contains whitespace, it will have to be entered with quotation marks in whatever way happens to work right on your system. Regrettably, only one-line comments are possible.
-v<version name>
Version to be created. Instead of giving the exact name of the branch, you can use label generation or label reporting.
A flag to tell the co operation to populate the new working tree without setting up a new branch.


Command-line operands, as distinct from options, give the working tree location for setup, the date and time for version, and the input version (or branch) name for checkout.

Where the operand is a branch or version name, you can specify label reporting instead of giving the name explicitly.

Label (branch and version) generation

Wherever the name of a new branch or version is called for, you may use a name containing a block of one or more question marks, as in
lccs ci -vtryNumber??
The question marks will be replaced by a zero-filled sequence number to produce a unique label, as in tryNumber33.

Label generation applies in the -b and -v options. You can also use it in a typed-in response to label reporting.

Label reporting and selection

The -b and -v options, as well as version operands, accept a funny syntax that will produce a display of existing labels that match a specified regular expression. The syntax may be summarized as
n|d [# of lines] [i] : regexp
This is supposed to represent 5 items strung together without intervening space:
  1. N or D (upper or lower case): if N, look for labels with names that match the regular expression. If D, look for labels with a description containing at least one line that matches.
  2. # of lines (optional): specifies the maximum number of description lines to be displayed with each label. Default is 1. Because of screen sizes, there is a maximum of 10 or something; larger numbers will be truncated.
  3. I or i (optional): ignore case in making a description match. (Names are case-independent anyway.)
  4. A colon (required).
  5. Regexp: a regular expression. The usual considerations apply if this contains whitespace.
For example, given
lccs co -f n10:.
you would get a list of every label (since a . matches any character), with up to 10 lines of description, which might run to multiple screens such as:
Select source version or branch
1. br00  V  Sat Mar 29 16:55:46 PST 1997
  Preliminary work on branching.
  Properly creates and reads AFAIK a Branch entry for the *Trunk*
  when executed against a config.lcc that lacks such an entry.
  Has a dummy foo operation that drives newBranch() and seems
  to work fine.
  Needs to be fixed for execution in all cases from the directory
  for which the operation needs to get the canonical name.
  No hint of co -b yet.
2. alpha07  V  Mon Mar 10 11:52:02 PST 1997
  No real need for a freeze here, but
  this has the -v code fixed to use Version subclass, not Label.
3. alpha06  V  Thu Mar 06 11:45:22 PST 1997
  This one might go out as the beta.
  The debug stuff is out, supposedly.
  The associated version of ddlib is alpha00.

Enter number of desired name, or
Enter the name or a *command: *More names, *Restart list, *Cancel:
If your screen doesn't look enough like this, see the section on ANSI terminal emulation.

The top line reminds you of what you're selecting. The numbered lines show some of the labels in arbitrary order. (In versions 1.1 and later, the labels will be sorted by name or by the first line of description, depending on what selection was used.) All of these happen to be versions, as indicated by the V. The bottom lines remind you of the input possibilities:

Note that names like *Trunk* can be specified only by a numeric selection: when typed in, they look like some unknown command.

Random details


Here are a few known problems that may arise.

Messed-up display screen

If the screen for an operation like label reporting shows up looking strange, like
Enter new version name
then you have problems with ANSI terminal emulation.

Checkins complain about bad date

If checkin occasionally blows up with errors like
RCS system gave status 1
g:\java\archive\lccs\.\Makefile,v  <--  g:\java\lccs_bin\.\Makefile
ci: g:\java\archive\lccs\.\Makefile,v: Date 1998/03/29 23:09:46 precedes 1998/03/29 23:09:47 in revision
Checkin operation reported error
lccs_bin.LccsException: g:\java\lccs_bin\.\Makefile failed checkin.
        at lccs_bin.RCSFile.checkin(Compiled Code)
Then you have a version of RCS that doesn't handle the -d option correctly. You need to patch the MudOption line in config.lcc. See the section on Source file dates.

Merging sidelines properly

The first thing to be said about mainlines and sidelines is that there's no such thing. as far as the software is concerned. What you see as merging a lot of fixes from the main line into a sideline for some large development is just the merging of a lot of files that may not even be on recognizably related branches according to RCS.

Furthermore, the common ancestor of two versions, though deducible simply by examination of the version numbers, is very often irrelevant to the operations you want to perform. The merge operation can make the deduction and use it, but it can cause trouble.

But we can recognize a destination branch, or stream, which is the one we're merging into, which is the one that owns the tree that contains the working directory. And there is a source branch that's being merged into it.

Suppose you make a new branch, which you think of as a sideline for some experimental project. On the sideline, you cut out a large chunk of code and replace it with something else. Meanwhile, on the mainline, you make a couple of changes in that chunk, along with a lot of unrelated changes. Then you merge the mainline into the sideline to keep up to date; you omit the -c  option, and lccs   finds the true ancestry of both lines and constructs the proper merge operations, file by file. There will be merge conflicts, of course. You fix them and go on developing on both lines.

Later you merge the mainline into the sideline again, to pick up still more fixes from the mainline. You lazily omit the -c  option, to use the formal common ancestor. As it happens for the sake of the example, neither line has done any more to the chunk of code we dealt with last time. But both have changed it since the time of the common ancestor, and you get the merge conflicts all over again! Obviously this is troublesome and hazardous and gets much worse in real life.

What you really want in this particular case is the state of the mainline, which was the source, at the time of the last merge. This is the true common ancestor of the current versions on the two branches; with this, only new mainline changes will cause conflicts in that chunk. And those conflicts are inherent in what you're doing, so get used to it.

The obvious answer is to tag the state of development lines at critical times and use those version tags with the -c   option. Here's a set of rules to get merges to work well regardless of the random sequence of merges back and forth between two lines.

Assume that we have two branches, foo  and bar . To keep track of merges between the two, we'll have a family of version labels foobar00, foobar01, ... . Or they could be named barfoo...  or whatever you want. But a version family is needed for each pair of versions that will interact.

Now we'll merge foo  (source) into bar  (destination). Here's the procedure.

  1. Get things ready and checked in on both branches, of course. You'll probably want to tag the current state of the destination bar , using the -v  option or the version  operation.
  2. Go to the destination bar  and perform the merge. If you have been following this procedure in earlier merges of the two branches, the operation is simply
    lccs merge -cn:foobar foo 
    This brings up a display of all existing foobar labels; if it brings up other labels, see below. Select the one with the highest number and let the operation proceed.
    If this is the first merge between these two branches, you omit the -c  option and let Lccs work it out.
  3. As soon as the merge is mechanically complete, go to the source branch foo  and tag its current state, using
    lccs version -vfoobar?? 
    This creates a new serially numbered label.
  4. Go back to the destination directory bar  and fix things up by clearing merge conflicts and getting the code to a state in which it compiles or whatever you consider reasonable. Do not make other changes yet.
  5. As soon as you like the state of things, you'll surely want to save it as
    lccs ci -vbar?? 
    or something of the sort. But do not save it with a foobar  label.
Really, this is a simple procedure, and it's supposed to be infallible.

In describing the merge step, we gave the simplified version of the command. Strictly speaking, to keep the list of versions free of spurious items, one would use
lccs merge "-cn:^foobar" foo 
The hat ^ anchors the match so that a label with foobar buried in the middle won't show up in the list; the quotes protect the expression from being mangled by the command interpreter. It's fairly unlikely that you'll need to do this.

The instructions here make a special case of the very first merge of the two lines. You can avoid that, if you like, provided that one branch is being split directly from the other one. For instance, if you're creating bar  from foo  with the lccs co -b  operation, you can set up the common ancestor immediately by going to the original branch foo  and entering
lccs version -vfoobar00 
Later, when you start merging, every merge can then be done in a consistent way.

The tagging procedure applies to any lines that are merged, regardless of where they were originally branched. For the first merge of lines that are not closely related, you'll probably want to omit the -c   option and grit your teeth and resolve the many conflicts that you're likely to get. But if you want to do something more clever, remember that the version operation can create a tag for the state of a selected branch at an arbitrary time.

Lccs internal files

files.lcc  is considered a user-modifiable file. Be careful with it, because there are many types of error that will just blow up Lccs.

The full format of the file is explained in the section on setup, except for the first line. In an active working tree the first line of files.lcc will be
archive <archive tree path>
There will also be a version identifier line, sometime, probably. Don't mess with either of these lines.

Elsewhere in the file you can make changes freely, so long as the result makes sense.

There are a few actions in handling the file list that may be less than obvious.

If you have a FAT or other wholly inadequate file system, the usual archive names ending in ,v are impossible. Lccs will create a subtree RCS in your archive tree, and will store all the archive files in it, with just its own files in the root of the archive tree. The name RCS in the path to a file is in this case the only way for the RCS package to know which file is the source and which is the archive; hence the Lccs requirement that RCS cannot appear in the path to either the working tree or the archive tree.

The config.lcc file is not supposed to modified by the user. The interpretation of the data in the file should be self-evident. And, of course, you will have to edit the file if you ever want to change the global watch conditions.

Locking and unlocking

Lccs tries to protect itself from being executed simultaneously by two or more users. Incredibly, the ultramodern network-aware Java language provides no mechanism for this; so the program uses a file-renaming kludge that is likely to work in any decent Java implementation.

While we're on the incredible items, there is at least one indecent Java implementation, which reports success for any File.renameTo() operation whatever. If you have this one, the locking mechanism won't function. Get a real library. Currently ( Lccs version 1.1 or later) the setup operation checks for this version.

If you try to execute Lccs against an archive that's currently locked, you get a message like

Archive in use:
Locked from g:\java\lccs at Wed Oct 01 13:06:37 PDT 1997
Try again? (Y/n)

This may be because someone else is using the archive; if so, wait a few seconds and try again.

Then again, perhaps somebody has done a lock operation and gone to lunch. How to handle this as a policy matter is not my business. If, however, you want to unlock the archive by force, just go to the proper directory, which is in the working tree for whatever branch the person was working on, and do an unlock operation. As a policy matter, even though it's none of my business, might I suggest letting the owner of that branch know what has happened?

Then again, maybe Lccs crashed and left the archive locked. In fact, it will do this deliberately if its database is messed up badly enough to prevent any useful work. It's advisable to find out why the thing crashed; the information in the message may be helpful. Once that's settled, the obvious brute-force approach is to unlock the system as in the previous paragraph.

If the lock file itself gets trashed, the "Locked from" line may be blank or gibberish. After determining what happened, you can restore operation of the lock quite simply: Just go to the root of the archive tree, and rename file unlock.$lc to unlock.lcc. Or, if the $lc file doesn't exist, just create unlock.lcc.

ANSI Terminal Emulation

The label reporting display uses ANSI terminal emulation. If you do not have this feature enabled in your text screen, the display may come out rather like

 [2JEnter new version name
1. port00  V  Mon Apr 14 14:11:49 PDT 1997
  This is a second attempt to stamp today's version.
2. lccs00  V  Fri Sep 12 16:00:45 PDT 1997
  Before preparing the release (branches implemented) of Lccs.
3. post00  V  Tue Feb 18 16:11:54 PST 1997
  Posted 97/2/18 PM
 [22;1H [K
 [KEnter the name or a *command: *Restart list, *Cancel:
Corrective action depends on your system. In DOS you need to call for the device ANSI.SYS in your config.sys file. In OS/2 the mode is on by default, and can be controlled by the ANSI command in config.sys or at run-time. On other systems, who knows? Probably you do.

Source file dates (the Mud option)

By default, RCS checks in a file, releases your lock on it, and deletes the source from your directory. This model of development is not what Lccs deals with.

If you check in with the -u option, RCS preserves the source file. Or rather, it deletes the source file and then checks it out again, regardless of whether any change was checked in. This results in a change of the last-modified time to the present; therefore, the next Make after a massive checkin will recompile everything. There seem to be Make files that rely on this behavior, judging from comments in the RCS documentation for the -u option. It's not what I want.

With the -M option the last-modified time is set to the time of the most recent checked-in revision. This means that you get a recompile only for things that were actually changed, not your whole pile of source. Lccs operated in this way through version 1.20. There are still situations in which this is bad behavior, as when a processor tries to time-stamp its output.

The -d option sets the last-modified time explicitly; with no operand, it sets the time to the value that it had before the checkin. Just what one wants. Therefore, Lccs from 1.21 on uses the options -M -u -d.

But in fact some versions of RCS get it wrong. Guess who has one of these versions. RCS miscalculates the time-stamp by one second and gets so mad about that stupid error that it performs hara-kiri to save its honor. (See Troubleshooting.) This is why the option wasn't used before version 1.21. Now, though, there is a nasty brute-force kludge to get around the problem.

The field MudOption in config.lcc defines the handling of the -d option in checkin. It is initialized by the setup -d option, and of course it can be manually patched. Existing archives will silently adopt the default MudOption value when first accessed by a new version of Lccs.

If setup -d is given no text value, or the -d option is simply omitted, then MudOption will also get a null value. Checkins will use -M -u -d.

If the operand to -d is ! (a single exclamation point), then checkins will not use -d, but just -M -u. The exclamation point is the symbol of negation, of course, in many modern languages, as in "Our methodology improves your productivity 50%!" which uses postfix notation to say "Our methodology improves your productivity 50% NOT."

Any other value is the directory in which the file is to be found. To use this option you must (a) have Perl5 on your path; (b) pick up from the Lccs distribution and put it somewhere known; (c) use setup -d<that place> or set MudOption <that place> in config.lcc. Thus, I use
lccs setup -d%perlsrc% sourceRoot

*Junk* and *Trunk*

Lccs has two reserved labels, *Trunk* and *Junk*. They are effectively reserved by the simple device of rejecting any user-supplied name that doesn't begin with a letter or underline _ character.

RCS has an anomalous treatment of what I call the trunk: the main line of numbers 1.1, 1.2, 1.17, 2.42, 197.32768, etc. In the course of development they are a single sequence, in which you may increment the first number upon shipping a new release or something. But in some ways RCS treats them as branches, as if they were 1.3.1, 1.3.2, and so on. Where this gets annoying is the lack of any explicit way of referring to whatever happens to be last on the trunk. To get that in direct RCS operations, you set the default branch to nil and do your checkin (or whatever) without specifying a revision number.

This doesn't work very well for Lccs. In fact, it qualifies as one of those Features that can lead you into a bad bug even though you thought you were keeping the problem in mind. So Lccs defines a name *Trunk* in every archive file, and uses that when referring to the main line.

At present, *Trunk* is always set to 1. Newly created archives get it set to 1 because the config variable TrunkLevel is initialized to 1 and never changed. There is no obvious reason that you couldn't lock the system and use RCS operations to redefine *Trunk* in one or more archive files as 2 or some other value. Also, you could change TrunkLevel. These operations are unofficial, unsupported, and untested; but if you try them and Lccs somehow messes up, gimme a bug report.

The label *Junk* is maybe a little more idiosyncratic. The problem is, no matter how much I say you must not do ad hoc RCS operations without first locking the system, there's no real way to prevent it -- though it's clumsy enough to deter a really casual user. If you do it anyway, without specifying a -r option on your ci operation, your changes will get checked in on the end of some branch (whatever is the default at the moment), and the owner might be less than happy about it. So that's what *Junk* is: the default branch in all archives at all times except when someone has the lock set. If something disappears, check this branch, which should never have any contents beyond *Junk*.1.

By the way, the decision to set the default branch on everything is what makes lock and unlock so awfully slow. Try the -v option if you want to see something repulsive.

ddlib classes

The package ddlib defines some classes that provide idiosyncratic solutions to problems that I expect to encounter more than once. Not all of the classes are actually used by Lccs. For the morbidly curious and for anyone who might save a few minutes of programming by using one of the classes, here is a list of some of the contents of ddlib. I'll provide actual documentation on request.

In the latest release these entries have been moved to the awful Java-compliant directory COM.dandrake.lib instead of ddlib.

An extension of PrintStream that provides some of the functions of the standard ANSI terminal emulation, such as clear and goTo. It also has a quasi-singleton entry that returns a single ready-made connection to stdout.
An extension of LineNumberInputStream that actually lets you read a line at a time with readLine. Duhhh. Obsolete, since JDK 1.1 provides such a facility.
An extension of pat.Regex that accepts a wildcard spec and turns it into a regular expression. Obsolete: the current version of the regexp package has this facility.
You don't want to know.
A simple class that uses a call to Perl to get the environment variables that JavaSoft doesn't want you to have.
A class that generates Enumerator objects to find files that match a given wildcard pattern. Handles a path intelligently, even a DOS-style path with a volume name. Filename case dependence is optional. Obsolete, I think, given the capabilities of the latest regexp version.
An extension to the Date class, curing the latter's insane predilection for counting months from zero in user input that employs the month/day/year format. While it's up, it allows some abbreviations, like omitting the year, and puts low-numbered years, like 00, in what is probably the right century. Other date input formats are bucked up to the parent class.
Manager for temporary files. Instantiating the class generates a file with a strange name for which we try to assure uniqueness. If you eventually want to rename the file to replace an existing file, the class handles the grubby details, including renaming of the original file to some sort of .bak file according to your specifications. If a ScratchFile object is created and never used, the scratch file will be deleted automatically.
A class that tries to prevent multiple independent executions of a program (or a family of programs) from going through the same code at once. Since Java provides no such facility, this uses a kludge involving the renaming of a file, which ought to be a unitary operation in any system. Its overhead is not exceptionally low.

What next?

There are no current plans for a next release, apart from making the program, which will turn out to have been bug-free, an official non-Beta release. Some things are on my mind, though, and comments will be welcome.

As noted, there ought to be support for rcsdiff and perhaps other RCS operations like co -p. Maybe when I've converted to JDK 1.1 and found that it supports external program execution reasonably well...

Now that we have binary files, it would be really nice if the checkin and checkout kept them stored in some compressed form. Not all binary file formats have very high entropy (cf. Foxpro), so the saving could be considerable.

We really need a userid field in the data for each version and branch.

A real pet idea, which I think could be useful: there should be some sort of trace 305-327 operation which would find a block of lines within a given version of a file and list the revision or revisions at which the lines first appeared in their present form, plus the later revisions, if any, in which they were first changed. In other words, "Who the hell changed that line, and why?" -- or at least the data required for starting to answer the question.

If anybody would use the feature, there ought to be a way to bump all the files, or selected files, from revision 1.x to 2.1 or whatever.

If it were a real program for public use, there would be ways to modify its database without kludgily editing files.lcc and config.lcc.

Vitally Important Boilerplate

Bug Reports

Bug reports on this program will be greeted with all possible enthusiasm. Favorable usage reports will be greeted with even greater enthusiasm than is possible for bug reports. My e-mail address is at the bottom of the page. Enjoy.


This program is provided at no charge, with NO WARRANTIES OR REPRESENTATIONS WHATEVER as to whether it will actually work or even manage not to destroy all your data and set your computer on fire: risks which you assume gladly and with no mental reservation of any kind.

Copyright Notice

Copyright on this document is listed below.

The executable program is Copyright© 1997 by Daniel Drake. An unlimited license for use and distribution of the current version of the Lonesome Cowboy Configuration System program is hereby granted on the condition that no charge is made for the program, and its origin and copyright are acknowledged.

ALL WARRANTIES WHATSOEVER ARE DISCLAIMED; SEE THE PREVIOUS PARAGRAPH. Sorry about the shouting; American law and practice call for it.

The regular expression classes distributed in the file are under the copyright notice contained in the file copyrght.htm.


Date last modified: February 19, 2003.
Built February 19, 2003
Dan Drake's Home Page
Mail to
Copyright (C) 1998 Daniel Drake. A royalty-free license to reproduce this document in whole or in part is hereby granted provided (i) all additions, omissions, and other changes are clearly marked; (ii) the work is not reproduced as, or as part of, a work for which payment is charged; (iii) this notice is reproduced without change. Quotations for critical or polemical purposes, with proper attribution, are permitted in any case, being obviously fair use.