Getting started with CVS
CVS is an open source version control system that has been around since the 1980s, and is still used by many organizations today. It uses a client-server architecture, with files stored on the server in the form of RCS files (RCS is the "even more archaic than CVS" version control system on which CVS is built).
Before you do anything in CVS, you need to tell it where to find the CVS server. You usually do this by setting an environment variable called CVSROOT
:
$ export CVSROOT=:pserver:john@cvs.mycompany.com:/usr/local/cvs
Next, you need to logon to the server:
$ cvs login
(Logging in to john@cvs.mycompany.com)
CVS password:
CVS uses a fairly simple (and somewhat limited) authentication system, based on the Unix user accounts and groups defined on the server. To add your project to the CVS repository, you use the cvs import
command:
$ cvs import javapetstore START-REF START -m "Initial import"
Handling binary files
The standard cvs import
is simple enough, but it usually won't be sufficient for any non-trivial contemporary Web projects. The major complication with CVS is that it handles binary files poorly. CVS is fundamentally designed to cater to text files, and by default it assumes that anything you add is a text file. If you don't tell it otherwise, CVS will assume that your JARs and GIFs are text files, and it may even try to merge them! Needless to say this can lead to data corruption. The safest thing to do is import an empty directory structure, and then add the files afterward. You have to be sure to let CVS know when you are importing binary files, using the -kb
option shown here:
$ cvs add -kb logo.gif
Note that if you're truly committed, it is possible to configure CVS to recognize binary files based on file name, by modifying the server configuration, but the process is a little too complex to go into here.
Rudiments of version control
After placing your project in CVS you need to check out a working copy. This is the copy of the project that you will use in your day-to-day development -- once you have imported the original directory into CVS you can discard it.
Listing 1. Checking out a local copy of javapetstore
$ cvs checkout javapowerstore
cvs checkout: Updating javapetstore
U javapetstore/3RD-PARTY-LICENSE.txt
U javapetstore/bp-project.xml
U javapetstore/build.xml
U javapetstore/index.html
cvs checkout: Updating javapetstore/bp-project
U javapetstore/bp-project/app-client-ant.xml
U javapetstore/bp-project/app-server-ant.xml
...
Once you have your local copy, you can work more or less normally. You can use commands like cvs add
and cvs rm
to add and remove files from the repository. Here's a typical cvs commit
:
$ cvs commit -m "Fixed bug #123"
Before committing your changes, however, you generally update your local copy to download any recent changes. If another developer has changed one of the files you have modified, CVS will attempt to merge the changes. If it can't manage a merge, it will let you know in no uncertain terms, as shown here:
Listing 2. Updating javapetstore in CVS -- conflicts found
$ cvs update
cvs update: Updating .
RCS file: /usr/local/cvs/javapetstore/index.html,v
retrieving revision 1.2
retrieving revision 1.3
Merging differences between 1.2 and 1.3 into index.html
rcsmerge: warning: conflicts during merge
cvs update: conflicts found in index.html
C index.html
cvs update: Updating bp-project
When you edit the file in question, the conflicting lines are indicated as follows:
Listing 3. CVS reports an error
<<<<<<< index.html
p.copy {text-align: right}
=======
p.copy {text-align: center}
>>>>>>> 1.3
Once you correct the conflicting lines, you can commit your changes normally.
Tags and branches
In version control systems, tags are used to identify particular versions of your application. In CVS, you can tag a set of files using the cvs tag
command, as shown here:
Listing 4. Tagging in action, via the cvs tag command
$ cvs tag release-1-0
cvs tag: Tagging .
T 3RD-PARTY-LICENSE.txt
T bp-project.xml
T build.xml
T index.html
cvs tag: Tagging bp-project
T bp-project/app-client-ant.xml
T bp-project/app-server-ant.xml
T bp-project/app-server.properties
T bp-project/build.properties
...
You use a similar approach to create a new development branch. Branches are used to allow work to continue on one stream of development (such as a new release) without affecting work on another stream (such as an existing production release).
Listing 5. Branching in CVS
e$ cvs tag -b release-1-0-patches
cvs tag: Tagging .
T 3RD-PARTY-LICENSE.txt
T bp-project.xml
T build.xml
T index.html
cvs tag: Tagging bp-project
T bp-project/app-client-ant.xml
...
Architectural issues with CVS
An overview of CVS wouldn't be complete without mentioning some of the downsides of using it. Here's a short list most developers would agree to:
- Slow tagging and branching
- Difficulty renaming or moving directories
- Lack of atomic commits
- Poor support for binary file formats
Unlike most modern version control systems, CVS does not support the notion of atomic commits. In a version control system that supports atomic commits, modified files are submitted to the repository not individually, but as a group (also known as a "change set"). If the changes cannot be correctly merged into the current source code in the repository, or if the update fails at some point (for example, if there is a network failure), then the commit is aborted and no modifications are made to the repository. This ensures that the source code in the repository is always in a coherent state.
When you commit a set of changes in CVS, you have no guarantee that all of your changes will be correctly incorporated into the repository. If a commit fails for some reason, the source code on the repository may well be in an unstable state. This is a major architectural flaw.
Many developers also balk at using CVS tags and branches for large-scale development projects. Updating each file one-by-one can be a time-consuming process. For example, if your project contains 1,000 files, CVS will go through your project and individually tag each one of them, which requires a separate network transaction for each tagged file. For a very large project, this process can take hours.
Like COBOL, CVS is a legacy system that is still widely in use. Nonetheless, CVS's old-fashioned architecture makes it a poor solution for modern development processes, where agility and flexibility are of the essence. Most developers today, given a choice, would go with a newer solution like Subversion, Bazaar or Mercurial.