CS 350. Operating Systems

Using CVS

This is a slightly modified version (localized for CS 350 at UW) of Matt Welsh's
Using CVS at Harvard.

Introduction

What is CVS?

CVS, the "Concurrent Versions System", is a source code management system (also called a version control system). It is a tool that allows a group of programmers to work together on the same collection of source files without getting in each others' way any more than necessary. It also keeps track of the change history of the collection and makes it possible to examine the way things looked at arbitrary times in the past. And finally, by keeping track of what is and is not part of a project, it helps programmers maintain the organization of large projects.

Why source management?

In most coursework in computer science, each assignment is a distinct unit: you sit down and code something up, you hand it in, it gets graded, and you thenceforth forget about it or even throw it away. In this environment, a source management system is not really necessary and may, if imposed by course staff, seem like a waste of time.

In the real world, however, programs are large and expensive to develop, and so their life-cycles are measured in years or sometimes decades. Over these time scales, and with such large amounts of code, just keeping track of everything becomes a major problem.

Worse, in the real world, programs have users, who are not part of the development team and not (generally) interested in internal details of the program. Usually, someone insists that now and then a new version be made available to the users. The development team has to be able to issue these releases, and then also has to be able, for years afterwards, to handle bug reports and sometimes issue fixes. In this environment it is imperative to be able to go to some central place and get a copy of the precise release you need.

And finally, when you have a number of programmers working on the same program at once, it's essential that some mechanism be put in place to allow them to coordinate their work. Otherwise, each programmer's version slowly diverges from the others, and eventually everyone has a private version different from everyone else's... and none of them work. Once this happens, it takes an immense amount of effort to straighten out the mess.

Source management (or version control) systems are designed to help programming teams handle these issues.

In CS350, the probable lifetime of your project is a few months, not a few years, and most likely no more than two people will be working on it at once. Furthermore, the OS/161 source you will be working with is several orders of magnitude smaller than a large real-world project. Nonetheless, it is large enough, the time is long enough, and there are enough people involved that failure to use some kind of source management system would be an act of reckless folly.

Why CVS?

We require the use of CVS in CS350 because it is reasonably powerful, freely (and widely) available, and commonly used. Most large open-source projects, and many proprietary ones as well, are managed using CVS.

If you are familiar with another source management system, CVS should be easy to understand and use.

Remainder of This Document

The rest of this handout is divided into two main sections. The first explains the philosophy of CVS, its operating model, and the assumptions behind the way it works. The rest explains, in terms of how one actually uses CVS rather than its various commands, a number of basic and not-so-basic CVS operations. A small additional section lists the main CVS commands.

You do not need to remember everything in this handout. In fact, this handout was written mostly so you do not need to remember all this.

The World According to CVS

Working Model

The CVS model assumes that there is one central official master copy of everything. It is called "the CVS repository." This copy is managed by CVS and is not meant to be touched directly.

Instead, when you wish to work on a CVS-managed project, you "check out" your own private copy. Since work on a program is an ongoing process, and other people may be working on the same program at the same time, you generally want changes in the official master copy to appear in your private copy. To facilitate this, the private copy is connected back to the master copy. Some people think of this as a "subscription" -the private copy is signed up to receive copies of updates that are made to the master copy.

Note, however, that these updates do not happen automatically. You must explicitly update your private copy when the master copy changes.

When you transfer ("commit") changes in your private copy to the master copy, the master copy is updated and other developers will then see those changes when they update. Changes you make in your private copy that you do not commit do not officially exist and will not be seen by anyone else until you commit them.

You can have as many working trees (private copies connected to the master copy) as you want. Often you will have only one, but circumstances may arise in which it is more convenient to have two, or to make temporary ones, or whatever. Just remember that they are all independent: while they're all connected back to the master copy, they are not connected to each other except through the master copy.

Repository

The official master copy is known as the "CVS repository". It is a directory tree that must be kept in a place where all participating programmers can access it. (CVS has various remote access features which will be described briefly later on.) CVS uses the environment variable $CVSROOT to locate the repository. Always be sure that this variable is set and that it points to the correct CVS repository before invoking CVS.

