Using CVS

By Mitch Stuart
Copyright © 2002-2003 FullSpan Software  -  Usage subject to license
Document Version: $Revision: 1.3 $, $Date: 2004/01/28 05:40:26 $

CVS is an open source version control system. This document gives some basic information and tips on using CVS, and provides pointers to CVS information and tools.

Contents

1. Documentation

2. CVS Software

3. Tools

3.1. Windows

3.2. Cross Platform

3.3. Web Based

4. Comparison with SourceSafe

4.1. Concurrent Development

4.2.Terminology and Behavior

5. Example Scenario

6. Engineer Usage

6.1. Occasional Tasks

6.2 Regular Tasks

6.3. Other Useful Commands

6.4. Working with Branches

7. Release Manager Usage

7.1. Organization

7.2. Tags and Branches

7.3. Branch and Merge Example

8. Administrator Usage

9. CVS Annoyances

1. Documentation

2. CVS Software

CVS itself (binary and source) is available for a variety of platforms.

The CVSNT project started as a Windows-specific port of CVS but now runs on *nix platforms. Some users seem to prefer this distribution (I have not tried it).

3. Tools

The CVS program has a command-line interface. However, add-on GUI tools are available and are quite popular.

3.1. Windows

WinCVS is a native Windows executable.

If you use WinCVS you will need a good, external, graphical diff program. The one that I use is Araxis Merge, however this costs (as of this writing) about $125 per seat. There are various free diff utilities listed on the WinCVS home page, including WinMerge.

3.2. Cross Platform

TkCVS is a Tcl/Tk GUI for CVS.

TkDiff is a Tcl/Tk GUI diff utility that works well with TkCVS.

3.3. Web Based

ViewCVS runs as a set of Python CGI scripts (under Apache or other web servers). It provides a browsable view of the CVS repository - the history and status of the files, as well as the file content itself.

CVSGraph works well with ViewCVS to provide a graphical tree view of the CVS repository.

4. Comparison with SourceSafe

4.1. Concurrent Development

The default working mode for SourceSafe is that only one user can have a file "checked out" at a time. When that user "checks in" his or her changes, the file becomes available for another user to check out. This can severely hamper productivity in development teams, because of the time delay waiting for a file to become available; the time and effort to coordinate the sequence of checkins and checkouts; and the inevitable need to work on a temporary copy of a file and manually merge the changes later. It is possible to configure SourceSafe to allow multiple checkouts.

In contrast, CVS allows multiple users to edit the same file. The first user to "commit" (check in) the updated file wins - their changes are accepted with no special handling. When the next user attempts to commit the same file, they will be warned that their copy is not up to date and they must update the file from the repository and have CVS merge in their local changes. This is discussed in detail in the Engineer Usage section below.

It is possible (using the CVS "watch" command) to force files to be gotten in read-only mode (I have never used this feature). However, this does not change the ability of any user to commit their changes in any order, so it does not really enforce the one-writer-per-file in the way that SourceSafe does. The CVS watch facility is more of way to keep the team informed of who is doing what, rather than a way to enforce a usage model.

4.2.Terminology and Behavior

SourceSafe and CVS have many similar concepts and features, but the terminology used often differs, and the behavior of similar commands is not always identical. The following table reviews some important features of each system.

SourceSafe CVS Comments
Project,
Create Project
module,
import
In SourceSafe, you create top-level projects in the root ($/) of the SourceSafe tree using the Create Project feature. Under each project you can create subprojects and/or add subdirectories. To add a subproject, you use the Create Project feature and give the subproject a name. To add a subdirectory, you use the Add Files feature. As far as I can tell, each subdirectory is considered a subproject, regardless of the method used to add it.

In CVS, projects are formally known as modules. (However, it is common to use the term project when discussing a CVS module, as I do later in this document.) You create modules with the import command. There is no concept of nested modules or submodules in CVS. The top-level directory of a module directory can, of course, have subdirectories, which can have subdirectories, and so on.

Checkout,
Get Latest Version
Checkout,
Update
In SourceSafe, you use Checkout to get a local writable copy of a file, directory, or project, and Get Latest Version to get a local read-only copy.

