Different Ways To Specify Revisions
Release numbering based selection
The co
command by default checks out the latest revision of
the baselines specified by the user. For instance, you can check the
latest revision of helloworld.c
out of your
helloworld.c,v
baseline by typing any of the following commands:
co helloworld.c
co hello*
co *,v
If, however, you want to check a specific revision out, you can
specify which revision. This command would check out your first revision
of the file:
co -r1.1 helloworld.c
You can also make RCS check out the latest revision of the specified
release or branch number. This command would check out the latest revision
of release 1:
co -r1 helloworld.c
Release-based selection can work in several slightly different
ways. If no attempt is made to specify an exact version number, the
latest revision of the item(s) will be checked out. If a release
or branch number is specified (more on branches later), the latest
revision in that release or branch will be checked out. If the exact
number of a revision is specified, that revision will be checked out.
But what about the situations where release-based selection just isn't
clear enough? With many baseline files, each undergoing many stages of
development, keeping track of which of the dozens of revision numbers
are compatible can be a nightmare. Luckily, RCS offers other means of
revision selection.
State and author based selection
Users can also specify revisions to be checked out based on the settings
of the RCS files' state and author (These can
be set by the rcs utility, discussed later).
RCS "states" are based on the status of the file within
the project framework. Any uniquely identifying strings can be used for the
possible states, but a typical set is:
- Exp - Experimental (This is set by default by ci)
- Stab - Stable, i.e. a revision that actually works
- Rel - Released
The -s parameter of co
selects a specific state.
The author of the file is typically the login id of the
user who created it, and is selected by the -w parameter of co
.
An example to check out the latest release of helloworld.c
which I had written would be:
co -sRel -wdspencer helloworld.c
Date-based selection
If a project release was known to have happened at a certain date and
time, for example, RCS selection can be made based on the "cut-off
date" of that release. Date-based selection will check out the revision
which is closest to - but not later than - the specified date.
Example:
co -d"January 13, 12:00 PM" hellowor*
Name-based selection
Release selection, state, author, and date-based selection are all very
useful. These still may not be enough for large projects, however. To
allow a user to specify a truly arbitrary group of revision items in a related
set, RCS includes name-based selection.
Users can specify a symbolic name of their choice with the
ci
or rcs
programs to associate that symbolic
name with a specific revision of a baseline item. If the same
symbolic name is assigned to all items which comprise a certain compatible
system, they can all be checked out by simply specifying that symbolic
name.
For instance, say a user wished
to associate revision 2.1 of program1.c with revision 5.3 of program2.c.
By checking both in with a new symbolic name, they can in the future use
that symbolic name to represent the known versions of each program file.
co -r2.1 program1.c
co -r5.3 program2.c
ci -n"My New Name" program1.c
ci -n"My New Name" program2.c
co -r"My New Name" program*.c,v
This feature lends powerful force to the use of RCS across an entire
project. A utility program called rcsfreeze
can be used to
"freeze" a certain configuration of all project elements by
assigning the selected revisions the same symbolic name.
Note: unfortunately the rcsfreeze
command doesn't seem to
exist on the Snakes. Users will have to set symbolic names manually,
with the -n"" and -N"" options of ci
Now, spend a while getting comfortable with the different ways you can
check in and check out program items. Reference the manual pages for
ci
and co
for specific details about how they
operate. Here are some exercises to use as starting points:
- Can you mix and match different selection types on
the same checkout command line?
- What happens if you specify conflicting selection
types that don't exist in that combination?
- Can you assign the same symbolic name to two revisions of the same item?
- If a symbolic name is assigned to one revision, what do you need to
do to get it assigned to a different revision?
- Create several baseline items in your current directory, and
assign a symbolic name to a few of them, but not all. If you
then check out
*,v
and select that symbolic name, which
items do you expect to be checked out? Which items actually are checked out?
Branches, Splitting and Merging Trees
Branches
Strictly linear version trees are what most of us are probably used to from
academic experiences. You create revision 1.1 of a program, fix it up
a bit for revision 1.2, add some more features for version 1.3, actually
get them working right for version 1.4, et cetera. By version 2.1, you
consider it to actually be done, so you call it version 2, but then you
find one last detail to fix - revision 2.2 becomes your newest.
(1.1) -> (1.2) -> (1.3) -> (1.4) -> (2.1) -> (2.2)
A slender revision tree has only one branch, called the trunk.
In the "real world," these trees actually tend to grow various
branches at different points.
Branches occur in a revision tree when a user creates a
new revision from the middle, rather than the end, of the tree. For
instance, a branch would be created off of revision 1.3 in the above
revision tree if the user later checked out 1.3, modified it, and checked
it back in. Branches are numbered two levels deep, so the newly checked-in
item would be revision 1.3.1.1. This double numbering is necessary to
allow for the possibility of a future 1.3.2.1 branch from 1.3.
(1.1) -> (1.2) -> (1.3) -> (1.4) -> (2.1) -> (2.2)
\
----> (1.3.1.1)
Typical branching situations
Branching situations might arise in situations of:
- Parallel Development - A stable version usually needs to be the
primary one under development, but alternative or experimental development
approaches may be carried out under branches.
- Temporary Fixes or Distributed Development and Customer
Modification - Say a customer of a large software system needs to
develop a site-specific bug fix based on the version of the system
they currently use, which may not be the latest
version under development. A temporary bug fix branch
may be generated by the developers, to be reintegrated with the trunk
when the opportunity presents itself. Alternatively, if the customer
has the source code available, they can generate a modification branch
on their own.
- Conflicting updates - If a programmer locks out a baseline
to make changes to it, but someone else needs to check it out
immediately to work on another part of it, the first person's changes
might not be
finished or stable enough to work with. A branch can be generated by
the second person
from the previous revision of the baseline, to be reintegrated when
they are both stable enough to rejoin the baseline.
Branches and Deltas
Digging back into the actual implementation of RCS for just a minute, how would
you choose to actually represent the text changes - deltas - for side (forward)
branches? The standard revision tree is done with reverse deltas, but
reverse deltas won't quite work for forward branches. It would be wasteful
of space to store a complete copy of the branch revision for each branch
tip, so RCS also implements forward deltas from the branch point
off of the trunk.
Merging
You'll notice that the last two examples of branching situations
speak pointedly about "reintegrating",
reattaching branches to the trunk of the revision tree after
both have stabilized. Luckily, RCS provides facilities to do this
automatically where possible.
The utility program rcsmerge
will merge seperate revision branches back together. It compares the
two branches "rev1" and "rev2" with respect to
their closest common ancestor (Repectively, perhaps, 1.3.1.1, 1.4, and
2.2 from our example above) and determines which lines of text are:
- Identical in all three revisions
- Identical in two out of three revisions
- Different in all three revisions
For case 2, where a line in "rev1" differs from a line that
is identical in the other two revisions, rcsmerge
replaces
that with the text from "rev1". Case 3 "indicates an
overlapping change, is flagged as an error, and requires user intervention
to select the correct alternative." [TIC85]
If rev1 is greater than rev2, is is treated as a reverse merge,
and attempts to undo all changes in the specified file that
merged rev2 into rev1.
An example of how to use rcsmerge
. Note that you must have
checked out and locked the revision file which you will be merging, and
that the output of rcsmerge
goes to standard output, so it
must be redirected into a new file that does not exist.
[~/csc441] 82: co -l helloworld.c
helloworld.c,v --> helloworld.c
revision 1.3 (locked)
done
[~/csc441] 83: rcsmerge -p -r1.2.1.1 -r1.3 helloworld.c,v > helloworld.merged.c
RCS file: helloworld.c,v
retrieving revision 1.2.1.1
retrieving revision 1.3
Merging differences between 1.2.1.1 and 1.3 into helloworld.c; result to stdout
[~/csc441] 84:
Play around a little with branching. If you can get rcsmerge
to work right (I've found that it's a little finicky), see what happens when
you try to merge a modified branch back onto the trunk of your revision
tree.
The RCS Baseline Directory and make(1)
This tutorial has so far shown you how to use the basic RCS features to
check items in and out of the database. This is useful in and of itself
if you want to simply create multiple versions of a file, but it can also
be extended to be of much greater use in a multi-person project.
If you create a central directory where your RCS baselines are to be
stored, all people on the project can then create symbolic directory
links such as "~/work/RCS/
" to it. It would be slightly too
chaotic for this tutorial for everyone to attempt to checkout and lock
files from a single central directory, so create a directory named RCS
of
your own which isn't a symbolic link, and experiment with
putting your baseline files in there.
If a subdirectory named RCS
exists beneath your current
working directory, all checkins and checkouts will be made by default
to and from files in that directory.
Many of you have probably also used make(1). Make is a
UNIX tool which will compile a group of source files in a particular
way based on compilation rules written down by the user in a file named
"Makefile". Using make makes it much, much easier to
compile large projects. Because RCS is also so useful in large project
situations, the make tool has been extended to support
"auto-checkout" features for RCS files. If a target file
that it is looking for is not available in the current or specified
directory, make will search both the current directory and the
RCS
subdirectory for a checked-in version of that file.
If one exists, it will be checked out, used in the compilation, and
deleted when no longer needed. (If make finds the file
before needing to look for RCS files, of course, it uses the checked-out
working file rather than overwriting it with a possibly older version.)
Questions For Thought and/or Things To Try:
- Why would it be a bad thing to allow a user to edit the baseline
file
file.c,v
directly? (A correct installation of RCS
doesn't allow this - the baseline files are made read-only.)
- What would be the security implications of putting the baseline
items in a central directory that, obviously, all users in the project
would have to have file-permission access to?
- If you want to, see if you can get make to check out a file
from your
RCS
directory and compile it. Does it seem like
it takes longer to checkout and compile than if the file is already
checked out?
Access Lists
UNIX file permissions aren't very helpful sometimes. They can be incorrectly
set, or a situation can exist that UNIX permissions just don't help with.
RCS implements its own access lists. By default, the access list to an
RCS baseline is empty, and everyone who has UNIX permissions to access
that file is allowed to do so. If the access list is non-empty, then only
the people listed in it are allowed to perform update operations on that
file (e.g, allowed to check in modified revisions). The access lists
can be set with the utility program rcs
.
If you want to use RCS for a project, experiment with adding an access
list to your baseline items. Reflect on the earlier security-implications
question.
Appendix: A Little About Some Of The Auxiliary RCS Programs: rcs, rcsdiff, (rcsfreeze?), rlog
(Adapted, in abbreviated form, from the appendix of