The CVS repository contains one CVS file for each file in your project, laid out in a directory tree that mirrors your project organization. These files have a ",v" suffix and contain control information and version history as well as the latest version of each file. (While in emergencies these files can be edited by hand, doing so is strongly discouraged.)

The CVS repository also contains a directory CVSROOT that holds various CVS configuration data. When you create a repository, you get default versions of all these files that include comments briefly explaining the purpose and syntax of each. If you want to edit these files, check out "CVSROOT" as if it were another project, edit the files, and commit them. See below for a couple of simple examples.

Note: do not confuse the CVSROOT directory with the $CVSROOT environment variable. The CVSROOT directory is really $CVSROOT/CVSROOT.

Back up your CVS repository occasionally, just in case. If you lose your working tree, you should only lose a small amount of work; but if you lose your CVS repository, you lose everything.

Merging

Unlike many (most?) source control systems, CVS does not lock files for modification. Instead, CVS works from a model where everybody edits freely and changes to the same file are merged. CVS does not support locks at all.

In the merge-based model, anyone can edit any file at any time. This is both an advantage and a disadvantage: if two people have small unrelated changes to make in the same file, they can do so without any difficulty. On the other hand, if two people make sweeping changes to the same file at once, the resulting merge becomes a nightmare.

(Another major advantage of merging is that when there are no locks, nobody can hold up development by leaving on vacation while holding a lock on a critical file.)

The way CVS merging works is as follows: when you check out a working tree, CVS remembers what version of each file you got. When you update your working tree, it updates these versions. When you go to commit, if the version your changes are based on is not the latest one, CVS aborts and tells you to update first.

Then, when you update, CVS notices that you have changed your copy and the master copy has changed as well. It then tries to merge the two sets of changes. If the changes are to unrelated areas of the file, this usually succeeds. If the changes overlap, or the merge program gets confused, CVS will say "conflicts during merge". The resulting file will contain blocks that look like this:

  int foo(void) {
<<<<<<< foo.c
    bar();
=======
    baz()
>>>>>>> 1.2
}

This means that your copy of foo.c changed function foo to call bar, but that the official master copy, in version 1.2, changed foo in the same place so it calls baz.

When you get merge conflicts like this, you need to resolve them before committing your new versions. You might pick your version, or the latest version from the repository, or some combination of the two, or whatever. When doing this, it's up to you to make sure you do the right thing.

Some notes:

  1. Even when there are conflicts, the conflict blocks do not necessarily reflect all the changes associated with the merge. Some may have merged successfully. If in doubt, look at diffs.
  2. It is also not always the case that everything that may be involved in resolving a conflict correctly is contained within the conflict block delimiters. The merge program is only a program, not an omnipotent human being.
  3. While the merge system is reasonably robust, once in a while it makes a mistake, particularly if some but not all of the changes merged. It's prudent to look at diffs after an automatic merge, just in case.
  4. Merging is painful. Merging a big change is a lot more painful than merging the same amount of change a bit at a time. Update early and often. Commit early and often.

If you are planning to make huge changes to a file, like reordering all the functions or moving large blocks of code into if clauses (which changes the indent, making CVS think everything changed), it's a good idea to coordinate manually with anyone else who might have pending changes to the file.

Log Messages

When you commit changes to the CVS repository, CVS gives you the opportunity to provide a message explaining the change. These messages get saved in the CVS file and can be reviewed later using cvs log. This can be quite useful when trying to reconstruct the thinking that led to some piece of code you wrote months previously.

These messages can also be logged centrally or mailed out to the people working on the project. We recommend that you set up your CVS repository to mail commit messages to you and your partner. (See below.) While the volume of mail thus generated can be irritating, there's no better way to stay in touch with what's going on.

The commit message should thus describe (briefly) what you did and why. There's no need to report the exact changes, as they can be retrieved using cvs diff.

When to commit