In CVS, all the files you retrieve from the repository are writable by default. Use the checkout command to get a local copy of a module into a new directory. Use the update command to update your existing local working directory with the modified or new files from the repository. (I believe that using checkout with an existing local directory is the same as doing an update on that directory, but I have not tried that.)

Add Files add The SourceSafe Add Files feature and the CVS add feature are almost identical in that they allow you to add new files and directories to the repository.

However, the files you add with SourceSafe are immediately available for others to retrieve. With CVS, the add feature only "registers" the files with CVS - they are not actually available in the repository until you commit them (discussed below).

Checkin commit The SourceSafe Checkin and CVS commit features do the same thing: save your local changes into the repository. You can (and usually should) supply a comment describing the changes. Even though the CVS command is called commit, it is common to use the term "checkin" in conversational usage (e.g., "I need to checkin my latest changes to CVS").

5. Example Scenario

In the following sections, we will look at how various participants use CVS: developers, release managers, and the CVS administrator. To have some realistic examples, we'll use the following scenario: A company called FullSpan has two products: a word processor and a spreadsheet. We'll call these products FSWord and FSCalc.

The word processor uses the names of mountains as its release code words: Everest is the upcoming release, and Ranier is the release after that.

6. Engineer Usage

6.1. Occasional Tasks

One time per client environment, you need to install and configure the CVS client software.

When working on a new project or wanting to get a clean slate, use the cvs checkout command to get a local working copy of a module.

6.2 Regular Tasks

For day to day usage you will usually do the following things:

6.3. Other Useful Commands

6.4. Working with Branches

When you check out a working copy from CVS, you can check out from the main line, or from a branch. To check out from a branch, use the -r option:
  cvs checkout -r fsword_everest
You can check out to a named directory; this is useful if you need to work on several releases in parallel:
  cvs checkout -r fsword_everest -d everest
The -r option is "sticky" - after you have checked out a branch, all of your updates and commits (in that working directory and its subdirectories) apply only to that branch.

7. Release Manager Usage

7.1. Organization

The release manager will create modules (projects) using the cvs import command. The internal structure of the module is a set of subdirectories and files. If you have an existing project, you can simply import the directories and files into CVS. However, it is worth spending some time thinking about the organization of the modules. Some ideas on organization: Based on the above considerations, our example company will use this structure:
        common
          doc
          dev
            build
            src
          ext
          qa
        fscalc
          doc
          dev
            build
            src
          ext
          qa
        fsword
          doc
          dev
            build
            src
          ext
          qa
The directories common, fscalc, and fsword are modules within CVS; each can be imported or checked out independently. The directories within the modules can have any organization that makes sense. In this case, we have split each module along organizational lines. There would be further breakdowns in the directories by functional classification. For example, the dev/src directory would have subdirectories for each of the components that make up the product.

7.2. Tags and Branches

Tags and branches use the same facility in CVS; a branch tag is just a special case of a tag. Use the cvs tag command to tag the repository based on the current snapshot in your working directory. Use the cvs rtag command to tag the repository based on the repository contents (often indicated by some attribute such as an existing tag or a date).

The CVS main line or trunk of each module has a special built-in tag called HEAD.

There are various strategies for tags and branches. It is important to have a controlled, documented process. My suggestion is that when development is to begin for a given release, a new branch is created for that release. Development of several releases can occur in parallel, each release on its own branch.

In addition to branches, non-branch tags should be created:

There should be an established tag and branch naming convention. The name should clearly identify the project and the purpose of the tag. Also, because tags and branches are both types of tags, you should implement a naming convention that distinguishes them.

For example, you could have a naming convention like this:

  project_release[_taginfo][_tag]
where project is the name of the project, release is the name of the release, taginfo specifies the purpose of the tag, and tag is an indicator as to whether this is a non-branch tag. Branches do not have the _tag suffix, but non-branch tags do. If taginfo has multiple words, they should be separated with hyphens instead of underscores, to make the components of the tag name clear (e.g., use fsword_everest-sp1_ga_tag instead of fsword_everest_sp1_ga_tag).

The following table illustrates my recommended tag and branch strategy and naming convention.

Tag Format and
  Tag Example(s)
