Using CVS on the gloworm cluster

Contents:

Other resources:

What is CVS?

CVS stands for Concurrent Versions System. It provides a reasonably robust environment for shared development of software modules (a hierarchical collection of files) by handling the merging of individual files that make up software modules

CVS keeps a single copy of the master source modules. This copy is kept in a repository, and it contains all of the information to extract current and past software releases.

Each user has a private copy of one or more of the source modules. Initially, the user would checkout his copy of the module. Once the private copy exists, the user may add or remove files and directories to his private copy, make changes to his copy and commit them to the repository, and update his private copy to incorporate all changes committed to the repository since the last checkout, update, or commit operation.

Recommended umask

Access control for CVS is based on the standard Unix user and group permission scheme. Since the repositories are intended to be shared, it is important to allow for group write permission. The default umask (002) is the only reasonable umask to have when creating any files that will be added to the repository or when running the cvs command. If you munge the permissions on the files in a shared repository, you will likely inconvenience other users.

Please do not change your default umask from 002 if you plan to use CVS.

If you want to restrict access to other non-CVS'd stuff in your account, create a ~/.private directory, set the permissions on that directory to be restrictive (e.g. 700), and put the stuff that you want to be restricted below that directory.

The CVSUMASK environment variable is globally set on the gloworm cluster. It will ensure that new directories in the repository are created with shareable permissions.

First-time setup

If you are creating a new repository, you should initialize it via
    cvs -d /path/to/repository init

You should check group ownership and permissions in a newly created repository, especially if you intend it to be shared. The permissions should permit group write and the set-gid bit should be set on all directories.

It is also recommended that you create a ~/.cvsrc file containing the following:

update -d -P
This automatically adds the -d (create new directories) and -P (prune empty directories) to the options for an update. My ~/.cvsrc contains:
update -d -P
diff -c
cvs -q
which also produces context diffs (instead of just displaying the changed lines) and causes cvs to be quiet (i.e. printing of directories and other informational diagnostics is suppressed).

Checking out a module

To retrieve a module from the repository for the first time, you will need to check it out. Use -d <repository-location> to specify the location of the repository. For example, to check out a copy of Alamode,
    cvs -d /home/supOO7/cvsroot checkout Alamode
The location of the repository will be saved in your checked out copy, so you never have to specify it again.

Note that cvs supports a remote access mechanism. Assuming that you can rsh (using stock Berkeley rsh, a kerberized rsh, or a ssh client) to the remote machine, you can specify and use a remote repository via

    cvs -d :ext:yergeau@gloworm:/home/supOO7/cvsroot checkout Alamode
Remote repository have slightly different semantics during commits. Rather than processing files for each directory, all modified files under the current directory and subdirectories will be committed in a single step. You will only have the opportunity to add one commit comment.

Updating your copy

    cvs update
Since the repository is shared, other users may make changes to the code in the repository. Provided that you've been put on the appropriate mailing list, you will be informed of every commit to a module. The update to your copy is based on differences between the version you previously checked out or updated and the current version in the repository. These differences are merged with your copy. Although the merge algorithms are quite advanced, it is still possible to get conflicts during a merge. If you get conflicts, you will have to hand edit the file(s) with the conflicts and decide what the update/merge should have done.

Because of the possibility of conflicts, it is best to update your copy with a few days after changes were made in the repository instead of waiting for several generations of diffs to stack up. The conflict avoidance algorithms work best with only one delta to apply.

Carefully watch the output when doing an update. It shows files that are being updated and the status of files in your private copy. The status indicators are the characters `U P A R M C ?':

It is best to update within the next few days of any changes. CVS does the best jobs of merging changes from the repository when your private copy is nearly in sync with repository version.

The `-n' flag can be used to fake an update (i.e. "cvs -n update"). CVS will show the status of the files, but will not change the local copy.

Committing your changes

In order to share your changes with others, you need to commit them to the repository. It is up to you to decide when it is appropriate to commit your changes, but it is generally a good idea to commit whenever Since other users of the repository will likely be affected by your commits, please make every effort to not break the code in the repository. Before you commit any changes, you must update your copy and resolve any conflicts, if needed. After updating, it is strongly recommended to recompile and test to make sure that the combination of the repository changes and your local changes hasn't broken anything. If anything is broken, fix it before committing. Make sure that you commit everything that is needed (i.e. do not checkpoint only one file if that file needs a change in a header file that you haven't committed).
    cvs commit
commits all changes to the local copy in the current directory and in any directories below it.
    cvs commit file
only commits an individual file or directory. Either form will start an editor in each directory so that a log message can be entered.

It is often useful do check the diffs before a commit so you can jog your memory and provide more meaningful comments for the log entry.

Even if you think you've only changed one file, please do an update or a diff on the whole module to make sure.

Adding files and directories

    cvs add [file | directory]
will schedule a file to be added to the repository on the next commit.

Removing files and directories

Deleting a file, then running
    cvs delete [files....]
will schedule a file to be removed from the repository on the next commit. The file isn't actually deleted permanently. It is just stored in an attic in the repository.