The general rule for commits is that any change should be committed as soon as you're reasonably certain that it's correct and appropriate in the long term, subject to the proviso that committing many small changes in quick succession will probably annoy everyone working with you.

Remember that your partner won't see anything you don't commit, so get bug fixes in quickly and hold back a little on new features that might still have problems.

Ideally you and your partner should keep track of which tests you expect to work at any particular time, and before committing check to make sure that they all still do work.

In most cases, one should try to avoid committing changes that cause the program to stop working properly (or, even, stop compiling at all.) This rule can sometimes be profitably bent when you know your partner will not be affected by the errors introduced.

Tags

CVS inherited part of its view of the world from an earlier toolset known as "RCS", mentioned briefly below. One of the consequences of this is that every file has its own private version number. Thus, while it is possible to do things like look at version 1.5 of every file, it is not very useful to do so.

Instead, CVS supports a concept known as a "tag". A tag is a symbolic name (like asst1-debugged) that you attach to a particular version of some set of files. You can then refer to that version of those files with the name.

A tag is normally used to identify a single consistent version of an entire project. For instance, you may wish to create one tage before starting an assignment and another tag after completing the assignment. These tags then identify the versions of all your files that were current before and after you did the assignment. This lets you, for example, ask CVS to show you all the changes in the entire system between those two points.