Comments
project_release
  fsword_everest
This is the branch tag for the everest release of the fsword product. Notice that it does not have the _tag suffix, which tells us that this is a branch tag.
project_release_start_tag
  fsword_everest_start_tag
This tag is created just after the branch tag, before anything is added or changed in the branch.
project_release_ident_tag
  fsword_everest_build23_tag
  fsword_everest_beta1rc4_tag
  fsword_everest_rc3_tag
  fsword_everest_ga_tag
These tags mark various milestones that may be useful build or merge points. These could be tagged with a build number (e.g., build23), a release candidate number (e.g., beta1rc4, rc3), or a status (e.g., ga, for "general availability" to identify the final product that is shipped to customers).
project_release_mergeto_otherreleaseident_seq_tag
  fsword_everest-sp2_mergeto_ranier_3_tag

project_release_pre_mergefrom_otherreleaseident_seq_tag
  fsword_ranier_pre_mergefrom_everest-sp2_3_tag

project_release_post_mergefrom_otherreleaseident_seq_tag
  fsword_ranier_post_mergefrom_everest-sp2_3_tag
These tags mark the merge points between branches (merging is discussed in the following section). In this case, we are merging from everest SP2 to ranier, and this is the third merge we have done between these two branches (identified by the sequence number 3).

7.3. Branch and Merge Example

In this extended example we will run through a few release cycles - a major release, a patch, and a service pack. Let's assume that CVS has been installed and configured and we want to start using it with the code base for the existing fsword word processor project.

Create CVS project. First we will create the CVS module (project) fsword.

cd /somepath/fsword
cvs import -m "import of project fsword" fsword vstart start
Note that you are actually in the fsword directory (not above it) when you create the module. The -m option gives a message (comment) for the operation; the message is visible in the CVS history log. The next parameter, fsword, is the name of the project. Choose this carefully; this is the name that engineers will use to check out the project. (If you later want to change or abbreviate the name, CVS allows you to create an alias.)

The vstart and start parameters are not important for our purposes (see the Fogel book for details).

Create fsword branch. We are about to begin development on the everest release of fsword. As discussed above, I think it is beneficial to tag the project before development begins. This allows engineers to refer to the same branch tag throughout the development cycle. If we began development on the main line, and then branched later, engineers would have to reset their workspaces to point to the branch. (Not a big deal, but just an annoyance that is easily avoided.)

cvs rtag -r HEAD fsword_pre_branch_everest_tag
cvs rtag -r HEAD -b fsword_everest
cvs rtag -r fsword_everest fsword_everest_start_tag

The rtag command adds a tag to the repository based on the contents of the repository itself (as opposed to the tag command, which tags the repository based on the contents of the local workspace). We are using rtag here because: (a) we have not yet run checkout on the fsword project, so we have no local CVS workspace with which to use the tag command, and (b) I prefer to use rtag whenever possible, since it is a server-side-only operation.

The -b option says to create a branch tag; remember, branch tags are just a special type of tag. As per our naming convention, we do not use the suffix "_tag" for this tag, because it is a branch tag.

fsword_everest is the name of the branch (recall that this is a branch of the fsword project).

Check out the everest branch.

cd ..
mv fsword fsword-before-cvs
cvs checkout -r fsword_everest fsword
This sequence saves the old fsword directory (the source of the import) under a new name - we need to move it out of the way so we can get the directory fresh from CVS.

Then the cvs checkout command retrieves the fsword project from CVS to our local disk, into a directory called fsword. (You can retrieve projects into other directories with the -d option on checkout. This can come in handy if you need to have several parallel versions of the same project in your workspace.)

The -r fsword_everest option tells CVS to check out the fsword_everest branch. If you leave out the -r option, CVS would retrieve the main line (trunk) version. In our current state, this would be the same thing, since we have not made any changes since importing the project. But the important thing is that the -r tag is sticky, meaning that any changes we make to our local workspace, and any CVS commands that we run in this workspace, apply to (and only to) the branch that we are working on.

Engineers can now check out the fsword project with the same command (cvs checkout -r fsword_everest fsword) shown above, and proceed with their work. When they commit their changes, those changes will be committed to the fsword_everest branch only.