Generating diffs

   cvs diff [files...]
will generate a diff of the changes you've made in your local copy. It does not show changes that will occur during an update.
    cvs diff [-r rev1 | -D date1] [-r rev2 | -D date2]
shows the differences between two specific revisions in the repository. See the cvs(1) manpage for information on revision and/or date specification.

As with the normal Unix diff command, `-c' can be added to generate a context diff.

Viewing the commit log information

   cvs log [files...]
shows the log comments entered for each commit

Email notification of commits

Grab a copy of log.pl from /home/supOO7/cvsroot/CVSROOT and put (copy) it in your $CVSROOT/CVSROOT directory.

Then,

   cvs checkout CVSROOT
Edit the loginfo file in the copy of CVSROOT, and add lines similar to
Alamode		$CVSROOT/CVSROOT/log.pl %s -f $CVSROOT/CVSROOT/commitlog -m alamode-cvs@gloworm
for each module. The above example will send email notification to alamode-cvs@gloworm whenever the module Alamode is cvs commit'd. If you want a system mail alias set up, contact action@gloworm. The list of address for the system mail alias will be maintained by you.

Version identification in files

CVS is based on RCS, and RCS keyword strings can be used for version identification, log comment inclusion, etc. Two common keywords that you may want to add to CVS'ed files are $Id$ and $Log$. See the manpage on the RCS ident(1) command for complete information.

Importing a source module

The easiest way bring a clean source tree into the repository is to import it. This is equivalent to adding all files/directories and committing down the directory tree. The general form is:
   cvs import modulename vendortag releasetag
A checkout will create the modulename directory, and that directory will contain everything below the directory you were in when you imported the source. The vendortag and releasetag are any meaningful (or meaningless, whatever the case may be) strings.

Branching

Branching and merging in CVS is actually pretty easy. Unfortunately, the documentation on the topic is somewhat confusing. Hopefully, this digested/simplied discussion won't be quite so confusing.

With CVS, branches are referred to using a symbolic name called a branch "tag". Users can create new branch tags and refer to a branch tag in a checkout or update operation. There was a default branch tag ("HEAD") created when the code was imported. That tag refers to the branch used as the default branch for a checkout.

Most of the philosopy (or "conventional wisdom") surrounding the practical examples of branching that I've seen is that the mainline development branch (aka the "HEAD" branch) is the most active development line. Branches are used to

The big unanswered issue is deciding when and why to create a new branch. You don't want too many active development branches at any given time. You don't want too many branches, period.

The following discussion considers branches for entire modules. You can create/use branch tags with individual files, but I don't recommend it. CVS works best on a per-module, rather than a per-file basis.

Now onto the mechanics...

To create and use a new branch you need to

  1. create a new branch tag

    In your mainline current working copy of Prophet, run

            cvs tag -b <TAG_NAME>
    
    (replace <TAG_NAME> with something meaningful and valid for a branch tag name, e.g. PRE_featureX).

    Note that this does not affect your mainline current working copy of Prophet; it only affects the repository. Your mainline current working copy continues to follow the default ("HEAD") branch.

  2. checkout a copy of of that branch

    In a clean directory (not over your mainline working copy), run

        cvs -d /home/prophet/cvsroot co -r <TAG_NAME> Prophet
    
    Note that the state of this checked out copy will be the state of the branch in the repository. If this is a new branch, then it will look identical to the copy of the mainline ("HEAD") branch in the repository.

At this point, you can make changes, add/delete files, commit, etc. in either the mainline ("HEAD") branch or another branch. The committed changes will not appear the other branch. I.e. commits in one branch will not be update'd into another branch.

All commits to every branch of a module will continue to be broadcast to the mailing list, regardless of what branch they affect. I may be able to further filter some of the non-default branch commits to a limited mailing list (or just ignore mailing them).

At some point, you will want to merge changes into another branch. With cvs, this is done with a "join" update followed by a commit. For example, to merge committed changes from a "PRE_featureX" branch into the mainline, you would cd into your mainline copy, then

  mainline/Module/src% cvs update -j PRE_featureX
Assuming that there were no conflicts in the merge ("join update"), you would then compile/test Prophet. If the testing is successful, then you could commit the merged changes (into the mainline branch). To merge changes from the mainline branch into another branch, just do a join update from the "HEAD" branch.
    featureX/Module/src% cvs update -j HEAD
As always, the repository version of the branch is not changed until you commit.

Watches and editing

Some modules (e.g. Prophet) or files may have a "watched" attribute. This attribute permits other users of the repository to see what files are being worked on by other users. This attribute also causes files to be checked out with read-only permissions. In order to edit a file in your private copy, you need to
    cvs edit <file>
(or use the emacs function vc-toggle-read-only which is bound to C-x C-q)

To "unedit" a file, I recommend deleting the file and updating instead of the "cvs unedit" command. The unedit command has been known to move an older version into place, retaining the modification date of the older version. Since the modification date is old, make may not properly rebuild/replace the object, if needed.

To view who is editing files

    cvs editors [<file>]