There are two "standard" source code management systems available on Unix. These are SCCS (Source Code Control System) and RCS (Revision Control System).
SCCS is included with many versions of Unix (SunOS and AIX included). RCS is public-domain software that must be obtained and installed.
SCCS and RCS support many similar features. RCS appears to have
a superior implementation, therefore we will focus on RCS.
static char rcsid[] = "$Id$";For a .h file, the line should have this form:
/* $ID$ */For a csh shell script (or perl script) file, the line should be
# $Id$When RCS retrieves a source file for the user, the $ID$ notation is expanded into an identification string that provides information about the the version of the file, the time & date of last modification, and the file owner.
ident prog[Note: ident is a variation on the standard Unix command strings which locates all printable strings longer than a specified minimum in an executable file.]
ln -s /home/myboss/zzz-project/RCSand that would create an entry in the current directory named RCS. If a command like
ls -l RCSis subsequently executed, the result is the same as if
ls -l /home/myboss/zzz-project/RCShad been executed.
ci foo.cThe ci command of RCS assigns revision number 1.1 to the file and it asks us to enter a brief description of the file.
co foo.cwill check out the latest revision of foo.c and create a read-only copy in your directory. This is what you need for browsing the file.
co -l foo.cwill check out the latest version of foo.c and create a writeable copy in your directory. This is what you need for making updates to the file. The -l flag means locked. If any other programmers subsequently try to check foo.c out for update, they will be refused access (and they will be told who has the file checked out).
ci foo.cThe programmer will be asked to supply a short description of the changes that were made.
ci -r2.1 foo.cwill check the file in as version 2.1.
co -r1.3 foo.cA second way is to ask for the revision that was current on a particular date, as in:
co '-d 25-May-1995' foo.c(The quotes are necessary.)
1.1 1.2 1.3 1.4 1.5 2.1 2.2 2.3 3.1However, it is more than possible that the software developers need to produce multiple versions of the product. E.g., there may be the Windows version, the Unix version ... There may be the domestic North American version and the export version. The possibilities are endless.
E.g., if the revision sequence
1.1 1.2 1.3 1.4 1.5 2.1 2.2 2.3 3.1corresponds to the Unix version of the software, and we now want to develop the Windows version, we would pick one of the Unix revisions as being a base for the new version. Let's suppose that we pick revision 2.2 for this purpose.
We would check out revision 2.2 for browsing ...
co -r2.2 foo.cwe would change the file permissions on foo.c so that it is no longer flagged as read-only ...
chmod 644 foo.cwe would edit the file to make it Windows specific, and then we would simply check in the file with a longer revision number ...
ci -r2.2.1 foo.cThis creates a new version which has revision number 2.2.1.1.
This procedure gives us version trees.
Tichy describes other scenarios where version trees are useful. For example, suppose that we have been selling a software product, and that there have been many releases of the product. Some customers are using revision 1.1, others are using 1.2, and so on. The revision tree might be as follows:
1.1 1.2 1.3 2.1 2.2 3.1 3.2 3.3Now suppose that a customer reports a bug in revision 1.3 of the product. What we should do is check-out 1.3, fix the bug, and check in the new code as revision 1.3.1.1. That is:
co -r1.3 code.c chmod 644 code.c ... edit code.c ci -r1.3.1 code.cWe cannot check the revised code in as 1.4 to be inserted between revisions 1.3 and 2.1 in our version tree because revisions 2.1 were not derived from 1.4 (and do not include our new bug fix).
Of course, we may later wish to take the bug fix of 1.3.1.1 and
make that same bug fix to the 2.1, 2.2 and later revisions of
the code. (The rcsmerge program has been provided for exactly
that purpose.)
1.1 1.2 2.1 2.2 2.3then the latest revision (2.3) is stored unmodified in the file foo.c,v. In addition, the foo.c,v contains a delta (Tichy calls it a reverse delta) which consists of the edits needed to convert the 2.3 revision of foo.c back into the 2.2 revision. And the file contains three more reverse deltas needed to convert revision 2.2 into 2.1, 2.1 into 1.2, and 1.2 into 1.1.
1.1 1.2 2.1 2.2 2.3 2.1.1.1 2.1.1.2The foo.c,v file would contain revision 2.3 in its entirety and alll the same reverse deltas as before. In addition, the foo.c,v file contains two forward deltas: one delta is used to convert 2.1 into 2.1.1.1, and the other converts 2.1.1.1 into 2.1.1.2.
rcsdiff -r1.3 -r2.2.1.2 foo.cIf you have checked out foo.c for update, you can remind yourself of what changes you have made by executing
rcsdiff foo.c
rlog foo.c
rlog -l foo.c
rlog -lbill -L -R RCS/*,v
Normal Unix file protections must first be set up. There needs to be a `group' set up for the project, and all Unix accounts for the programmers must belong to that group. (The file /etc/groups defines all such groups on Unix.) The RCS directory must be owned by that group.
The project manager should subsequently execute commands like
rcs -abill,jane foo.cto add update access permissions for users bill and jane.
To provide access to all files in the RCS directory, the command is
rcs -abill,jane RCS/*,v
rcs -sStab:1.3 foo.cwhich changes the status of revision 1.3 of foo.c to Stab.
rcs -U foo.cand to turn it on again, execute
rcs -L foo.c
rcs -u foo.c
rcs -u foo.cthen the file will be unlocked and an e-mail message sent to the person who held the lock.
rcs -o2.2 foo.cthen revision 2.2 of file foo.c is deleted. The preceding and succeeding revisions still exist. The net effect is that of merging revision 2.2 with its successor (probably numbered 2.3). The letter `o' for this option is supposed to suggest the word outdated.
One way of eliminating the confusion would be to keep all revision numbers in lock-step. Whenever we update foo.c from 2.6 to 2.7, then main.c (and all other files) are renumbered as 2.7 too.
RCS provides a better solution. It allows you to attach symbolic version names to file revisions.
For each file used in the released product, we could execute a command like this:
rcs -r2.7 -nV6.0beta foo.cThis attaches the symbolic version name V6.0beta to revision 2.7 of foo.c.
[ Alternatively, the symbolic name can be attached when a file is checked back in, for example:
ci -nV6.0beta foo.cAlso, one can move the symbolic version name from one revision to another. Executing
rcs -r2.8 -nV6.0beta foo.cwould transfer the label to revision 2.8.]
If you wish to create the released version from the latest revisions of all the files, there is a simple short-cut. One just needs to execute:
rcsclean rcsfreeze -nV6.0betaThe rcsclean command automatically checks any files back in that you have checked out for update. (Otherwise RCS will use the latest revisions that it knows about.) The rcsfreeze command then attaches the symbolic label to every file.
co -sV6.0beta foo.c bar.cAnd to check out that version of all the files, we can use the command:
co -sV6.0beta RCS/*,v
The public-domain version of make distributed by the Free Software Foundation has support for RCS built in. The standard make does not. To use the standard make, we must add additional rules to our Makefile. Here is an example:
## Example Makefile for use with RCS RCSFLAGS = CFLAGS = -O CC = cc OBJS = foo.o main.o .DEFAULT: co $(RCSFLAGS) $< myprog: $(OBJS) $(CC) $(CFLAGS) -o foo $(OBJS) clean: rcsclean rm -f a.out core *.o foo.o: foo.h foo.c $(CC) $(CFLAGS) -c foo.c main.o: foo.h main.c $(CC) $(CFLAGS) -c main.cThe .DEFAULT name is a special make target. It is a rule to be applied if a needed file is missing (and there are no explicit or implicit rules that can be invoked to create the missing file). In our case, when a file is missing from the current directory, we want to check it from RCS.
Unfortunately, the .DEFAULT rule does not interact with implicit rules for compiling `.c' files in the manner that we would desire. We cannot just put dependencies in the form:
foo.o: foo.h main.o: foo.hinto the Makefile and rely on make to know that foo.o depends on foo.c (and main.o on main.c). Make would check out only foo.h and then think it was finished.
[An implicit rule such as
.c.o: $(CC) $(CFLAGS) $(CPPFLAGS) -c $<:only kicks in if the .c file can be found in the current directory.]
Therefore we have to force make to check out foo.cwhen foo.o is the target, and having shown the explicit dependency on foo.c, we have to show the C compilation command as well.