Create fsword-patch1 branch. Let's say a customer patch is needed on the original fsword product. We don't want to use the fsword_everest branch to create this because there are many other changes for the everest release that we don't want to be part of the patch. So we create a branch to work on those changes.

cvs rtag -r HEAD fsword_pre_branch_patch1_tag
cvs rtag -r HEAD -b fsword-patch1
cvs rtag -r fsword-patch1 fsword-patch1_start_tag  

cvs checkout -d fsword-patch1 -r fsword-patch1 fsword
This above commands create and check out the fsword-patch1 branch. The -r option tells CVS to give us the fsword-patch1 branch of the fsword project. The -d option tells CVS to put the files in a directory called fsword-patch1 (instead of the default directory for the project, fsword).

Development and testing of the patch is completed, and the files are all commited to CVS in the fsword-patch1 branch.

Merge fsword-patch1 branch to everest. This sequence merges the patch1 branch changes into the everest branch:

cvs rtag -r fsword-patch1 fsword-patch1_mergeto_everest_1_tag
cvs checkout -r fsword_everest fsword
cd fsword
cvs tag fsword_everest_pre_mergefrom_patch1_1_tag
cvs update -j fsword-patch1_mergeto_everest_1_tag

[resolve any conflicts, build, test, fix]

cvs commit -m "Merging patch1 into everest"
cvs tag fsword_everest_post_mergefrom_patch1_1_tag

The rtag command tags the patch1 branch so we know what changes have been merged to everest in this merge (merge sequence number 1, since this is the first merge from patch1 to everest).

The checkout command gets a clean copy of everest to our workspace. This command can be skipped if you already have a copy of everest in your workspace.

The first tag command tags the everest branch before the merge.

The update command actually does the merge. It will notify you of any conflicts. After this you need to resolve merge conflicts (if any), build and test.

The commit command puts the merged changes into the repository on the everest branch.

The final tag command tags the everest branch after the merge.

Second merge of fsword-patch1 to everest. Oops! It turns out that patch1 was not really ready to ship after all. Some problems were found and additional changes were committed. This sequence shows how to get those latest changes from the patch1 branch into the everest branch.

cvs rtag -r fsword-patch1 fsword-patch1_mergeto_everest_2_tag
cvs checkout -r fsword_everest fsword
cd fsword
cvs tag fsword_everest_pre_mergefrom_patch1_2_tag
cvs update -j fsword-patch1_mergeto_everest_1_tag -j fsword-patch1_mergeto_everest_2_tag

[resolve any conflicts, build, test, fix]

cvs commit -m "Merging patch1 into everest (2nd merge)"
cvs tag fsword_everest_post_mergefrom_patch1_2_tag

The commands are almost identical to the previous set (except for using merge sequence number 2 instead of 1), so I will not review them again.

However, notice the update command. It has two -j options this time. This tells CVS to merge only the changes between fsword-patch1_mergeto_everest_1_tag and fsword-patch1_mergeto_everest_2_tag into our local workspace. If we specified only one -j option (-j fsword-patch1_mergeto_everest_2_tag), CVS would attempt to merge all of the changes from the beginning of the patch1 branch to our local copy. But we have already merged in all the changes up to fsword-patch1_mergeto_everest_1_tag, so we don't want CVS to re-merge those changes again.

From this example, the benefit of rigorously tagging merge points should be clear.

Create everest-sp1 branch. The everest release has not shipped yet, but we want to start parallel development on the first service pack. So we create an everest-sp1 branch from the everest branch (a nested branch).

cvs rtag -r fsword_everest fsword_pre_branch_everest-sp1_tag
cvs rtag -r fsword_everest -b fsword_everest-sp1
cvs rtag -r fsword_everest-sp1 fsword_everest-sp1_start_tag

Engineers can now begin using the everest-sp1 branch.