Tags are more or less arbitrary alphanumeric text, but, to disambiguate them from file version numbers, they may not begin with digits or contain dots. Since tags are frequently used for program version numbers, and version numbers regularly contain dots, it is conventional to use an underscore (`_') in place of each dot.

See below for specific directions for manipulating tags with CVS.

Branches

Sometimes you might have more than one "line of development" in your program. For instance, when you ship release 1.0 to customers, you might have one team working on release 2.0, and another team making minor bug fixes to the release 1.0 code for release 1.01.

In this case, most changes made for release 2.0 should not be incorporated into release 1.01, and while many fixes made for release 1.01 should be incorporated into release 2.0, some probably shouldn't be.

This sort of situation is handled using "branches". Each branch is a (mostly) separate line of development, diverging from some common ancestor version. (This divergence is where the term "branch" arose.)

Using branches in CVS can be fairly painful and is beyond the scope of this document. The vendor branch functionality is an exception and is discussed briefly below along with cvs import.

Use CVS Effectively

CVS is a tool, not a panacea. It helps you organize and maintain a project, but it doesn't do it by itself. It requires that you use it in a manner that makes it useful.

In order for the repository to be a useful tool for keeping track of what is really part of the project and what is not, you have to actively maintain the set of files CVS knows about. Don't add or commit temporary files, editor backups, object files, and the like to the CVS repository. Do remove files you're not using any more. (Even after telling CVS to remove them, you can still get them back later, because removing them is just a change that CVS tracks.)

In order for the version history to be useful, you have to add tags at important points in development, like releases. You also have to write at least minimally useful commit messages so you can look at them later and be reminded of the circumstances.

In order for the merging features to be useful, you have to avoid making sweeping changes without warning your partner, you have to update and commit regularly but not insanely often, and you have to take the trouble to merge correctly by hand when conflicts occur.

If you don't do these things, you will eventually end up in a hole, and CVS will not save you from yourself.

RCS

Some of you may have seen or used RCS, the Revision Control System. RCS is an ancestor of CVS; in fact, RCS more or less serves as the "back end" of CVS.

If you have used RCS, think of CVS as like a super-RCS that works on whole directory trees instead of single files. It also, as noted above, does not support file locking, but uses merging instead. There are some other minor differences as well.

If you haven't used RCS or don't know what it is, don't worry about it.

How do I...

The previous section explained CVS concepts in general terms. In this section we explain how to do various useful things.

How do I make a new repository?

Set the CVSROOT environment variable to point to the top directory of the new repository.

  % setenv CVSROOT ~/cvsroot/cs350
Now do cvs init:
  % cvs init
This creates a new empty repository.

How do I make a new project in a repository?

There are two ways to add code to a CVS repository. One is to use cvs add to add files and directories one at a time. The other is to use cvs import to do a bulk import of a whole existing source tree. The next section describes using cvs import; cvs add is described further below.

How do I import existing code into a repository?

First, make sure the CVSROOT environment variable is set to the right CVS repository, the one you want to import into. (Always double-check this. It's embarrassing to import into the wrong repository.)

  % echo $CVSROOT

Unpack the source tree you're going to import in a temporary directory. Then, go into the top level of the source tree you wish to import:

  % mkdir ~/tmp
  % cd ~/tmp
  % tar -xvzf  ~/somewhere/os161.tar.gz
  % cd os161-1.11

Now run cvs import. You need to provide three things: the place to import into, a symbolic name (which will become a tag, see above) that identifies the stuff you're importing or where it came from, and another symbolic name (also a tag) that identifies the particular version of the stuff you're importing.

To specify the place to import into, you provide a relative path within the repository where you want the contents of the current directory to appear. For CS350, you want the OS/161 distribution to appear in a directory called src at the top level of the repository, so you would specify src.

The first tag is a branch tag. It will name a branch (see above) on which the imported version will exist. The second tag names a specific version on this branch, which is known as a vendor branch.

The idea is that you can, later, import a new version of the same source tree using the same branch tag and a new version tag, and CVS will help you merge the changes with your own work. This is intended to allow people to maintain their own personal modifications to other people's programs; it can be useful for other purposes as well. It is an almost painless use of branches.

In addition to the required argument, you can specify an optional log message with the -m option. If you don't do this, CVS will run an editor for you to enter the message.

  % cvs import -m "Import OS/161 distribution" src cs350-group os161-base

When you run cvs import, CVS will print one line for every file it processes, with a key letter before it. "N" means the file is new. "U" means CVS has updated an existing file in the repository. "C" means that cvs has attempted to update a file, but a conflict exists and a merge is required. (The "C" and "U" results cannot happen on an initial import when there are no files already in the repository; they apply only to second and subsequent imports on a vendor branch.)

You do not need to watch these lines for "C" as they whiz by. If CVS finds conflicts, it reports them when it finishes. It will then print a message showing you a cvs checkout command to do to perform the merge. At this point, you must perform this checkout in a temporary directory, resolve any merge conflicts, and commit the results. If you do not, when you next update your main working area, some files and not others may reflect changes from the new import.

Adding files and directories to a repository

You can add files or directories to a repository using cvs add. When adding files or directories, it is important to get their permissions correct before adding them, so check permissions and use the chmod, chown, and chgrp commands to set the permissions on the files/directories the way you want them.

To add a directory into an existing repository, create the directory in the appropriate place in a checked out tree and then ask CVS to add the directory:

  % cvs add dir

To add files to an existing directory, create the new files in the appropriate directory in a checked out CVS tree, and then ask CVS to add them to the repository:

  % cvs add newfile

Unlike adding directories, these files will not be added to the repository until you use CVS to commit your changes.

How do I check out a working tree?

Use the cvs checkout command with the name of the project (the top-level directory in the CVS repository):

  % cvs checkout src

This will create a directory called src that holds a working tree. Be sure to do this in a good place. Do not do it in the CVS repository-this creates a huge mess.

If you want to check out a particular version, or branch, you can use the -r option with a tag that names the version or branch. (While you can give file version numbers as well, doing so does not produce useful results.) You can also specify a date using the -D option, to get a snapshot of the project as of a particular date. Note that when you do this, CVS remembers what you did, and may as a result not update or let you commit changes. Use the -A option to make it go back to the normal behavior.

How do I update my working tree?

Use cvs update. You can update whole directory trees or individual files. It's your responsibility, if you don't update everything at once, to make sure the resulting working tree you have is self-consistent.

One should (almost) always use the -d and -P options with cvs update when updating directories or whole trees. The -d option causes CVS to add new directories that are not in your working tree; without it, these directories never appear. The -P option causes it to remove completely empty directories from your working tree. This is important, because there is no way to really remove directories from the repository (see below).

The -q option before the update can be used to make CVS not print the cvs update: Updating foo messages, which can improve legibility with large trees.

  % cvs -q update -dP src
or
  % cvs update src/kern/vm/vmstuff.c

If you don't specify what to update, CVS updates the current directory (and any subdirectories).

Like with cvs checkout you can specify particular versions or dates with -r or -D, and make CVS forget about these using -A.

When you update, CVS prints one line for each file it processes, with a letter in front of it reflecting the file's status. These letters are:

Note that an "M" may or may not mean that a merge has been performed. You can identify merges because CVS prints additional messages as it performs them.

CVS can be made to shut up about unknown files whose presence is routine by adding them to a .cvsignore file, which can then be committed to the source tree. There are a number of examples in the OS/161 source.

There is no good way to retrieve the status summary without also doing an update. (There is a cvs status command, but its output is much less concise.)

How do I commit my changes?

Use cvs commit. You can commit directories or individual files. You can use the -m option to supply a commit message on the command line; if you don't, CVS will invoke the editor for each directory into which it commits files. Like with most commands, if you do not specify anything to commit explicitly, CVS commits all changes in the current directory and all subdirectories.

  % cvs commit foo.c
or
  % cvs commit src/kern

Remember that changes that have not been committed-including adding and removing files- will not be seen by other developers.

How do I add files?

Use cvs add and supply the filenames. The files will then show up with the "A" code on subsequent updates until they are committed. Remember, they will not be seen by other developers until they are committed.

Important: before committing new files, always chmod them so their permissions allow access by everyone you're working with. CVS does not cross-check the permissions of files in the repository; it replicates the permissions of the files when they're first committed. If those permissions are not correct, the file may appear in the repository with permissions that make it inaccessible to some or all other developers, causing them to become upset until you chmod the repository file properly. (See below.) This behavior is a long-standing bug in CVS.

How do I remove files?

When you wish to remove a file from the tree, delete it, then use cvs remove to make CVS take it away too. Commit the file (or its directory) after removing it.

It's a good idea to compile the project after removing but before committing, just to make sure you aren't breaking things.

Files that have been deleted are still kept around by CVS; while they'll be removed from people's working trees by default, you can still look at them, and you can bring them back again later using cvs add.

How do I rename files?

Carefully. Unfortunately, there isn't any completely satisfactory way to do this.

You can rename the file in your working directory, add the new name, and remove the old name; the problem with this is that you can't get at the previous change history of the file or make diffs of the file across the rename without knowing the old name and fiddling around.

You can rename the CVS file in the repository. This preserves the previous change history of the file, but breaks checkouts of old versions, because the file will appear under a name not expected by makefiles, scripts, etc.

Or, you can copy the CVS file in the repository from the old name to the new name, being sure to get the permissions right, and then use cvs remove in a working tree to get rid of the old name. This also preserves the previous change history, and does not break checkouts of old versions, but does cause the latter to acquire spurious extra copies of files.

There isn't any entirely satisfactory solution. (This is a design flaw in CVS.)

How do I add a directory?

Create the directory in your working area, and run cvs add on it.

Unlike files, directories get added instantly in the repository. Since they cannot be removed, do this only with caution.

Always check (and correct) the permissions of the directory within the repository after adding it.

How do I remove a directory?

In short, you don't. There isn't any way to do this in CVS. The design of CVS is flawed in certain ways, and this is the most obvious (and aggravating) consequence.

The best you can do is remove all the files from it and use the -P option with cvs update, which causes empty directories to disappear from your working tree.

You cannot remove the directory in the repository, because CVS keeps the deleted files there.

How do I make a merge less painful?

CVS's merge algorithm is entirely textual and not all that smart; when nothing else is visibly the same, it tends to use blank lines or lines with just braces as "anchors". Since these often aren't properly the same braces or blank lines, the results are usually bad and extremely tedious to straighten out.

If you anticipate making large changes that may cause unpleasant merges, one trick is to insert comments with unique code numbers at key points in the file beforehand, something like this:

  int    foo(void)    {
  // marker foo.c 55
    if (bar()) {
      stuff();
    }
  // marker foo.c 56
  // marker foo.c 57
    if (baz()) {
      otherstuff();	
    }
  // marker foo.c 58
  }

Before you change anything else, commit the comments, and have everyone concerned about the upcoming merge update to get the comments. (This may require merging, but, with some caution, it shouldn't be painful.) Then when you edit, treat the comments as markers that identify blocks of code. Commit the new hacked-up version with the markers intact, perhaps like this:

  int    foo(void)    {
  // marker foo.c 55
    if (newbar()) {
      newstuff();
    }
  // marker foo.c 56
    if (totallynew()) {
      morenewstuff(); 
    }
  // marker foo.c 57
    if (baz()) {
      otherstuff();
    }
  // marker foo.c 58
  }

As long as you don't change the marker lines, CVS will generally be able to match them up while merging. This will not prevent merge conflicts, but it will usually keep the conflict blocks from becoming severely misaligned.

Once everyone has merged, you can delete the markers.

It is also often the case that you can avoid merging by having different members of the group working on different files.

How do I set file permissions in the CVS repository?

CVS does not always set permissions correctly by itself, and so at various times (which this document attempts to point out) one may need to go into the CVS repository itself and chmod, chown, or chgrp files.

The CVS files (the ones that end in .,v) are never writable. They should be readable (and executable if the file they represent should be executable) by the people who use the repository. Generally this means they should either be mode 444 or mode 555, or mode 440 or 550 and belong to the right group, or, for an entirely private repository, mode 400 or 500.

Directories should be readable, writable, and executable by everyone who uses the repository. (Read-only access to the repository tree is not useful, as CVS has to create lock files even when it is only reading the actual data.) On file systems with System V group semantics, it is generally useful to set the set-group-id bit. This causes new files in the directory to belong to the right group, and new subdirectories to inherit the right permissions. On file systems with BSD group semantics, new files will always belong to the right group, but the permissions of new directories will usually need to be set manually.

Certain files in $CVSROOT/CVSROOT need to be writable by everyone who uses the repository. These are: history, val-tags, and any global commit log files you might have.

Also note that in order for CVS to be able to access the repository at all, every directory above the repository in the directory tree must also be executable.

How do I create a tag?

Use cvs tag with the name of the tag. You can tag specific files or subdirectories by specifying them after the tag name; by default the current directory and all subdirectories are tagged. Generally one tags the entire project at once.

  % cvs tag asst2-end src

The versions tagged will be the version in the repository that your working tree is based on. (As always, uncommitted changes will not be processed.)

You can specify specific versions to be tagged (including via other tags) using the -r and -D options. You can also tag specific versions without reference to your working tree using the similar cvs rtag command. Consult the CVS documentation for more information.

How do I move an existing tag?

Use the -F option to tag:

  % cvs tag -F asst2-end src/kern/vm/vm.c

Without this, CVS will complain if you try to apply a tag that already exists.

How do I export a release?

Use cvs export. This is like cvs checkout, except instead of creating a "subscription" to the CVS repository, it extracts a snapshot, with no CVS control/management files.

Always do cvs export in a temporary directory. Doing it over your working directory makes a mess.

You always must supply a -r or -D option with cvs export, to specify the explicit version you want to export. While you can use -Dnow to get the latest version, it is recommended that when exporting an actual release that one always use a tag to name that release.

How do I get rid of an extra working tree?

You can just delete it, after of course checking to make sure it doesn't have any changes or other things in it that you want to keep.

To do this the "right" way, use cvs release on the tree first, or cvs release -d to have CVS also delete it. This "unsubscribes" from the CVS repository.

All cvs release actually does is record in the history file that the cvs checkout that created the tree is no longer active. This is only useful if you want to audit the history file or try to keep track of how many working trees are outstanding. These things are occasionally worthwhile, so it's better to get in the habit of using cvs release.

How do I set up commit messages to be mailed out?

The way CVS processes commit messages, besides storing them in the CVS files themselves (which always happens) is controlled by a file in $CVSROOT/CVSROOT called loginfo.

To edit it, you need to check out a working tree for CVSROOT, which you do like this:

  % cd /some/temporary/area
  % cvs checkout CVSROOT
  % cd CVSROOT

Edit the loginfo file. By default it contains a large comment explaining how the file works. To mail out commit messages, you might use a line like this:

  ALL (echo ""; echo $USER; date; cat) | /usr/bin/mailx -s "CS350 CVS commit" your@address partner@address

The program /usr/bin/mailx should work in the student environment; elsewhere you might need to use /usr/bin/mail or /usr/bin/Mail or something else, depending on the configuration of the system hosting the CVS repository.

When done editing, commit loginfo. That commit will not mail out, but the next one will. You may want to make a test commit somewhere, in case it doesn't work.

When everything runs properly, cvs release or delete your working copy of CVSROOT.

How do I make diffs?

Use cvs diff. Specify the files or directory trees you wish to compare; if you do not specify anything, by default the current directory and all subdirectories are diffed.

By default your working tree is diffed against the version in the repository to which it was last updated. You can diff against a specific repository version by providing a -r or -D option (as described above under checkout). You can diff two specific versions by providing two such options.

If you want to see the latest commits that you haven't updated yet in your working directory, use -rHEAD as one of the arguments.

You can also provide most of the normal diff format options. The most commonly used format is the "-u" format. The "-w" option causes diff to ignore changes in spacing and is thus also useful. The "-N" option includes the contents of new files in the diffs, instead of just a note that such files are new. See the diff man page for more information. If you need to prepare diffs for CS350, please use the "-uNw" options.

How do I find out where a particular line of code appeared?

The cvs annotate command prints each line of the file with a prefix containing the version number in which the line appeared, the date of that version, and the username of the person who committed it. You can use the -r and -D options to retrieve the file as it existed in any previous version.

This can be used in conjunction with cvs diff to track down the history of individual lines of code, as long as they haven't moved around very much.

How do I back out a bad commit?

It's late at night and you foolishly/accidentally commit some immensely stupid change that breaks everything. (We've all been there; if you haven't yet, you will eventually.)

All is not lost. Part of the role of CVS is to keep track of old versions; you can extract the old version and re-commit it, or you can tell CVS to unmerge the change, like this, supposing that the dud commit made version 1.3 of foo.c and the working version was 1.2:

  % cvs update -j1.3 -j1.2 foo.c

This tells cvs to merge ("join") the diff from version 1.3 to 1.2, which you'll note is the reverse direction from time (since you want to back the change out), into the current version. If this causes merge conflicts, you should resolve them in the usual way, then commit the modified file.

It does not matter if version 1.3 is not the latest version.

Note that you can do this on whole trees if the versions you want are named by tags.

How do I move my working directory?

In general, you can just rename the entire tree with mv. The CVS control files in the working directory are position-independent.

What if I move the repository?

In this case, however, because each working directory contains references back to the repository, you need to fiddle.

In general, it's easiest to cvs release all working directories prior to moving the repository, and check out new ones afterwards. If this is impractical, or impossible, you can recover by editing the files Repository and Root in the CVS subdirectory of every directory in the working tree to reflect the new location. Needless to say this is a nuisance, even if the edits are scripted. It's best avoided.

How do I use remote CVS?

CVS actually supports two different remote access methods, one that runs over a remote-shell channel like rsh or ssh, and one that uses a CVS-specific remote access protocol.

With the advent of ssh, the first method is generally preferable, and can be set up like this:

  % setenv CVSROOT user@machine:/path/to/CVS/repository
  % setenv CVS_RSH ssh

where "machine" is the system hosting the CVS repository, and "user" is your username on that machine. If that's the same as on the local machine, it can be left off.

With these settings, cvs operates remotely. Remote CVS is completely transparent, except that ssh will ask you for your password all the time. To use this setup effectively, you will want to set up ssh to allow passwordless logins. See the ssh documentation for more information.

For more information

To get a list of CVS commands and options, use the man page for CVS (type "man cvs") or look at the online CVS documentation.