CVS: Version Control Beyond RCS
CVS (Concurrent Versions System) is a version control system. With CVS, developers are able to review the change history of any source file, retrieve any revision of a particular file, avoid overwriting one another's changes, and keep track of releases and the file revisions that go along with them.
First, a short introduction to some of the concepts of CVS. All the sources for a project are stored in a central location called the repository. The sources in the repository are organized into modules. Typically each module represents a separate project. A module can represent any number of files and directories.
When a developer wishes to work with a particular set of sources he uses CVS to retrieve a local copy of them. This local copy can represent the most recent revision of each file or some past revision.
Typical version control systems require a user to “lock” each file he wishes to edit, preventing other developers from editing these files until the first user has committed his changes. This can be quite time consuming as developers either wait for one another to commit changes, or work simultaneously and manually merge the changes together later on. CVS solves this problem by allowing concurrent editing.
Concurrent editing allows two or more developers to work concurrently on the same files. Ordinarily, concurrent editing leads to one developer overwriting another's previously committed changes.
CVS prevents overwrites by forcing a developer to merge into his local copy any changes that have been committed to a file in the repository since he retrieved his local copy of that file. Only then can the developer commit his changes. The process is mostly automatic, but if, for instance, changes have been made to the same line of code in both the repository and the local copy, manual resolution of the conflict is necessary.
Let's look in on our software development team in action. Due to the confidentiality of their project, all the file names have been changed to protect the innocent. Their project is to “build a better mouse trap.”
Fezzik, though primarily a documentation writer, (This explains why all our team's manuals are in rhyme.) does some detecting and squashing of software bugs in the course of his work. Let's watch as Fezzik fixes a critical bug in a previous release of the product.
Version 4.0 of the product was released a few months ago. Now a critical bug has been found by a very important customer. The bug must be fixed in the released version of the software, a point release made for the customer, and the fix must be merged into the working version of the software.
In investigating the problem, Fezzik has determined that the bug was introduced between the 3.3 and 4.0 versions of the software. His first step is to get a copy of the 4.0 sources. He does this with the command:
cvs checkout -r PROD_REL4-0 mousetrap
This will check out all of the directories and source code files for the entire project. The version of each source file will be the version that was included in the release with the name PROD_REL4-0, exclusive of any changes that have been to that source file since the release.
Now Fezzik has a new directory called mousetrap. In this directory are all the project sources as they were when the release 4.0 was made. Because development on the project has continued since release 4.0, Fezzik must start a new branch on the revision tree from this point. He uses the following commands:
cvs tag -b POINT_RELS_REL4-0 cvs update -r POINT_RELS_REL4-0
The revision tree will look like this:
|HEAD - dev for next full release POINT_RELS_REL4-0\ | \ | \| +PROD_REL4-0 | | | +PROD_REL3-3 | |
Fezzik could have created the branch tag before checking out the project, eliminating one step from the method he used. The following sequence would produce the same effect as above:
cvs rtag -b -r PROD_REL4-0 POINT_RELS_REL4-0 mousetrap cvs checkout -r POINT_RELS_REL4-0
CVS has many command and option combinations. Therefore, there is often more than one way to produce the same or similar results. This flexibility allows CVS to be adapted to a wide range of development processes.
Fezzik suspects that the file cheese.c contains the bug, so he decides to view all the changes made to that file between releases 3.3 and 4.0. To do this he uses the diff command:
cvs diff -r PROD_REL3-3 cheese.c
This produces a listing of the differences between the file cheese.c in our current directory and the revision used in PROD_REL3-3. Fezzik could have specified a second revision with a
-r PROD_REL4-0
and had the same effect.
In reviewing the differences, Fezzik finds what he believes to be the bug he is looking for. He would like to clear any modifications he plans with the person who made the change, so he looks at the log file for cheese.c.
cvs log cheese.c
He sees that only Buttercup modified this file between the two releases, so he is able to talk to her about the fix. After making the fix and testing it, Fezzik is ready to commit his changes. He issues the following command from the top directory of the project:
cvs commit -m "Fixed bug #1202"
Fezzik did not specify a specific file to commit, so this command will commit all modified files in this directory and, recursively, in all of its subdirectories. Each file committed will have the message “Fixed bug #1202” added as a log message. Since this fix will be sent to a customer, Fezzik decides to tag the set of files that will be sent:
cvs tag PROD_REL4-0-1
Now Fezzik needs to create the patch file to be sent to the customer:
cvs rdiff -r PROD_REL4-0 -r PROD_REL4-0-1 mousetrap > patch4.0-4.0.1
This creates a Larry Wall format patch file which the customer can feed into the patch program to update his sources. The patches will update the customer's 4.0 sources to the new 4.0.1 sources.
Now Fezzik needs to merge the fix into the current development sources. First he updates his sources to the latest revisions on the main thread by using this command:
cvs update -A
This has the same effect as deleting the local copy of the module, and doing a new checkout of the module, thereby getting the latest revisions.
Then he has CVS automatically merge in the changes from the 4.0.1 point release with:
cvs update -j PROD_REL4-0-1
CVS will automatically merge the changes Fezzik made on the branch into these latest sources. If the merging of the two sets of sources causes conflicts, CVS will announce this. At the conflict points in the file, there will be delimited regions containing the text from both sources. These regions will need to be merged manually.
Once everything is merged, Fezzik can commit all of the changes using another commit command:
cvs commit -m "Merged in fixes in PROD_REL4-0-1"
Now we turn to Westley, who will demonstrate how CVS is used when working on a multiple-person project.
Westley has been off the project for a month [ not too surprising, what with his having been dead and all]. The first thing he needs to do is bring his sources up to date.
cvs update
Since he has not modified anything since his last commit everything simply updates; no conflicts could exist. Now Westley would like to see what has happened since his departure on April 10. He uses the following command to view all the log messages for the commits that were made after April 10th:
cvs log -d>4/10 -b
After reviewing the log file he feels caught up, so he jumps right into modifying sources. During the course of his work he needs to create a new file, happiness.c. After he creates the file happiness.c on the disk, he issues the following command:
cvs add happiness.c
Westley also obsoletes a file, and after deleting the file, he issues the following command:
cvs delete agony.c
Both the add and delete commands will not take full effect until the CVS commit is done.
After a day of work, Westley has added a new feature and is ready to commit his modifications. He issues the following command:
cvs commit -m "Made a number of wonderful improvements"
CVS informs him that someone else has already committed changes to some of the files that he has made changes to, so he must update his files with the other developer's changes before committing his own. A simple call to the update command will do this:
cvs update
After manually editing to resolve any conflicts between his changes and the changes already made to the repository, Westley tests his new feature again. Everything looks okay, so he attempts his commit again:
cvs commit -m "Made a number of wonderful improvements"
This time it works. This method of working is quite an improvement over many systems where a person must first lock a file before it can be edited, and anyone else wanting to edit the file must wait until the first person has committed changes and unlocked the file.
Now Buttercup will demonstrate how to release a new version of the software. She begins by checking out the latest sources.
cvs checkout mousetrap
After verifying that these sources are the exact versions that should be released, she tags the release:
cvs tag PROD_REL4-1
Then she creates a set of directories containing all of the sources to be delivered by using the export command:
cvs export -R PROD_REL4-1 mousetrap
This will create a directory structure filled with only the correct sources and none of the CVS administration directories or files.
The full power of CVS exceeds the scope of this article, but I hope I have provided enough of a taste to entice you to try CVS. We have been using it for 5 months at Lernout & Hauspie, and are more than pleased with its performance.
Per Cederqvist has written an excellent introduction to CVS called Version Management with CVS. It can be found on the WWW by following links on the CVS page at https://www.winternet.com/~zoo/cvs/. This manual will be included with the next version of CVS, version 1.4.
For prior adventures of this software development team, see (or read) William Goldman's The Princess Bride.
Tom Morse (tmorse@lhs.com) has been working in Unix for the past 10 years and is currently employed at Lernout & Hauspie Speech Products. When he is not chained to a computer, he spends his time mountain biking, hiking, and attempting to learn Dutch.