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.
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.
lccs125.zip
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: lccs.htm
lccs.zip
pat
package.
copyrght.htm
pat
package.
demo.zip
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.
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.)
classpath=.;c:\javasys\lib\jempcl.zip;G:\mystuff\java\zips\lccs.zip
-classpath
option
when executing lccs
, as injava -classpath lccs.zip;%classpath% lccs.Lccs <etc.>
-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.
demo.zip
which came with the package. It doesn't matter where you do this. You will get a directory structure
demo
archive
branch
source
stuff
nonsense
sub1
foo
nil
files.lcc
foo
goo
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
or
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
Y
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
*C
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.
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.
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
files.lcc
. You can save
a lot of work in preparing this list because
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.
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
.
java lccs.Lccs setup <working root directory>
lccs setup <working root directory>
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:
veryLongName.java,v
?
In DOS (FAT filesystems) it should say that long filenames are not
allowed; in Unix and HPFS filesystems it should say that they are
allowed. In others, it will either get the right answer or not. In any
case, the system that it tests is the archive tree, not the working
tree.
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
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:
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.
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??
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
command checks out files from the archive to populate a working tree with a complete, self-consistent version. It takes several forms:
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.
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.
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.
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 [-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.
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 enterversion -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]
rcsdiff
s, 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.
RCS
, containing the path of the related archive directory.rcsdiff foo.bar
rcsdiff -rThatBranch foo.bar
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.
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.
java java-options lccs.Lccs lccs-options lccs-operation operation-options operands
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.
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.
-c
is used with no text, the end of the trunk will be used. Label reporting can be used instead of an explicit label.
setup
operation, this specifies how the date of the source files will be maintained. See the Mud option.
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.co
operation to populate the new working tree without setting up a new branch.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.
lccs ci -vtryNumber??
tryNumber33
.
Label generation applies in the -b
and -v
options. You can also use it in a typed-in response to
label reporting.
-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 asn|d [# of lines] [i] : regexp
lccs co -f n10:.
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.
Enter new version name
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 1.1.2.2. 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.
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.
bar
, using the -v
option or the version
operation.
bar
and perform the merge. If you have
been following this procedure in earlier merges of the two branches,
the operation is simplylccs merge -cn:foobar foo
-c
option and let Lccs work it out.foo
and tag its current state, using lccs version -vfoobar??
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.lccs ci -vbar??
foobar
label.
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.
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.
files.lcc
and config.lcc
, currently.
If a track
statement with one of these names gets into the
list, Lccs will refuse to run. If it finds a wildcard match on one, it
will force an ignore
and print a message to that effect.
ignore
statement disappears from the working
tree, then the ignore
will silently disappear from the
file list in the next checkin. After that, if it's created again and caught by a wildcard match, Lccs
will ask whether to track it.
track
statement disappears from the working tree,
nothing special happens unless it also has no archive. (If the file isn't
in the working tree,
the Lccs checkin operation will simply not check in a new version, since there
isn't one at the moment.) If there is neither
a working file nor an archive, the file list is not valid, and Lccs will
refuse to proceed.
,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.
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
.
[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.
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 mudsim.pl
is to be found.
To use this option you must (a) have Perl5 on your path;
(b) pick up mudsim.pl
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
*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
classesddlib
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.
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
.LineNumberInputStream
that actually lets you read a line at a time with readLine
. Duhhh. Obsolete, since JDK 1.1 provides such a facility.
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.
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.
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.
.bak
file according to your specifications. If a ScratchFile object is created and never used, the scratch file will be deleted automatically.
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
.
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
lccs125.zip
file are under the copyright notice
contained in the file copyrght.htm.
Document: http://www.dandrake.com/lccs/lccs.htm