Create ranier branch.We still have not shipped the everest product (what's up with that!?), but we've been lucky to hire some additional developers so we are going to start parallel development of the next major fsword release, code-named ranier. We need to create a branch for the ranier development.

We could branch off the everest branch, like we did for Service Pack 1. But if we keep branching like this, we will never get back to the main line, and the CVS branch tree will grow in a very narrow and deep way, which makes it awkward to work with.

Therefore, we want to create the ranier branch from the main line, to keep the CVS tree reasonably flat. Before we create the ranier branch, we need to refresh the main line with the current code.

cvs rtag -r fsword_everest fsword_everest_mergeto_head_1_tag
cvs checkout -r HEAD fsword
cvs tag fsword_head_pre_mergefrom_everest_1_tag
cvs update -j fsword_everest_mergeto_head_1_tag

[resolve any conflicts, build, test, fix]

cvs commit -m "Merging everest into main line"
cvs tag fsword_head_post_mergefrom_everest_1_tag 

cvs rtag -r HEAD fsword_pre_branch_ranier_tag
cvs rtag -r HEAD -b fsword_ranier
cvs rtag -r fsword_ranier fsword_ranier_start_tag

Notice that in the abvove example, we are using the everest branch as the baseline for the ranier branch. We could have instead used everest-sp1 branch. We just need to pick which one best accomodates our needs. For example, if the everest-sp1 branch is very unstable right now, we would want to use the everest branch, so the ranier developers are not stuck with all the same bugs as the everest-sp1 developers are fighting.

In either case, assume that changes will continue to both the everest and everest-sp1 branches. All of those changes will eventually need to be merged into ranier. Ideally this will be done by merging from everest to everest-sp1, and then from everest-sp1 to ranier. It could also be done by merging separately from everest to ranier, and from everest-sp1 to ranier. Because we are using tags to keep track of every branch and merge point, there are many options for merging. However, it seems like a good idea to do serial merges (as opposed to parallel merges) whenever possible. In other words, we would rather merge from A to B and then B to C, rather than from A to B and from A to C - it is likely that there are other changes in B so we will need to merge from B to C anyway, and that may cause conflicts with our direct A to C merge.

Release everest. Finally it is time to release everest. We need to go through a release cycle, tagging release candidates (or build numbers, however you want to do it), and finally tagging the "final" GA (general availability) or FCS (first customer ship) version.

cvs tag fsword_everest_rc1_tag

[test, fix]

cvs tag fsword_everest_rc2_tag

[test, fix]...

cvs tag fsword_everest_rcN_tag

[test - OK, ready to ship]

cvs tag fsword_everest_ga_tag

Now we need to merge the final everest changes into everest-sp1, and then everest-sp1 into ranier. It is not urgent that this be done on the same day as the final release is tagged. It can wait a few days or a couple of weeks until the branch being merged into is in a stable enough state to accept the merges. But don't wait too long, because that will increase the chance of conflits.

Normally you would now merge everest back to the main line, and then branch off for the next release. However, in this example we have already created the ranier branch, so it is not strictly necessary to merge everest into the main line - as long as everest is merged into everest-sp1 and ranier, everything is OK. When ranier is released (or when ranier is branched for the next main release, whichever comes first), ranier will be merged to the main line.

Release everest-sp1. Now we are ready to release everest-sp1. We go through a similar testing and release candidate cycle as for everest (perhaps with fewer candidates - since this is only a service pack there should be a smaller scope of changes, and hoppefully a smaller number of bugs). Then we merge from everest-sp1 into the next main release (ranier in this case).

What if we need a patch for Service Pack 1? We would create an everest-sp1-patch1 branch from everest-sp1.

What if we need a Service Pack 2 for everest? We would create an everest-sp2 branch from the everest branch. If there are any changes in everest-sp1 or any everest-sp1-patchN branches that have not yet been merged into everest, those would need to either be merged into everest before the everest-sp2 branch is created, or separately merged into the everest-sp2 branch.

8. Administrator Usage

One time per server environment, the administrator will need to install the CVS server. Also, you will want to enable one or more of the remote access methods (unless all users will be located on the same server as the repository). The most popular method seems to be the pserver; this is desribed well in the documentation, but I ran into a few issues that are described in my brief writeup.

Depending on the access method and configuration you choose, there may be some administrative work involved in issuing CVS userids and passwords.

After installing CVS, the administrator will initialize the repository and set the permissions. It is then ready for usage by release managers and engineers.

9. CVS Annoyances