Open Source Development with CVS, 3rd Edition

by Karl Fogel and Moshe Bar



Copyright © 1999, 2000 Karl Fogel <[email protected]>

This document is free software; you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version.

This document is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

This manual describes how to use and administer CVS (Concurrent Versions System). It is part of a larger work entitled Open Source Development With CVS; please see the introduction for details.

This is version 1.21 of this manual.

Short Contents

Table of Contents


Node: Top, Next: , Up: (dir)

Top


Node: Introduction, Next: , Previous: Top, Up: Top

Introduction

This is a set of free, online chapters about using CVS (Concurrent Versions System) for collaboration and version control. It covers everything from CVS installation and basic concepts all the way to advanced usage and administration. It is intended for anyone who uses or plans to use CVS.

These chapters are excerpted from a larger work called Open Source Development With CVS (published by The Coriolis Group, ISBN 1-57610-490-7). The remainder of that book - chapters 1, 3, 5, and 7 - deals with the challenges and philosophical issues of running an Open Source project using CVS.

While the free chapters here constitute a complete CVS book by themselves, we certainly hope you'll like them enough to purchase a treeware copy of the entire book! You can order it directly from the publisher, at http://www.coriolis.com/bookstore/bookdetail.cfm?id=1576104907.

These chapters are released under the GNU General Public License. For more information about free software in general, visit http://www.gnu.org/, and particularly http://www.gnu.org/philosophy/free-sw.html.

To submit comments or errata regarding any of this material, please send email to [email protected]. For news and updates, visit http://cvsbook.red-bean.com/.


Node: An Overview of CVS, Next: , Previous: Introduction, Up: Top

An Overview of CVS

I can't imagine programming without it... that would be like parachuting without a parachute!
-Brian Fitzpatrick on CVS

This chapter introduces the fundamentals of CVS, and then provides an in-depth guided tour of everyday CVS usage. Concepts are presented sequentially, so if you're new to CVS, the best way to read this is to start at the beginning and go straight through, without skipping anything.


Node: Basic Concepts, Next: , Up: An Overview of CVS

Basic Concepts

If you've never used CVS (or any version control system) before, it's easy to get tripped up by some of its underlying assumptions. What seems to cause the most initial confusion about CVS is that it is used for two apparently unrelated purposes: record keeping and collaboration. It turns out, however, that these two functions are closely connected.

Record keeping became necessary because people wanted to compare a program's current state with how it was at some point in the past. For example, in the normal course of implementing a new feature, a developer may bring the program into a thoroughly broken state, where it will probably remain until the feature is mostly finished. Unfortunately, this is just the time when someone usually calls to report a bug in the last publicly released version. To debug the problem (which may also exist in the current version of the sources), the program has to be brought back to a useable state.

Restoring the state poses no difficulty if the source code history is kept under CVS. The developer can simply say, in effect, "Give me the program as it was three weeks ago", or perhaps "Give me the program as it was at the time of our last public release". If you've never had this kind of convenient access to historical snapshots before, you may be surprised at how quickly you come to depend on it. Personally, I always use revision control on my coding projects now - it's saved me many times.

To understand what this has to do with facilitating collaboration, we'll need to take a closer look at the mechanism that CVS provides to help numerous people work on the same project. But before we do that, let's take a look at a mechanism that CVS doesn't provide (or at least, doesn't encourage): file locking. If you've used other version control systems, you may be familiar with the lock-modify-unlock development model, wherein a developer first obtains exclusive write access (a lock) to the file to be edited, makes the changes, and then releases the lock to allow other developers access to the file. If someone else already has a lock on the file, they have to "release" it before you can lock it and start making changes (or, in some implementations, you may "steal" their lock, but that is often an unpleasant surprise for them and not good practice!).

This system is workable if the developers know each other, know who's planning to do what at any given time, and can communicate with each other quickly if someone cannot work because of access contention. However, if the developer group becomes too large or too spread out, dealing with all the locking issues begins to chip away at coding time; it becomes a constant hassle that can discourage people from getting real work done.

CVS takes a more mellow approach. Rather than requiring that developers coordinate with each other to avoid conflicts, CVS enables developers to edit simultaneously, assumes the burden of integrating all the changes, and keeps track of any conflicts. This process uses the copy-modify-merge model, which works as follows:

  1. Developer A requests a working copy (a directory tree containing the files that make up the project) from CVS. This is also known as "checking out" a working copy, like checking a book out of the library.
  2. Developer A edits freely in her working copy. At the same time, other developers may be busy in their own working copies. Because these are all separate copies, there is no interference - it is as though all of the developers have their own copy of the same library book, and they're all at work scribbling comments in the margins or rewriting certain pages independently.
  3. Developer A finishes her changes and commits them into CVS along with a "log message", which is a comment explaining the nature and purpose of the changes. This is like informing the library of what changes she made to the book and why. The library then incorporates these changes into a "master" copy, where they are recorded for all time.
  4. Meanwhile, other developers can have CVS query the library to see if the master copy has changed recently. If it has, CVS automatically updates their working copies. (This part is magical and wonderful, and I hope you appreciate it. Imagine how different the world would be if real books worked this way!)

As far as CVS is concerned, all developers on a project are equal. Deciding when to update or when to commit is largely a matter of personal preference or project policy. One common strategy for coding projects is to always update before commencing work on a major change and to commit only when the changes are complete and tested so that the master copy is always in a "runnable" state.

Perhaps you're wondering what happens when developers A and B, each in their own working copy, make different changes to the same area of text and then both commit their changes? This is called a conflict, and CVS notices it as soon as developer B tries to commit changes. Instead of allowing developer B to proceed, CVS announces that it has discovered a conflict and places conflict markers (easily recognizable textual flags) at the conflicting location in his copy. That location also shows both sets of changes, arranged for easy comparison. Developer B must sort it all out and commit a new revision with the conflict resolved. Perhaps the two developers will need to talk to each other to settle the issue. CVS only alerts the developers that there is a conflict; it's up to human beings to actually resolve it.

What about the master copy? In official CVS terminology, it is called the project's repository. The repository is simply a file tree kept on a central server. Without going into too much detail about its structure (but see Repository Administration), let's look at what the repository must do to meet the requirements of the checkout-commit-update cycle. Consider the following scenario:

  1. Two developers, A and B, check out working copies of a project at the same time. The project is at its starting point - no changes have been committed by anyone yet, so all the files are in their original, pristine state.
  2. Developer A gets right to work and soon commits her first batch of changes.
  3. Meanwhile, developer B watches television.
  4. Developer A, hacking away like there's no tomorrow, commits her second batch of changes. Now, the repository's history contains the original files, followed by A's first batch of changes, followed by this set of changes.
  5. Meanwhile, developer B plays video games.
  6. Suddenly, developer C joins the project and checks out a working copy from the repository. Developer C's copy reflects A's first two sets of changes, because they were already in the repository when C checked out her copy.
  7. Developer A, continuing to code as one possessed by spirits, completes and commits her third batch of changes.
  8. Finally, blissfully unaware of the recent frenzy of activity, developer B decides it's time to start work. He doesn't bother to update his copy; he just commences editing files, some of which may be files that A has worked in. Shortly thereafter, developer B commits his first changes.

At this point, one of two things can happen. If none of the files edited by developer B have been edited by A, the commit succeeds. However, if CVS realizes that some of B's files are out of date with respect to the repository's latest copies, and those files have also been changed by B in his working copy, CVS informs B that he must do an update before committing those files.

When developer B runs the update, CVS merges all of A's changes into B's local copies of the files. Some of A's work may conflict with B's uncommitted changes, and some may not. Those parts that don't are simply applied to B's copies without further complication, but the conflicting changes must be resolved by B before being committed.

If developer C does an update now, she'll receive various new changes from the repository: those from A's third commit, and those from B's first successful commit (which might really come from B's second attempt to commit, assuming B's first attempt resulted in B being forced to resolve conflicts).

In order for CVS to serve up changes, in the correct sequence, to developers whose working copies may be out of sync by varying degrees, the repository needs to store all commits since the project's beginning. In practice, the CVS repository stores them all as successive diffs. Thus, even for a very old working copy, CVS is able to calculate the difference between the working copy's files and the current state of the repository, and is thereby able to bring the working copy up to date efficiently. This makes it easy for developers to view the project's history at any point and to revive even very old working copies.

Although, strictly speaking, the repository could achieve the same results by other means, in practice, storing diffs is a simple, intuitive means of implementing the necessary functionality. The process has the added benefit that, by using patch appropriately, CVS can reconstruct any previous state of the file tree and thus bring any working copy from one state to another. It can allow someone to check out the project as it looked at any particular time. It can also show the differences, in diff format, between two states of the tree without affecting someone's working copy.

Thus, the very features necessary to give convenient access to a project's history are also useful for providing a decentralized, uncoordinated developer team with the ability to collaborate on the project.

For now, you can ignore the details of setting up a repository, administering user access, and navigating CVS-specific file formats (those will be covered in Repository Administration). For the moment, we'll concentrate on how to make changes in a working copy.

But first, here is a quick review of terms:


Node: A Day With CVS, Next: , Previous: Basic Concepts, Up: An Overview of CVS

A Day With CVS

This section describes some basic CVS operations, then follows with a sample session covering typical CVS usage. As the guided tour progresses, we'll also start to look at how CVS works internally.

Although you don't need to understand every last detail of CVS's implementation to use it, a basic knowledge of how it works is invaluable in choosing the best way to achieve a given result. CVS is more like a bicycle than an automobile, in the sense that its mechanisms are entirely transparent to anyone who cares to look. As with a bicycle, you can just hop on and start riding immediately. However, if you take a few moments to study how the gears work, you'll be able to ride it much more efficiently. (In the case of CVS, I'm not sure whether transparency was a deliberate design decision or an accident, but it does seem to be a property shared by many free programs. Externally visible implementations have the advantage of encouraging the users to become contributing developers by exposing them to the system's inner workings right from the start.)

Each part of the tour may make use of knowledge introduced in previous parts. Therefore, if this is your first time, I recommend that you simply start at the beginning and take the tour sequentially, without skipping over anything. The menu below is merely meant as a convenience for repeat visitors - you shouldn't use it to jump directly to a section that interests you unless you're already familiar with the material in the previous sections.


Node: Conventions Used In This Tour, Next: , Up: A Day With CVS

Conventions Used In This Tour

The tour takes place in a Unix environment. CVS also runs on Windows and Macintosh operating systems, and Tim Endres of Ice Engineering has even written a Java client (see http://www.trustice.com/java/jcvs/), which can be run anywhere Java runs. However, I'm going to take a wild guess and assume that the majority of CVS users - present and potential - are most likely working in a Unix command-line environment. If you aren't one of these, the examples in the tour should be easy to translate to other interfaces. Once you understand the concepts, you can sit down at any CVS front end and work with it (trust me, I've done it many times).

The examples in the tour are oriented toward people who will be using CVS to keep track of programming projects. However, CVS operations are applicable to all text documents, not just source code.

The tour also assumes that you already have CVS installed (it's present by default on many of the popular free Unix systems, so you might already have it without knowing it) and that you have access to a repository. Even if you are not set up, you can still benefit from reading the tour. In Repository Administration, you'll learn how to install CVS and set up repositories.

Assuming CVS is installed, you should take a moment to find the online CVS manual. Known familiarly as the "Cederqvist" (after Per Cederqvist, its original author), it comes with the CVS source distribution and is usually the most up-to-date reference available. It's written in Texinfo format and should be available on Unix systems in the "Info" documentation hierarchy. You can read it either with the command line info program

     floss$ info cvs
     

or by pressing Ctrl+H and then typing "i" inside Emacs. If neither of these works for you, consult your local Unix guru (or see Repository Administration regarding installation issues). You'll definitely want to have the Cederqvist at your fingertips if you're going to be using CVS regularly.


Node: Invoking CVS, Next: , Previous: Conventions Used In This Tour, Up: A Day With CVS

Invoking CVS

CVS is one program, but it can perform many different actions: updating, committing, branching, diffing, and so on. When you invoke CVS, you must specify which action you want to perform. Thus, the format of a CVS invocation is:

     floss$ cvs command
     

For example, you can use

     floss$ cvs update
     floss$ cvs diff
     floss$ cvs commit
     

and so on. (Don't bother to try running any of those particular commands yet, though; they won't do anything until you're in a working copy, which we'll get to shortly.)

Both CVS and the command can take options. Options that affect the behavior of CVS, independently of the command being run, are called global options; command-specific options are just called command options. Global options always go to the left of the command; command options, to its right. So in

     floss$ cvs -Q update -p
     

-Q is a global option, and -p is a command option. (If you're curious, -Q means "quietly"-that is, suppress all diagnostic output, and print error messages only if the command absolutely cannot be completed for some reason; -p means to send the results of update to standard output instead of to files.)


Node: Accessing A Repository, Next: , Previous: Invoking CVS, Up: A Day With CVS

Accessing A Repository

Before you can do anything, you must tell CVS the location of the repository you'll be accessing. This isn't a concern if you already have a working copy checked out - any working copy knows what repository it came from, so CVS can automatically deduce the repository for a given working copy. However, let's assume you don't have a working copy yet, so you need to tell CVS explicitly where to go. This is done with the -d global option (the -d stands for "directory", an abbreviation for which there is a historical justification, although -r for "repository" might have been better), followed by the path to the repository. For example, assuming the repository is on the local machine in /usr/local/cvs (a fairly standard location):

     floss$ cvs -d /usr/local/cvs command
     

In many cases, however, the repository is on another machine and must therefore be reached over the network. CVS provides a choice of network access methods; which one you'll use depends mostly on the security needs of the repository machine (hereinafter referred to as "the server"). Setting up the server to allow various remote access methods is covered in Repository Administration; here we'll deal only with the client side.

Fortunately, all the remote access methods share a common invocation syntax. In general, to specify a remote repository as opposed to a local one, you just use a longer repository path. You first name the access method, delimited on each side by colons, followed by the username and the server name (joined with an @ sign), another separator colon, and finally the path to the repository directory on the server.

Let's look at the pserver access method, which stands for "password-authenticated server":

     floss$ cvs -d :pserver:[email protected]:/usr/local/cvs login
     (Logging in to [email protected])
     CVS password: (enter your CVS password here)
     floss$
     

The long repository path following -d told CVS to use the pserver access method, with the username jrandom, on the server cvs.foobar.com, which has a CVS repository in /usr/local/cvs. There's no requirement that the hostname be "cvs.something.com" by the way; that's a common convention, but it could just as easily have been:

     floss$ cvs -d :pserver:[email protected]:/usr/local/cvs command
     

The command actually run was login, which verifies that you are authorized to work with this repository. It prompts for a password, then contacts the server to verify the password. Following Unix custom, cvs login returns silently if the login succeeds; it shows an error message if it fails (for instance, because the password is incorrect).

You only have to log in once from your local machine to a given CVS server. After a successful login, CVS stores the password in your home directory, in a file called .cvspass. It consults that file every time a repository is contacted via the pserver method, so you only have to run login the first time you access a given CVS server from a particular client machine. Of course, you can rerun cvs login anytime if the password changes.

Note: pserver is currently the only access method requiring an initial login like this; with the others, you can start running regular CVS commands immediately.

Once you've stored the authentication information in your .cvspass file, you can run other CVS commands using the same command-line syntax:

     floss$ cvs -d :pserver:[email protected]:/usr/local/cvs command
     

Getting pserver to work in Windows may require an extra step. Windows doesn't have the Unix concept of a home directory, so CVS doesn't know where to put the .cvspass file. You'll have to specify a location. It's normal to designate the root of the C: drive as the home directory:

     C:\WINDOWS> set HOME=C:
     C:\WINDOWS> cvs -d :pserver:[email protected]:/usr/local/cvs login
     (Logging in to [email protected])
     CVS password: (enter password here)
     C:\WINDOWS>
     

Any folder in the file system will suffice. You may want to avoid network drives, though, because the contents of your .cvspass file would then be visible to anyone with access to the drive.

In addition to pserver, CVS supports the ext method (which uses an external connection program, such as rsh or ssh), kserver (for the Kerberos security system version 4), and gserver (which uses the GSSAPI, or Generic Security Services API, and also handles Kerberos versions 5 and higher). These methods are similar to pserver, but each has its own idiosyncrasies.

Of these, the ext method is probably the most commonly used. If you can log into the server with rsh or ssh, you can use the ext method. You can test it like this:

     floss$ rsh -l jrandom cvs.foobar.com
     Password: enter your login password here
     

Okay, let's assume you successfully logged in and logged out of the server with rsh, so now you're back on the original client machine:

     floss$ CVS_RSH=rsh; export CVS_RSH
     floss$ cvs -d :ext:[email protected]:/usr/local/cvs command
     

The first line sets (in Unix Bourne shell syntax) the CVS_RSH environment variable to rsh, which tells CVS to use the rsh program to connect. The second line can be any CVS command; you will be prompted for your password so CVS can log into the server.

If you're in C shell rather than in Bourne shell, try this:

     floss% setenv CVS_RSH rsh
     

and for Windows, try this:

     C:\WINDOWS> set CVS_RSH=rsh
     

The rest of the tour will use the Bourne syntax; translate for your environment as necessary.

To use ssh (the Secure Shell) instead of rsh, just set the CVS_RSH variable appropriately:

     floss$ CVS_RSH=ssh; export CVS_RSH
     

Don't get thrown by the fact that the variable's name is CVS_RSH but you're setting its value to ssh. There are historical reasons for this (the catch-all Unix excuse, I know). CVS_RSH can point to the name of any program capable of logging you into the remote server, running commands, and receiving their output. After rsh, ssh is probably the most common such program, although there are probably others. Note that this program must not modify its data stream in any way. This disqualifies the Windows NT rsh, because it converts (or attempts to convert) between the DOS and Unix line-ending conventions. You'd have to get some other rsh for Windows or use a different access method.

The gserver and kserver methods are not used as often as the others and are not covered here. They're quite similar to what we've covered so far; see the Cederqvist for details.

If you only use one repository and don't want to type -d repos each time, just set the CVSROOT environment variable (which perhaps should have been named CVSREPOS, but it's too late to change that now):

     floss$ CVSROOT=/usr/local/cvs
     floss$ export CVSROOT
     floss$ echo $CVSROOT
     /usr/local/cvs
     floss$
     

or maybe

     floss$ CVSROOT=:pserver:[email protected]:/usr/local/cvs
     floss$ export CVSROOT
     floss$ echo $CVSROOT
     :pserver:[email protected]:/usr/local/cvs
     floss$
     

The rest of this tour assumes that you've set CVSROOT to point to your repository, so the examples will not show the -d option. If you need to access many different repositories, you should not set CVSROOT and should just use -d repos when you need to specify the repository.


Node: Starting A New Project, Next: , Previous: Accessing A Repository, Up: A Day With CVS

Starting A New Project

If you're learning CVS in order to work on a project that's already under CVS control (that is, it is kept in a repository somewhere), you'll probably want to skip down to the next section, "Checking Out A Working Copy." On the other hand, if you want to take existing source code and put it into CVS, this is the section for you. Note that it still assumes you have access to an existing repository; see Repository Administration if you need to set up a repository first.

Putting a new project into a CVS repository is known as importing. The CVS command, as you may have guessed, is

     floss$ cvs import
     

except that it needs some more options (and needs to be in the right location) to succeed. First, go into the top-level directory of your project tree:

     floss$ cd myproj
     floss$ ls
     README.txt  a-subdir/   b-subdir/   hello.c
     floss$
     

This project has two files - README.txt and hello.c - in the top level, plus two subdirectories - a-subdir and b-subdir - plus some more files (not shown in the example) inside those subdirectories. When you import a project, CVS imports everything in the tree, starting from the current directory and working its way down. Therefore, you should make sure that the only files in the tree are ones you want to be permanent parts of the project. Any old backup files, scratch files, and so on should all be cleaned out.

The general syntax of an import command is

     floss$ cvs import -m "log msg" projname vendortag releasetag
     

The -m flag (for message) is for specifying a short message describing the import. This will be the first log message for the entire project; every commit thereafter will also have its own log message. These messages are mandatory; if you don't give the -m flag, CVS automatically starts up an editor (by consulting the EDITOR environment variable) for you to type a log message in. After you save the log message and exit the editor, the import then continues.

The next argument is the project's name (we'll use "myproj"). This is the name under which you'll check out the project from the repository. (What actually happens is that a directory of that name gets created in the repository, but more on that in Repository Administration.) The name you choose now does not need to be the same as the name of the current directory, although in most cases it usually is.

The vendortag and releasetag arguments are a bit of bookkeeping for CVS. Don't worry about them now; it hardly matters what you use. In Advanced CVS you'll learn about the rare circumstances where they're significant. For now, we'll use a username and "start" for those arguments.

We're ready to run import:

     floss$ cvs import -m "initial import into CVS" myproj jrandom start
     N myproj/hello.c
     N myproj/README.txt
     cvs import: Importing /usr/local/cvs/myproj/a-subdir
     N myproj/a-subdir/whatever.c
     cvs import: Importing /usr/local/cvs/myproj/a-subdir/subsubdir
     N myproj/a-subdir/subsubdir/fish.c
     cvs import: Importing /usr/local/cvs/myproj/b-subdir
     N myproj/b-subdir/random.c
     
     No conflicts created by this import
     floss$
     

Congratulations! If you ran that command (or something similar), you've finally done something that affects the repository.

Reading over the output of the import command, you'll notice that CVS precedes each filename with a single letter - in this case, "N" for "new file". The use of a single letter on the left to indicate the status of a file is a general pattern in CVS command output. We'll see it later in checkout and update as well.

You might think that, having just imported the project, you can start working in the tree immediately. This is not the case, however. The current directory tree is still not a CVS working copy. It was the source for the import command, true, but it wasn't magically changed into a CVS working copy merely by virtue of having been imported. To get a working copy, you need to check one out from the repository.

First, though, you might want to archive the current project tree. The reason is that once the sources are in CVS, you don't want to confuse yourself by accidentally editing copies that aren't in version control (because those changes won't become part of the project's history). You want to do all of your editing in a working copy from now on. However, you also don't want to remove the imported tree entirely, because you haven't yet verified that the repository actually has the files. Of course, you can be 99.999 percent certain that it does because the import command returned with no error, but why take chances? Paranoia pays, as every programmer knows. Therefore, do something like this:

     floss$ ls
     README.txt  a-subdir/   b-subdir/   hello.c
     floss$ cd ..
     floss$ ls
     myproj/
     floss$ mv myproj was_myproj
     floss$ ls
     was_myproj/
     floss$
     

There. You still have the original files, but they're clearly named as an obsolete version, so they won't be in the way when you get a real working copy. Now you're ready to check out.


Node: Checking Out A Working Copy, Next: , Previous: Starting A New Project, Up: A Day With CVS

Checking Out A Working Copy

The command to check out a project is exactly what you think it is:

     floss$ cvs checkout myproj
     cvs checkout: Updating myproj
     U myproj/README.txt
     U myproj/hello.c
     cvs checkout: Updating myproj/a-subdir
     U myproj/a-subdir/whatever.c
     cvs checkout: Updating myproj/a-subdir/subsubdir
     U myproj/a-subdir/subsubdir/fish.c
     cvs checkout: Updating myproj/b-subdir
     U myproj/b-subdir/random.c
     
     floss$ ls
     myproj/      was_myproj/
     floss$ cd myproj
     floss$ ls
     CVS/        README.txt  a-subdir/   b-subdir/   hello.c
     floss$
     

Behold - your first working copy! Its contents are exactly the same as what you imported, with the addition of a subdirectory named "CVS". That's where CVS stores version control information. Actually, each directory in the project has a CVS subdirectory:

     floss$ ls a-subdir
     CVS/        subsubdir/  whatever.c
     floss$ ls a-subdir/subsubdir/
     CVS/    fish.c
     floss$ ls b-subdir
     CVS/      random.c
     

The fact that CVS keeps its revision information in subdirectories named CVS means that your project can never contain subdirectories of its own named CVS. In practice, I've never heard of this being a problem.

Before editing any files, let's take a peek inside the black box:

     floss$ cd CVS
     floss$ ls
     Entries     Repository  Root
     floss$ cat Root
     /usr/local/cvs
     floss$ cat Repository
     myproj
     floss$
     

Nothing too mysterious there. The Root file points to repository, and the Repository file points to a project inside the repository. If that's a little confusing, let me explain.

There is a longstanding confusion about terminology in CVS. The word "repository" is used to refer to two different things. Sometimes, it means the root directory of a repository (for example, /usr/local/cvs), which can contain many projects; this is what the Root file refers to. But other times, it means one particular project-specific subdirectory within a repository root (for example, /usr/local/cvs/myproj, /usr/local/cvs/yourproj, or /usr/local/cvs/fish). The Repository file inside a CVS subdirectory takes the latter meaning.

In this book, "repository" generally means Root (that is, the top-level repository), although it may occasionally be used to mean a project-specific subdirectory. If the intended sense can't be figured out from the context, there will be clarifying text. Note that the Repository file may sometimes contain an absolute path to the project name instead of a relative path. This can make it slightly redundant with the Root file:

     floss$ cd CVS
     floss$ cat Root
     :pserver:[email protected]:/usr/local/cvs
     floss$ cat Repository
     /usr/local/cvs/myproj
     floss$
     

The Entries file stores information about the individual files in the project. Each line deals with one file, and there are only lines for files or subdirectories in the immediate parent directory. Here's the top-level CVS/Entries file in myproj:

     floss$ cat Entries
     /README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//
     /hello.c/1.1.1.1/Sun Apr 18 18:18:22 1999//
     D/a-subdir////
     D/b-subdir////
     

The format of each line is

     /filename/revision number/last modification date//
     

and the directory lines are prefixed with "D". (CVS doesn't really keep a change history for directories, so the fields for revision number and datestamp are empty.)

The datestamps record the date and time of the last update (in Universal Time, not local time) of the files in the working copy. That way, CVS can easily tell whether a file has been modified since the last checkout, update, or commit. If the file system timestamp differs from the timestamp in the CVS/Entries file, CVS knows (without even having to consult the repository) that the file was probably modified.

If you take a look at the CVS/* files in one of the subdirectories

     floss$ cd a-subdir/CVS
     floss$ cat Root
     /usr/local/cvs
     floss$ cat Repository
     myproj/a-subdir
     floss$ cat Entries
     /whatever.c/1.1.1.1/Sun Apr 18 18:18:22 1999//
     D/subsubdir////
     floss$
     

you can see that the root repository has not changed, but the Repository file spells out the location of this subdirectory of the project, and the Entries file contains different lines.

Immediately after import, the revision number of every file in the project is shown as 1.1.1.1. This initial revision number is a bit of a special case, so we won't examine it in detail just yet; we'll take a closer look at revision numbers after we've committed some changes.


Node: Version Versus Revision, Next: , Previous: Checking Out A Working Copy, Up: A Day With CVS

Version Versus Revision

The internal revision number that CVS keeps for each file is unrelated to the version number of the software product of which the files are part. For example, you may have a project composed of three files, whose internal revision numbers on May 3, 1999, were 1.2, 1.7, and 2.48. On that day, you package up a new release of the software and release it as SlickoSoft Version 3. This is purely a marketing decision and doesn't affect the CVS revisions at all. The CVS revision numbers are invisible to your customers (unless you give them repository access); the only publicly visible number is the "3" in Version 3. You could have called it Version 1729 as far as CVS is concerned - the version number (or "release" number) has nothing to do with CVS's internal change tracking.

To avoid confusion, I'll use the word "revision" to refer exclusively to the internal revision numbers of files under CVS control. I may still call CVS a "version control system", however, because "revision control system" just sounds too awkward.


Node: Making A Change, Next: , Previous: Version Versus Revision, Up: A Day With CVS

Making A Change

The project as it stands doesn't do much. Here are the contents of hello.c:

     floss$ cat hello.c
     #include <stdio.h>
     
     void
     main ()
     {
        printf ("Hello, world!\n");
     }
     

Let's make the first change to the project since importing it; we'll add the line

     printf ("Goodbye, world!\n");
     

right after the Hello, world!. Invoke your favorite editor and make the change:

     floss$ emacs hello.c
       ...
     

This was a fairly simple change, one where you're not likely to forget what you did. But in a larger, more complex project, it's quite possible you may edit a file, be interrupted by something else, and return several days later and be unable to remember exactly what you did, or even to remember if you changed anything at all. Which brings us to our first "CVS Saves Your Life" situation: comparing your working copy against the repository.


Node: Finding Out What You (And Others) Did -- update And diff, Next: , Previous: Making A Change, Up: A Day With CVS

Finding Out What You (And Others) Did - update And diff

Previously, I've talked about updating as a way of bringing changes down from the repository into your working copy - that is, as a way of getting other people's changes. However, update is really a bit more complex; it compares the overall state of the working copy with the state of the project in the repository. Even if nothing in the repository has changed since checkout, something in the working copy may have, and update will show that, too:

     floss$ cvs update
     cvs update: Updating .
     M hello.c
     cvs update: Updating a-subdir
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir
     

The M next to hello.c means the file has been modified since it was last checked out, and the modifications have not yet been committed to the repository.

Sometimes, merely knowing which files you've edited is all you need. However, if you want a more detailed look at the changes, you can get a full report in diff format. The diff command compares the possibly modified files in the working copy to their counterparts in the repository and displays any differences:

     floss$ cvs diff
     cvs diff: Diffing .
     Index: hello.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.1.1.1
     diff -r1.1.1.1 hello.c
     6a7
     >   printf ("Goodbye, world!\n");
     cvs diff: Diffing a-subdir
     cvs diff: Diffing a-subdir/subsubdir
     cvs diff: Diffing b-subdir
     

That's helpful, if a bit obscure, but there's still a lot of cruft in the output. For starters, you can ignore most of the first few lines. They just name the repository file and give the number of the last checked-in revision. These are useful pieces of information under other circumstances (we'll look more closely at them later), but you don't need them when you're just trying to get a sense of what changes have been made in the working copy.

A more serious impediment to reading the diff is that CVS is announcing its entry as it goes into each directory during the update. This can be useful during long updates on large projects, as it gives you a sense of how much longer the command will take, but right now it's just getting in the way of reading the diff. Let's tell CVS to be quiet about where it's working, with the -Q global option:

     floss$ cvs -Q diff
     Index: hello.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.1.1.1
     diff -r1.1.1.1 hello.c
     6a7
     >   printf ("Goodbye, world!\n");
     

Better - at least some of the cruft is gone. However, the diff is still hard to read. It's telling you that at line 6, a new line was added (that is, what became line 7), whose contents were:

     printf ("Goodbye, world!\n");
     

The preceding ">" in the diff tells you that this line is present in the newer version of the file but not in the older one.

The format could be made even more readable, however. Most people find "context" diff format easier to read because it displays a few lines of context on either side of a change. Context diffs are generated by passing the -c flag to diff:

     floss$ cvs -Q diff -c
     Index: hello.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.1.1.1
     diff -c -r1.1.1.1 hello.c
     *** hello.c     1999/04/18 18:18:22     1.1.1.1
     --- hello.c     1999/04/19 02:17:07
     ***************
     *** 4,7 ****
     ---4,8 --
       main ()
       {
         printf ("Hello, world!\n");
     +   printf ("Goodbye, world!\n");
       }
     

Now that's clarity! Even if you're not used to reading context diffs, a glance at the preceding output will probably make it obvious what happened: a new line was added (the + in the first column signifies an added line) between the line that prints Hello, world! and the final curly brace.

We don't need to be able to read context diffs perfectly (that's patch's job), but it's worth taking the time to acquire at least a passing familiarity with the format. The first two lines (after the introductory cruft) are

     *** hello.c     1999/04/18 18:18:22     1.1.1.1
     --- hello.c     1999/04/19 02:17:07
     

and they tell you what is being diffed against what. In this case, revision 1.1.1.1 of hello.c is being compared against a modified version of the same file (thus, there's no revision number for the second line, because only the working copy's changes haven't been committed to the repository yet). The lines of asterisks and dashes identify sections farther down in the diff. Later on, a line of asterisks, with a line number range embedded, precedes a section from the original file. Then a line of dashes, with a new and potentially different line number range embedded, precedes a section from the modified file. These sections are organized into contrasting pairs (known as "hunks"), one side from the old file and the other side from the new.

Our diff has one hunk:

     ***************
     *** 4,7 ****
     --- 4,8 --
       main ()
       {
         printf ("Hello, world!\n");
     +   printf ("Goodbye, world!\n");
       }
     

The first section of the hunk is empty, meaning that no material was removed from the original file. The second section shows that, in the corresponding place in the new file, one line has been added; it's marked with a "+". (When diff quotes excerpts from files, it reserves the first two columns on the left for special codes, such as "+" so the entire excerpt appears to be indented by two spaces. This extra indentation is stripped off when the diff is applied, of course.)

The line number ranges show the hunk's coverage, including context lines. In the original file, the hunk was in lines 4 through 7; in the new file, it's lines 4 through 8 (because a line has been added). Note that the diff didn't need to show any material from the original file because nothing was removed; it just showed the range and moved on to the second half of the hunk.

Here's another context diff, from an actual project of mine:

     floss$ cvs -Q diff -c
     Index: cvs2cl.pl
     ===================================================================
     RCS file: /usr/local/cvs/kfogel/code/cvs2cl/cvs2cl.pl,v
     retrieving revision 1.76
     diff -c -r1.76 cvs2cl.pl
     *** cvs2cl.pl   1999/04/13 22:29:44     1.76
     --- cvs2cl.pl   1999/04/19 05:41:37
     ***************
     *** 212,218 ****
               # can contain uppercase and lowercase letters, digits, '-',
               # and '_'. However, it's not our place to enforce that, so
               # we'll allow anything CVS hands us to be a tag:
     !         /^\s([^:]+): ([0-9.]+)$/;
               push (@{$symbolic_names{$2}}, $1);
             }
           }
     -- 212,218 --
               # can contain uppercase and lowercase letters, digits, '-',
               # and '_'. However, it's not our place to enforce that, so
               # we'll allow anything CVS hands us to be a tag:
     !         /^\s([^:]+): ([\d.]+)$/;
               push (@{$symbolic_names{$2}}, $1);
             }
           }
     

The exclamation point shows that the marked line differs between the old and new files. Since there are no "+" or "-" signs, we know that the total number of lines in the file has remained the same.

Here's one more context diff from the same project, slightly more complex this time:

     floss$ cvs -Q diff -c
     Index: cvs2cl.pl
     ===================================================================
     RCS file: /usr/local/cvs/kfogel/code/cvs2cl/cvs2cl.pl,v
     retrieving revision 1.76
     diff -c -r1.76 cvs2cl.pl
     *** cvs2cl.pl   1999/04/13 22:29:44     1.76
     --- cvs2cl.pl   1999/04/19 05:58:51
     ***************
     *** 207,217 ****
     }
             else    # we're looking at a tag name, so parse & store it
             {
     -         # According to the Cederqvist manual, in node "Tags", "Tag
     -         # names must start with an uppercase or lowercase letter and
     -         # can contain uppercase and lowercase letters, digits, '-',
     -         # and '_'. However, it's not our place to enforce that, so
     -         # we'll allow anything CVS hands us to be a tag:
               /^\s([^:]+): ([0-9.]+)$/;
               push (@{$symbolic_names{$2}}, $1);
             }
     - 207,212 --
     ***************
     *** 223,228 ****
     --- 218,225 --
           if (/^revision (\d\.[0-9.]+)$/) {
             $revision = "$1";
           }
     +
     +     # This line was added, I admit, solely for the sake of a diff example.
     
           # If have file name but not time and author, and see date or
           # author, then grab them:
     

This diff has two hunks. In the first, five lines were removed (these lines are only shown in the first section of the hunk, and the second section's line count shows that it has five fewer lines). An unbroken line of asterisks forms the boundary between hunks, and in the second hunk we see that two lines have been added: a blank line and a pointless comment. Note how the line numbers compensate for the effect of the previous hunk. In the original file, the second hunk's range of the area was lines 223 through 228; in the new file, because of the deletion that took place in the first hunk, the range is in lines 218 through 225.

Congratulations, you are probably now as expert as you'll ever need to be at reading diffs.


Node: CVS And Implied Arguments, Next: , Previous: Finding Out What You (And Others) Did -- update And diff, Up: A Day With CVS

CVS And Implied Arguments

In each of the CVS commands so far, you may have noticed that no files were specified on the command line. We ran

     floss$ cvs diff
     

instead of

     floss$ cvs diff hello.c
     

and

     floss$ cvs update
     

instead of

     floss$ cvs update hello.c
     

The principle at work here is that if you don't name any files, CVS acts on all files for which the command could possibly be appropriate. This even includes files in subdirectories beneath the current directory; CVS automatically descends from the current directory through every subdirectory in the tree. For example, if you modified b-subdir/random.c and a-subdir/subsubdir/fish.c, running update may result in this:

     floss$ cvs update
     cvs update: Updating .
     M hello.c
     cvs update: Updating a-subdir
     cvs update: Updating a-subdir/subsubdir
     M a-subdir/subsubdir/fish.c
     cvs update: Updating b-subdir
     M b-subdir/random.c
     floss$
     

or better yet:

     floss$ cvs -q update
     M hello.c
     M a-subdir/subsubdir/fish.c
     M b-subdir/random.c
     floss$
     

Note: The -q flag is a less emphatic version of -Q. Had we used -Q, the command would have printed out nothing at all, because the modification notices are considered nonessential informational messages. Using the lowercase -q is less strict; it suppresses the messages we probably don't want, while allowing certain, more useful messages to pass through.

You can also name specific files for the update:

     floss$ cvs update hello.c b-subdir/random.c
     M hello.c
     M b-subdir/random.c
     floss$
     

and CVS will only examine those files, ignoring all others.

In truth, it's more common to run update without restricting it to certain files. In most situations, you'll want to update the entire directory tree at once. Remember, the updates we're doing here only show that some files have been locally modified, because nothing has changed yet in the repository. When other people are working on the project with you, there's always the chance that running update will pull some new changes down from the repository and incorporate them into your local files. In that case, you may find it slightly more useful to name which files you want updated.

The same principle can be applied to other CVS commands. For example, with diff, you can choose to view the changes one file at a time

     floss$ cvs diff -c b-subdir/random.c
     Index: b-subdir/random.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
     retrieving revision 1.1.1.1
     diff -c -r1.1.1.1 random.c
     *** b-subdir/random.c   1999/04/18 18:18:22     1.1.1.1
     --- b-subdir/random.c   1999/04/19 06:09:48
     ***************
     *** 1 ****
     ! /* A completely empty C file. */
     --- 1,8 --
     ! /* Print out a random number. */
     !
     ! #include <stdio.h>
     !
     ! void main ()
     ! {
     !   printf ("a random number\n");
     ! }
     

or see all the changes at once (hang on to your seat, this is going to be a big diff):

     floss$ cvs -Q diff -c
     Index: hello.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.1.1.1
     diff -c -r1.1.1.1 hello.c
     *** hello.c     1999/04/18 18:18:22     1.1.1.1
     --- hello.c     1999/04/19 02:17:07
     ***************
     *** 4,7 ****
     --- 4,8 --
       main ()
       {
         printf ("Hello, world!\n");
     +   printf ("Goodbye, world!\n");
       }
     Index: a-subdir/subsubdir/fish.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v
     retrieving revision 1.1.1.1
     diff -c -r1.1.1.1 fish.c
     *** a-subdir/subsubdir/fish.c   1999/04/18 18:18:22     1.1.1.1
     --- a-subdir/subsubdir/fish.c   1999/04/19 06:08:50
     ***************
     *** 1 ****
     ! /* A completely empty C file. */
     --- 1,8 --
     ! #include <stdio.h>
     !
     ! void main ()
     ! {
     !   while (1) {
     !     printf ("fish\n");
     !   }
     ! }
     Index: b-subdir/random.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
     retrieving revision 1.1.1.1
     diff -c -r1.1.1.1 random.c
     *** b-subdir/random.c   1999/04/18 18:18:22     1.1.1.1
     --- b-subdir/random.c   1999/04/19 06:09:48
     ***************
     *** 1 ****
     ! /* A completely empty C file. */
     --- 1,8 --
     ! /* Print out a random number. */
     !
     ! #include <stdio.h>
     !
     ! void main ()
     ! {
     !   printf ("a random number\n");
     ! }
     

Anyway, as you can see from these diffs, this project is clearly ready for prime time. Let's commit the changes to the repository.


Node: Committing, Next: , Previous: CVS And Implied Arguments, Up: A Day With CVS

Committing

The commit command sends modifications to the repository. If you don't name any files, a commit will send all changes to the repository; otherwise, you can pass the names of one or more files to be committed (other files would be ignored, in that case).

Here, we commit one file by name and two by inference:

     floss$ cvs commit -m "print goodbye too" hello.c
     Checking in hello.c;
     /usr/local/cvs/myproj/hello.c,v  <--  hello.c
     new revision: 1.2; previous revision: 1.1
     done
     floss$ cvs commit -m "filled out C code"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in a-subdir/subsubdir/fish.c;
     /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v  <--  fish.c
     new revision: 1.2; previous revision: 1.1
     done
     Checking in b-subdir/random.c;
     /usr/local/cvs/myproj/b-subdir/random.c,v  <--  random.c
     new revision: 1.2; previous revision: 1.1
     done
     floss$
     

Take a moment to read over the output carefully. Most of what it says is pretty self-explanatory. One thing you may notice is that revision numbers have been incremented (as expected), but the original revisions are listed as 1.1 instead of 1.1.1.1 as we saw in the Entries file earlier.

There is an explanation for this discrepancy, but it's not very important. It concerns a special meaning that CVS attaches to revision 1.1.1.1. For most purposes, we can just say that files receive a revision number of 1.1 when imported, but the number is displayed - for reasons known only to CVS - as 1.1.1.1 in the Entries file, until the first commit.


Node: Revision Numbers, Next: , Previous: Committing, Up: A Day With CVS

Revision Numbers

Each file in a project has its own revision number. When a file is committed, the last portion of the revision number is incremented by one. Thus, at any given time, the various files comprising a project may have very different revision numbers. This just means that some files have been changed (committed) more often than others.

(You may be wondering, what's the point of the part to the left of the decimal point, if only the part on the right ever changes? Actually, although CVS never automatically increments the number on the left, that number can be incremented on request by a user. This is a rarely used feature, and we won't cover it in this tour.)

In the example project that we've been using, we just committed changes to three files. Each of those files is now revision 1.2, but the remaining files in the project are still revision 1.1. When you check out a project, you get each file at its highest revision so far. Here is what qsmith would see if he checked out myproj right now and looked at the revision numbers for the top-level directory:

     paste$ cvs -q -d :pserver:[email protected]:/usr/local/cvs co myproj
     U myproj/README.txt
     U myproj/hello.c
     U myproj/a-subdir/whatever.c
     U myproj/a-subdir/subsubdir/fish.c
     U myproj/b-subdir/random.c
     paste$ cd myproj/CVS
     paste$ cat Entries
     /README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//
     /hello.c/1.2/Mon Apr 19 06:35:15 1999//
     D/a-subdir////
     D/b-subdir////
     paste$
     

The file hello.c (among others) is now at revision 1.2, while README.txt is still at the initial revision (revision 1.1.1.1, also known as 1.1).

If he adds the line

     printf ("between hello and goodbye\n");
     

to hello.c and commit it, the file's revision number will be incremented once more:

     paste$ cvs ci -m "added new middle line"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in hello.c;
     /usr/local/cvs/myproj/hello.c,v  <--  hello.c
     new revision: 1.3; previous revision: 1.2
     done
     paste$
     

Now hello.c is revision 1.3, fish.c and random.c still are revision 1.2, and every other file is revision 1.1.

Note: that the command was given as cvs ci instead of cvs commit. Most CVS commands have short forms, to make typing easier. For checkout, update, and commit, the abbreviated versions are co, up, and ci, respectively. You can get a list of all of the short forms by running the command cvs --help-synonyms.

You can usually ignore a file's revision number. In most situations, the numbers are just internal bookkeeping that CVS handles automatically. However, being able to find and compare revision numbers is extremely handy when you have to retrieve (or diff against) an earlier copy of a file.

Examining the Entries file isn't the only way to discover a revision number. You can also use the status command

     paste$ cvs status hello.c
     ===================================================================
     File: hello.c           Status: Up-to-date
     
        Working revision:    1.3     Tue Apr 20 02:34:42 1999
        Repository revision: 1.3     /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     

which, if invoked without any files being named, shows the status of every file in the project:

     paste$ cvs status
     cvs status: Examining.
     ===================================================================
     File: README.txt        Status: Up-to-date
     
        Working revision:    1.1.1.1 Sun Apr 18 18:18:22 1999
        Repository revision: 1.1.1.1 /usr/local/cvs/myproj/README.txt,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     
     ===================================================================
     File: hello.c           Status: Up-to-date
     
        Working revision:    1.3     Tue Apr 20 02:34:42 1999
        Repository revision: 1.3     /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     
     cvs status: Examining a-subdir
     ===================================================================
     File: whatever.c        Status: Up-to-date
     
        Working revision:    1.1.1.1 Sun Apr 18 18:18:22 1999
        Repository revision: 1.1.1.1 /usr/local/cvs/myproj/a-subdir/whatever.c,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     
     cvs status: Examining a-subdir/subsubdir
     ===================================================================
     File: fish.c            Status: Up-to-date
     
        Working revision:    1.2     Mon Apr 19 06:35:27 1999
        Repository revision: 1.2     /usr/local/cvs/myproj/
                                     a-subdir/subsubdir/fish.c,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     
     cvs status: Examining b-subdir
     ===================================================================
     File: random.c          Status: Up-to-date
     
        Working revision:    1.2     Mon Apr 19 06:35:27 1999
        Repository revision: 1.2     /usr/local/cvs/myproj/b-subdir/random.c,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     
     paste$
     

Just ignore the parts of that output that you don't understand. In fact, that's generally good advice with CVS. Often, the one little bit of information you're looking for will be accompanied by reams of information that you don't care about at all, and maybe don't even understand. This situation is normal. Just pick out what you need, and don't worry about the rest.

In the previous example, the parts we care about are the first three lines (not counting the blank line) of each file's status output. The first line is the most important; it tells you the file's name, and its status in the working copy. All of the files are currently in sync with the repository, so they all say Up-to-date. However, if random.c has been modified but not committed, it might read like this:

     ===================================================================
     File: random.c          Status: Locally Modified
     
        Working revision:    1.2     Mon Apr 19 06:35:27 1999
        Repository revision: 1.2     /usr/local/cvs/myproj/b-subdir/random.c,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     

The Working revision and Repository revision tell you whether the file is out of sync with the repository. Returning to our original working copy (jrandom's copy, which hasn't seen the new change to hello.c yet), we see:

     floss$ cvs status hello.c
     ===================================================================
     File: hello.c           Status: Needs Patch
     
        Working revision:    1.2     Mon Apr 19 02:17:07 1999
        Repository revision: 1.3     /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     
     floss$
     

This tells us that someone has committed a change to hello.c, bringing the repository copy to revision 1.3, but that this working copy is still on revision 1.2. The line Status: Needs Patch means that the next update will retrieve those changes from the repository and "patch" them into the working copy's file.

Let's pretend for the moment that we don't know anything about qsmith's change to hello.c, so we don't run status or update. Instead, we just start editing the file, making a slightly different change at the same location. This brings us to our first conflict.


Node: Detecting And Resolving Conflicts, Next: , Previous: Revision Numbers, Up: A Day With CVS

Detecting And Resolving Conflicts

Detecting a conflict is easy enough. When you run update, CVS tells you, in no uncertain terms, that there's a conflict. But first, let's create the conflict. We edit hello.c to insert the line

     printf ("this change will conflict\n");
     

right where qsmith committed this:

     printf ("between hello and goodbye\n");
     

At this point, the status of our copy of hello.c is

     floss$ cvs status hello.c
     ===================================================================
     File: hello.c           Status: Needs Merge
     
        Working revision:    1.2     Mon Apr 19 02:17:07 1999
        Repository revision: 1.3     /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:          (none)
        Sticky Date:         (none)
        Sticky Options:      (none)
     
     floss$
     

meaning that there are changes both in the repository and the working copy, and these changes need to be merged. (CVS isn't aware that the changes will conflict, because we haven't run update yet.) When we do the update, we see this:

     floss$ cvs update hello.c
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.2
     retrieving revision 1.3
     Merging differences between 1.2 and 1.3 into hello.c
     rcsmerge: warning: conflicts during merge
     cvs update: conflicts found in hello.c
     C hello.c
     floss$
     

The last line of output is the giveaway. The C in the left margin next to the filename indicates that changes have been merged, but that they conflict. The contents of hello.c now shows both changes:

     #include <stdio.h>
     
     void
     main ()
     {
       printf ("Hello, world!\n");
     <<<<<<< hello.c
       printf ("this change will conflict\n");
     =======
       printf ("between hello and goodbye\n");
     >>>>>>> 1.3
       printf ("Goodbye, world!\n");
     }
     

Conflicts are always shown delimited by conflict markers, in the following format:

     <<<<<<< (filename)
       the uncommitted changes in the working copy
       blah blah blah
     =======
       the new changes that came from the repository
       blah blah blah
       and so on
     >>>>>>> (latest revision number in the repository)
     

The Entries file also shows that the file is in a halfway state at the moment:

     floss$ cat CVS/Entries
     /README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//
     D/a-subdir////
     D/b-subdir////
     /hello.c/1.3/Result of merge+Tue Apr 20 03:59:09 1999//
     floss$
     

The way to resolve the conflict is to edit the file so that it contains whatever text is appropriate, removing the conflict markers in the process, and then to commit. This doesn't necessarily mean choosing one change over another; you could decide neither change is sufficient and rewrite the conflicting section (or indeed the whole file) completely. In this case, we'll adjust in favor of the first change, but with capitalization and punctuation slightly different from qsmith's:

     floss$ emacs hello.c
       (make the edits...)
     floss$ cat hello.c
     #include <stdio.h>
     
     void
     main ()
     {
       printf ("Hello, world!\n");
       printf ("BETWEEN HELLO AND GOODBYE.\n");
       printf ("Goodbye, world!\n");
     }
     floss$ cvs ci -m "adjusted middle line"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in hello.c;
     /usr/local/cvs/myproj/hello.c,v  <-  hello.c
     new revision: 1.4; previous revision: 1.3
     done
     floss$
     


Node: Finding Out Who Did What (Browsing Log Messages), Next: , Previous: Detecting And Resolving Conflicts, Up: A Day With CVS

Finding Out Who Did What (Browsing Log Messages)

By now, the project has undergone several changes. If you're trying to get an overview of what has happened so far, you don't necessarily want to examine every diff in detail. Browsing the log messages would be ideal, and you can accomplish this with the log command:

     floss$ cvs log
     (pages upon pages of output omitted)
     

The log output tends to be a bit verbose. Let's look at the log messages for just one file:

     floss$ cvs log hello.c
     RCS file: /usr/local/cvs/myproj/hello.c,v
     Working file: hello.c
     head: 1.4
     branch:
     locks: strict
     access list:
     symbolic names:
             start: 1.1.1.1
             jrandom: 1.1.1
     keyword substitution: kv
     total revisions: 5;     selected revisions: 5
     description:
     --------------
     revision 1.4
     date: 1999/04/20 04:14:37;  author: jrandom;  state: Exp;  lines: +1 -1
     adjusted middle line
     --------------
     revision 1.3
     date: 1999/04/20 02:30:05;  author: qsmith;  state: Exp;  lines: +1 -0
     added new middle line
     --------------
     revision 1.2
     date: 1999/04/19 06:35:15;  author: jrandom;  state: Exp;  lines: +1 -0
     print goodbye too
     --------------
     revision 1.1
     date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;
     branches:  1.1.1;
     Initial revision
     --------------
     revision 1.1.1.1
     date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;  lines: +0 -0
     initial import into CVS
     =========================================================================
     floss$
     

As usual, there's a lot of information at the top that you can just ignore. The good stuff comes after each line of dashes, in a format that is self-explanatory.

When many files are sent in the same commit, they all share the same log message; a fact that can be useful in tracing changes. For example, remember back when we committed fish.c and random.c simultaneously? It was done like this:

     floss$ cvs commit -m "filled out C code"
     Checking in a-subdir/subsubdir/fish.c;
     /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v  <-  fish.c
     new revision: 1.2; previous revision: 1.1
     done
     Checking in b-subdir/random.c;
     /usr/local/cvs/myproj/b-subdir/random.c,v  <-  random.c
     new revision: 1.2; previous revision: 1.1
     done
     floss$
     

The effect of this was to commit both files with the same log message: "Filled out C code." (As it happened, both files started at revision 1.1 and went to 1.2, but that's just a coincidence. If random.c had been at revision 1.29, it would have moved to 1.30 with this commit, and its revision 1.30 would have had the same log message as fish.c's revision 1.2.)

When you run cvs log on them, you'll see the shared message:

     floss$ cvs log a-subdir/subsubdir/fish.c b-subdir/random.c
     
     RCS file: /usr/local/cvs/myproj/a-subdir/subsubdir/fish.c,v
     Working file: a-subdir/subsubdir/fish.c
     head: 1.2
     branch:
     locks: strict
     access list:
     symbolic names:
             start: 1.1.1.1
             jrandom: 1.1.1
     keyword substitution: kv
     total revisions: 3;     selected revisions: 3
     description:
     --------------
     revision 1.2
     date: 1999/04/19 06:35:27;  author: jrandom;  state: Exp;  lines: +8 -1
     filled out C code
     --------------
     revision 1.1
     date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;
     branches:  1.1.1;
     Initial revision
     --------------
     revision 1.1.1.1
     date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;  lines: +0 -0
     initial import into CVS
     =========================================================================
     RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
     Working file: b-subdir/random.c
     head: 1.2
     branch:
     locks: strict
     access list:
     symbolic names:
             start: 1.1.1.1
             jrandom: 1.1.1
     keyword substitution: kv
     total revisions: 3;     selected revisions: 3
     description:
     --------------
     revision 1.2
     date: 1999/04/19 06:35:27;  author: jrandom;  state: Exp;  lines: +8 -1
     filled out C code
     --------------
     revision 1.1
     date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;
     branches:  1.1.1;
     Initial revision
     --------------
     revision 1.1.1.1
     date: 1999/04/18 18:18:22;  author: jrandom;  state: Exp;  lines: +0 -0
     initial import into CVS
     =========================================================================
     floss$
     

From this output, you'll know that the two revisions were part of the same commit (the fact that the timestamps on the two revisions are the same, or very close, is further evidence).

Browsing log messages is a good way to get a quick overview of what's been going on in a project or to find out what happened to a specific file at a certain time. There are also free tools available to convert raw cvs log output to more concise and readable formats (such as GNU ChangeLog style); we won't cover those tools in this tour, but they'll be introduced in Third-Party Tools.


Node: Examining And Reverting Changes, Next: , Previous: Finding Out Who Did What (Browsing Log Messages), Up: A Day With CVS

Examining And Reverting Changes

Suppose that, in the course of browsing the logs, qsmith sees that jrandom made the most recent change to hello.c:

     revision 1.4
     date: 1999/04/20 04:14:37;  author: jrandom;  state: Exp;  lines: +1 -1
     adjusted middle line
     

and wonders what jrandom did? In formal terms, the question that qsmith is asking is, "What's the difference between my revision (1.3) of hello.c, and jrandom's revision right after it (1.4)?" The way to find out is with the diff command, but this time by comparing two past revisions using the -r command option to specify both of them:

     paste$ cvs diff -c -r 1.3 -r 1.4 hello.c
     Index: hello.c
     ===========================================================
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.3
     retrieving revision 1.4
     diff -c -r1.3 -r1.4
     *** hello.c     1999/04/20 02:30:05     1.3
     --- hello.c     1999/04/20 04:14:37     1.4
     ***************
     *** 4,9 ****
       main ()
       {
         printf ("Hello, world!\n");
     !   printf ("between hello and goodbye\n");
         printf ("Goodbye, world!\n");
       }
     --- 4,9 --
       main ()
       {
         printf ("Hello, world!\n");
     !   printf ("BETWEEN HELLO AND GOODBYE.\n");
         printf ("Goodbye, world!\n");
       }
     paste$
     

The change is pretty clear, when viewed this way. Because the revision numbers are given in chronological order (usually a good idea), the diff shows them in order. If only one revision number is given, CVS uses the revision of the current working copy for the other.

When qsmith sees this change, he instantly decides he likes his way better and resolves to "undo"-that is, to step back by one revision.

However, this doesn't mean that he wants to lose his revision 1.4. Although, in an absolute technical sense, it's probably possible to achieve that effect in CVS, there's almost never any reason to do so. It's much preferable to keep revision 1.4 in the history and make a new revision 1.5 that looks exactly like 1.3. That way the undo event itself is part of the file's history.

The only question is, how can you retrieve the contents of revision 1.3 and put them into 1.5?

In this particular case, because the change is a very simple one, qsmith can probably just edit the file by hand to mirror revision 1.3 and then commit. However, if the changes are more complex (as they usually are in a real-life project), trying to re-create the old revision manually will be hopelessly error-prone. Therefore, we'll have qsmith use CVS to retrieve and recommit the older revision's contents.

There are two equally good ways to do this: the slow, plodding way and the fast, fancy way. We'll examine the slow, plodding way first.


Node: The Slow Method Of Reverting, Next: , Previous: Examining And Reverting Changes, Up: A Day With CVS

The Slow Method Of Reverting

This method involves passing the -p flag to update, in conjunction with -r. The -p option sends the contents of the named revision to standard output. By itself, this isn't terribly helpful; the contents of the file fly by on the display, leaving the working copy unchanged. However, by redirecting the standard output into the file, the file will now hold the contents of the older revision. It's just as though the file had been hand-edited into that state.

First, though, qsmith needs to get up to date with respect to the repository:

     paste$ cvs update
     cvs update: Updating .
     U hello.c
     cvs update: Updating a-subdir
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir
     paste$ cat hello.c
     #include <stdio.h>
     
     void
     main ()
     {
       printf ("Hello, world!\n");
       printf ("BETWEEN HELLO AND GOODBYE.\n");
       printf ("Goodbye, world!\n");
     }
     paste$
     

Next, he runs update -p to make sure that the revision 1.3 is the one he wants:

     paste$ cvs update -p -r 1.3 hello.c
     ===================================================================
     Checking out hello.c
     RCS:  /usr/local/cvs/myproj/hello.c,v
     VERS: 1.3
     ***************
     #include <stdio.h>
     
     void
     main ()
     {
       printf ("Hello, world!\n");
       printf ("between hello and goodbye\n");
       printf ("Goodbye, world!\n");
     }
     

Oops, there are a few lines of cruft at the beginning. They aren't actually being sent to standard output, but rather to standard error, so they're harmless. Nevertheless, they make reading the output more difficult and can be suppressed with -Q:

     paste$ cvs -Q update -p -r 1.3 hello.c
     #include <stdio.h>
     
     void
     main ()
     {
       printf ("Hello, world!\n");
       printf ("between hello and goodbye\n");
       printf ("Goodbye, world!\n");
     }
     paste$
     

There - that's exactly what qsmith was hoping to retrieve. The next step is to put that content into the working copy's file, using a Unix redirect (that's what the ">" does):

     paste$ cvs -Q update -p -r 1.3 hello.c > hello.c
     paste$ cvs update
     cvs update: Updating .
     M hello.c
     cvs update: Updating a-subdir
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir
     paste$
     

Now when update is run, the file is listed as modified, which makes sense because its contents have changed. Specifically, it has the same content as the old revision 1.3 (not that CVS is aware of its being identical to a previous revision - it just knows the file has been modified). If qsmith wants to make extra sure, he can do a diff to check:

     paste$ cvs -Q diff -c
     Index: hello.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.4
     diff -c -r1.4 hello.c
     *** hello.c     1999/04/20 04:14:37     1.4
     --- hello.c     1999/04/20 06:02:25
     ***************
     *** 4,9 ****
       main ()
       {
         printf ("Hello, world!\n");
     !   printf ("BETWEEN HELLO AND GOODBYE.\n");
         printf ("Goodbye, world!\n");
       }
     --- 4,9 --
       main ()
       {
         printf ("Hello, world!\n");
     !   printf ("between hello and goodbye\n");
         printf ("Goodbye, world!\n");
       }
     paste$
     

Yes, that's exactly what he wanted: a pure reversion - in fact, it is the reverse of the diff he previously obtained. Satisfied, he commits:

     paste$ cvs ci -m "reverted to 1.3 code"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in hello.c;
     /usr/local/cvs/myproj/hello.c,v  <-  hello.c
     new revision: 1.5; previous revision: 1.4
     done
     paste$
     


Node: The Fast Method Of Reverting, Previous: The Slow Method Of Reverting, Up: A Day With CVS

The Fast Method Of Reverting

The fast, fancy way of reverting is to use the -j (for "join") flag to the update command. This flag is like -r in that it takes a revision number, and you can use up to two -j's at once. CVS calculates the difference between the two named revisions and applies that difference as a patch to the file in question (so the order in which you give the revisions is important).

Thus, assuming qsmith's copy is up to date, he can just do this:

     paste$ cvs update -j 1.4 -j 1.3 hello.c
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.4
     retrieving revision 1.3
     Merging differences between 1.4 and 1.3 into hello.c
     paste$ cvs update
     cvs update: Updating .
     M hello.c
     cvs update: Updating a-subdir
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir
     paste$ cvs ci -m "reverted to 1.3 code" hello.c
     Checking in hello.c;
     /usr/local/cvs/myproj/hello.c,v  <--  hello.c
     new revision: 1.5; previous revision: 1.4
     done
     paste$
     

When you only need to revert one file, there's not really much difference between the plodding and fast methods. Later in the book, you'll see how the fast method is much better for reverting multiple files at once. In the meantime, use whichever way you're more comfortable with.

Reverting Is Not A Substitute For Communication

In all likelihood, what qsmith did in our example was quite rude. When you're working on a real project with other people and you think that someone has committed a bad change, the first thing you should do is talk to him or her about it. Maybe there's a good reason for the change, or maybe he or she just didn't think things through. Either way, there's no reason to rush and revert. A full record of everything that happens is stored permanently in CVS, so you can always revert to a previous revision after consulting with whoever made the changes.

If you're a project maintainer facing a deadline or you feel you have the right and the need to revert the change unconditionally, then do so - but follow it immediately with an email to the author whose change was reverted, explaining why you did it and what needs to be fixed to recommit the change.


Node: Other Useful CVS Commands, Next: , Previous: A Day With CVS, Up: An Overview of CVS

Other Useful CVS Commands

At this point, you should be pretty comfortable with basic CVS usage. I'll abandon the tour narrative and introduce a few more useful commands in summarized form.


Node: Adding Files, Next: , Up: Other Useful CVS Commands

Adding Files

Adding a file is a two-step process: First you run the add command on it, then commit. The file won't actually appear in the repository until commit is run:

     floss$ cvs add newfile.c
     cvs add: scheduling file 'newfile.c' for addition
     cvs add: use 'cvs commit' to add this file permanently
     floss$ cvs ci -m "added newfile.c" newfile.c
     RCS file: /usr/local/cvs/myproj/newfile.c,v
     done
     Checking in newfile.c;
     /usr/local/cvs/myproj/newfile.c,v  <-  newfile.c
     initial revision: 1.1
     done
     floss$
     


Node: Adding Directories, Next: , Previous: Adding Files, Up: Other Useful CVS Commands

Adding Directories

Unlike adding a file, adding a new directory is done in one step; there's no need to do a commit afterwards:

     floss$ mkdir c-subdir
     floss$ cvs add c-subdir
     Directory /usr/local/cvs/myproj/c-subdir added to the repository
     floss$
     

If you look inside the new directory in the working copy, you'll see that a CVS subdirectory was created automatically by add:

     floss$ ls c-subdir
     CVS/
     floss$ ls c-subdir/CVS
     Entries     Repository  Root
     floss$
     

Now you can add files (or new directories) inside it, as with any other working copy directory.


Node: CVS And Binary Files, Next: , Previous: Adding Directories, Up: Other Useful CVS Commands

CVS And Binary Files

Until now, I've left unsaid the dirty little secret of CVS, which is that it doesn't handle binary files very well (well, there are other dirty little secrets, but this definitely counts as one of the dirtiest). It's not that CVS doesn't handle binaries at all; it does, just not with any great panache.

All the files we've been working with until now have been plain text files. CVS has some special tricks for text files. For example, when it's working between a Unix repository and a Windows or Macintosh working copy, it converts file line endings appropriately for each platform. For example, Unix convention is to use a linefeed (LF) only, whereas Windows expects a carriage return/linefeed (CRLF) sequence at the end of each line. Thus, the files in a working copy on a Windows machine will have CRLF endings, but a working copy of the same project on a Unix machine will have LF endings (the repository itself is always stored in LF format).

Another trick is that CVS detects special strings, known as RCS keyword strings, in text files and replaces them with revision information and other useful things. For example, if your file contains this string

     $Revision$
     

CVS will expand on each commit to include the revision number. For example, it may get expanded to

     $Revision: 1.3 $
     

CVS will keep that string up to date as the file is developed. (The various keyword strings are documented in Advanced CVS and Third-Party Tools.)

This string expansion is a very useful feature in text files, as it allows you to see the revision number or other information about a file while you're editing it. But what if the file is a JPG image? Or a compiled executable program? In those kinds of files, CVS could do some serious damage if it blundered around expanding any keyword string that it encountered. In a binary, such strings may even appear by coincidence.

Therefore, when you add a binary file, you have to tell CVS to turn off both keyword expansion and line-ending conversion. To do so, use -kb:

     floss$ cvs add -kb filename
     floss$ cvs ci -m "added blah" filename
       (etc)
     

Also, in some cases (such as text files that are likely to contain spurious keyword strings), you may wish to disable just the keyword expansion. That's done with -ko:

     floss$ cvs add -ko filename
     floss$ cvs ci -m "added blah" filename
       (etc)
     

(In fact, this chapter is one such document, because of the $Revision$ example shown here.)

Note that you can't meaningfully run cvs diff on two revisions of a binary file. Diff uses a text-based algorithm that can only report whether two binary files differ, but not how they differ. Future versions of CVS may provide a way to diff binary files.


Node: Removing Files, Next: , Previous: CVS And Binary Files, Up: Other Useful CVS Commands

Removing Files

Removing a file is similar to adding one, except there's an extra step: You have to remove the file from the working copy first:

     floss$ rm newfile.c
     floss$ cvs remove newfile.c
     cvs remove: scheduling 'newfile.c' for removal
     cvs remove: use 'cvs commit' to remove this file permanently
     floss$ cvs ci -m "removed newfile.c" newfile.c
     Removing newfile.c;
     /usr/local/cvs/myproj/newfile.c,v  <-  newfile.c
     new revision: delete; previous revision: 1.1
     done
     floss$
     

Notice how, in the second and third commands, we name newfile.c explicitly even though it doesn't exist in the working copy anymore. Of course, in the commit, you don't absolutely need to name the file, as long as you don't mind the commit encompassing any other modifications that may have taken place in the working copy.


Node: Removing Directories, Next: , Previous: Removing Files, Up: Other Useful CVS Commands

Removing Directories

As I said before, CVS doesn't really keep directories under version control. Instead, as a kind of cheap substitute, it offers certain odd behaviors that in most cases do the "right thing". One of these odd behaviors is that empty directories can be treated specially. If you want to remove a directory from a project, you first remove all the files in it

     floss$ cd dir
     floss$ rm file1 file2 file3
     floss$ cvs remove file1 file2 file3
       (output omitted)
     floss$ cvs ci -m "removed all files" file1 file2 file3
       (output omitted)
     

and then run update in the directory above it with the -P flag:

     floss$ cd ..
     floss$ cvs update -P
       (output omitted)
     

The -P option tells update to "prune" any empty directories - that is, to remove them from the working copy. Once that's done, the directory can be said to have been removed; all of its files are gone, and the directory itself is gone (from the working copy, at least, although there is actually still an empty directory in the repository).

An interesting counterpart to this behavior is that when you run a plain update, CVS does not automatically bring new directories from the repository into your working copy. There are a couple of different justifications for this, none really worth going into here. The short answer is that from time to time you should run update with the -d flag, telling it to bring down any new directories from the repository.


Node: Renaming Files And Directories, Next: , Previous: Removing Directories, Up: Other Useful CVS Commands

Renaming Files And Directories

Renaming a file is equivalent to creating it under the new name and removing it under the old. In Unix, the commands are:

     floss$ cp oldname newname
     floss$ rm oldname
     

Here's the equivalent in CVS:

     floss$ mv oldname newname
     floss$ cvs remove oldname
       (output omitted)
     floss$ cvs add newname
       (output omitted)
     floss$ cvs ci -m "renamed oldname to newname" oldname newname
       (output omitted)
     floss$
     

For files, that's all there is to it. Renaming directories is not done very differently: create the new directory, cvs add it, move all the files from the old directory to the new one, cvs remove them from the old directory, cvs add them in the new one, cvs commit so everything takes effect, and then do cvs update -P to make the now-empty directory disappear from the working copy. That is to say:

     floss$ mkdir newdir
     floss$ cvs add newdir
     floss$ mv olddir/* newdir
     mv: newdir/CVS: cannot overwrite directory
     floss$ cd olddir
     floss$ cvs rm foo.c bar.txt
     floss$ cd ../newdir
     floss$ cvs add foo.c bar.txt
     floss$ cd ..
     floss$ cvs commit -m "moved foo.c and bar.txt from olddir to newdir"
     floss$ cvs update -P
     

Note: the warning message after the third command. It's telling you that it can't copy olddir's CVS/ subdirectory into newdir because newdir already has a directory of that name. This is fine, because you want olddir to keep its CVS/ subdirectory anyway.

Obviously, moving directories around can get a bit cumbersome. The best policy is to try to come up with a good layout when you initially import your project so you won't have to move directories around very often. Later, you'll learn about a more drastic method of moving directories that involves making the change directly in the repository. However, that method is best saved for emergencies; whenever possible, it's best to handle everything with CVS operations inside working copies.


Node: Avoiding Option Fatigue, Next: , Previous: Renaming Files And Directories, Up: Other Useful CVS Commands

Avoiding Option Fatigue

Most people tire pretty quickly of typing the same option flags with every command. If you know that you always want to pass the -Q global option or you always want to use -c with diff, why should you have to type it out each time?

There is help, fortunately. CVS looks for a .cvsrc file in your home directory. In that file, you can specify default options to apply to every invocation of CVS. Here's an example .cvsrc:

     diff -c
     update -P
     cvs -q
     

If the leftmost word on a line matches a CVS command (in its unabbreviated form), the corresponding options are used for that command every time. For global options, you just use cvs. So, for example, every time that user runs cvs diff, the -c flag is automatically included.


Node: Getting Snapshots (Dates And Tagging), Next: , Previous: Avoiding Option Fatigue, Up: Other Useful CVS Commands

Getting Snapshots (Dates And Tagging)

Let's return to the example of the program that's in a broken state when a bug report comes in. The developer suddenly needs access to the entire project as it was at the time of the last release, even though many files may have been changed since then, and each file's revision number differs from the others. It would be far too time-consuming to look over the log messages, figure out what each file's individual revision number was at the time of release, and then run update (specifying a revision number with -r) on each one of them. In medium- to large-sized projects (tens to hundreds of files), such a process would be too unwieldy to attempt.

CVS, therefore, provides a way to retrieve previous revisions of the files in a project en masse. In fact, it provides two ways: by date, which selects the revisions based on the time that they were committed, and by tag, which retrieves a previously marked "snapshot" of the project.

Which method you use depends on the situation. The date-based retrievals are done by passing update the -D flag, which is similar to -r but takes dates instead of revision numbers:

     floss$ cvs -q update -D "1999-04-19"
     U hello.c
     U a-subdir/subsubdir/fish.c
     U b-subdir/random.c
     floss$
     

With the -D option, update retrieves the highest revision of each file as of the given date, and it will revert the files in the working copy to prior revisions if necessary.

When you give the date, you can, and often should, include the time. For example, the previous command ended up retrieving revision 1.1 of everything (only three files showed changes, because all of the others are still at revision 1.1 anyway). Here's the status of hello.c to prove it:

     floss$ cvs -Q status hello.c
     ===================================================================
     File: hello.c                 Status: Up-to-date
        Working revision:          1.1.1.1 Sat Apr 24 22:45:03 1999
        Repository revision:       1.1.1.1 /usr/local/cvs/myproj/hello.c,v
        Sticky Date:               99.04.19.05.00.00
     floss$
     

But a glance back at the log messages from earlier in this chapter shows that revision 1.2 of hello.c was definitely committed on April 19, 1999. So why did we now get revision 1.1 instead of 1.2?

The problem is that the date "1999-04-19" was interpreted as meaning "the midnight that begins 1999-04-19" - that is, the very first instant on that date. This is probably not what you want. The 1.2 commit took place later in the day. By qualifying the date more precisely, we can retrieve revision 1.2:

     floss$ cvs -q update -D "1999-04-19 23:59:59"
     U hello.c
     U a-subdir/subsubdir/fish.c
     U b-subdir/random.c
     floss$ cvs status hello.c
     ===================================================================
     File: hello.c                 Status: Locally Modified
        Working revision:  1.2     Sat Apr 24 22:45:22 1999
        Repository revision:       1.2     /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:                (none)
        Sticky Date:               99.04.20.04.59.59
        Sticky Options:    (none)
     floss$
     

We're almost there. If you look closely at the date/time on the Sticky Date line, it seems to indicate 4:59:59 A.M., not 11:59 as the command requested (later we'll get to what the "sticky" means). As you may have guessed, the discrepancy is due to the difference between local time and Universal Coordinated Time (also known as "Greenwich mean time"). The repository always stores dates in Universal Time, but CVS on the client side usually assumes the local system time zone. In the case of -D, this is rather unfortunate because you're probably most interested in comparing against the repository time and don't care about the local system's idea of time. You can get around this by specifying the GMT zone in the command:

     floss$ cvs -q update -D "1999-04-19 23:59:59 GMT"
     U hello.c
     floss$ cvs -q status hello.c
     ===================================================================
     File: hello.c                 Status: Up-to-date
        Working revision:  1.2     Sun Apr 25 22:38:53 1999
        Repository revision:       1.2     /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:                (none)
        Sticky Date:               99.04.19.23.59.59
        Sticky Options:    (none)
     floss$
     

There - that brought the working copy back to the final commits from April 19 (unless there were any commits during the last second of the day, which there weren't).

What happens now if you run update?

     floss$ cvs update
     cvs update: Updating .
     cvs update: Updating a-subdir
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir
     floss$
     

Nothing happens at all. But you know that there are more recent versions of at least three files. Why aren't these included in your working copy?

That's where the "sticky" comes in. Updating ("downdating"?) with the -D flag causes the working copy to be restricted permanently to that date or before. In CVS terminology, the working copy has a "sticky date" set. Once a working copy has acquired a sticky property, it stays sticky until told otherwise. Therefore, subsequent updates will not automatically retrieve the most recent revision. Instead, they'll stay restricted to the sticky date. Stickiness can be revealed by running cvs status or by directly examining the CVS/Entries file:

     floss$ cvs -q update -D "1999-04-19 23:59:59 GMT"
     U hello.c
     floss$ cat CVS/Entries
     D/a-subdir////
     D/b-subdir////
     D/c-subdir////
     /README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//D99.04.19.23.59.59
     /hello.c/1.2/Sun Apr 25 23:07:29 1999//D99.04.19.23.59.59
     floss$
     

If you were to modify hello.c and then try to commit

     floss$ cvs update
     M hello.c
     floss$ cvs ci -m "trying to change the past"
     cvs commit: cannot commit with sticky date for file 'hello.c'
     cvs [commit aborted]: correct above errors first!
     floss$
     

CVS would not permit the commit to happen because that would be like allowing you to go back and change the past. CVS is all about record keeping and, therefore, will not allow you to do that.

This does not mean CVS is unaware of all the revisions that have been committed since that date, however. You can still compare the sticky-dated working copy against other revisions, including future ones:

     floss$ cvs -q diff -c -r 1.5 hello.c
     Index: hello.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.5
     diff -c -r1.5 hello.c
     *** hello.c   1999/04/24 22:09:27     1.5
     --- hello.c   1999/04/25 00:08:44
     ***************
     *** 3,9 ****
       void
       main ()
       {
         printf ("Hello, world!\n");
     -   printf ("between hello and goodbye\n");
         printf ("Goodbye, world!\n");
       }
     --- 3,9 --
       void
       main ()
       {
     +   /* this line was added to a downdated working copy */
         printf ("Hello, world!\n");
         printf ("Goodbye, world!\n");
       }
     

This diff reveals that, as of April 19, 1999, the between hello and goodbye line had not yet been added. It also shows the modification that we made to the working copy (adding the comment shown in the preceding code snippet).

You can remove a sticky date (or any sticky property) by updating with the -A flag (-A stands for "reset", don't ask me why), which brings the working copy back to the most recent revisions:

     floss$ cvs -q update -A
     U hello.c
     floss$ cvs status hello.c
     ===================================================================
     File: hello.c                 Status: Up-to-date
        Working revision:  1.5     Sun Apr 25 22:50:27 1999
        Repository revision:       1.5     /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:                (none)
        Sticky Date:               (none)
        Sticky Options:    (none)
     floss$
     


Node: Acceptable Date Formats, Next: , Previous: Getting Snapshots (Dates And Tagging), Up: Other Useful CVS Commands

Acceptable Date Formats

CVS accepts a wide range of syntaxes to specify dates. You'll never go wrong if you use ISO 8601 format (that is, the International Standards Organization standard #8601, see also www.saqqara.demon.co.uk/datefmt.htm), which is the format used in the preceding examples. You can also use Internet email dates as described in RFC 822 and RFC 1123 (see www.rfc-editor.org/rfc/). Finally, you can use certain unambiguous English constructs to specify dates relative to the current date.

You will probably never need all of the formats available, but here are some more examples to give you an idea of what CVS accepts:

     floss$ cvs update -D "19 Apr 1999"
     floss$ cvs update -D "19 Apr 1999 20:05"
     floss$ cvs update -D "19/04/1999"
     floss$ cvs update -D "3 days ago"
     floss$ cvs update -D "5 years ago"
     floss$ cvs update -D "19 Apr 1999 23:59:59 GMT"
     floss$ cvs update -D "19 Apr"
     

The double quotes around the dates are there to ensure that the Unix shell treats the date as one argument even if it contains spaces. The quotes will do no harm if the date doesn't contain spaces, so it's probably best to always use them.


Node: Marking A Moment In Time (Tags), Previous: Acceptable Date Formats, Up: Other Useful CVS Commands

Marking A Moment In Time (Tags)

Retrieving by date is useful when the mere passage of time is your main concern. But more often what you really want to do is retrieve the project as it was at the time of a specific event - perhaps a public release, a known stable point in the software's development, or the addition or removal of some major feature.

Trying to remember the date when that event took place or deducing the date from log messages would be a tedious process. Presumably, the event, because it was important, was marked as such in the formal revision history. The method CVS offers for making such marks is known as tagging.

Tags differ from commits in that they don't record any particular textual change to files, but rather a change in the developers' attitude about the files. A tag gives a label to the collection of revisions represented by one developer's working copy (usually, that working copy is completely up to date so the tag name is attached to the "latest and greatest" revisions in the repository).

Setting a tag is as simple as this:

     floss$ cvs -q tag Release-1999_05_01
     T README.txt
     T hello.c
     T a-subdir/whatever.c
     T a-subdir/subsubdir/fish.c
     T b-subdir/random.c
     floss$
     

That command associates the symbolic name "Release-1999_05_01" with the snapshot represented by this working copy. Defined formally, snapshot means a set of files and associated revision numbers from the project. Those revision numbers do not have to be the same from file to file and, in fact, usually aren't. For example, assuming that tag was done on the same myproj directory that we've been using throughout this chapter and that the working copy was completely up to date, the symbolic name "Release-1999_05_01" will be attached to hello.c at revision 1.5, to fish.c at revision 1.2, to random.c at revision 1.2, and to everything else at revision 1.1.

It may help to visualize a tag as a path or string linking various revisions of files in the project. In Figure 2.1, an imaginary string passes through the tagged revision number of each file in a project.

     
          File A      File B      File C      File D      File E
          ------      ------      ------      ------      ------
          1.1         1.1         1.1         1.1         1.1
      ----1.2-.       1.2         1.2         1.2         1.2
          1.3 |       1.3         1.3         1.3         1.3
               \      1.4       .-1.4-.       1.4         1.4
                \     1.5      /  1.5  \      1.5         1.5
                 \    1.6     /   1.6   |     1.6         1.6
                  \   1.7    /          |     1.7         1.7
                   \  1.8   /           |     1.8       .-1.8------->
                    \ 1.9  /            |     1.9      /  1.9
                     `1.10'             |     1.10    /   1.10
                      1.11              |     1.11    |
                                        |     1.12    |
                                        |     1.13    |
                                         \    1.14    |
                                          \   1.15   /
                                           \  1.16  /
                                            `-1.17-'
     
     [Figure 2.1: How a tag might stand in relation to files's revisions.]
     
     

But if you pull the string taut and sight directly along it, you'll see a particular moment in the project's history - namely, the moment that the tag was set (Figure 2.2).

     
          File A      File B      File C      File D      File E
          ------      ------      ------      ------      ------
                                              1.1
                                              1.2
                                              1.3
                                              1.4
                                              1.5
                                              1.6
                                              1.7
                      1.1                     1.8
                      1.2                     1.9
                      1.3                     1.10        1.1
                      1.4                     1.11        1.2
                      1.5                     1.12        1.3
                      1.6                     1.13        1.4
                      1.7         1.1         1.14        1.5
                      1.8         1.2         1.15        1.6
          1.1         1.9         1.3         1.16        1.7
      ----1.2---------1.10--------1.4---------1.17--------1.8------->
          1.3         1.11        1.5         1.17        1.9
                                  1.6         1.17        1.10
     
     [Figure 2.2: The same tag as a "straight sight" through the revision history.]
     
     

As you continue to edit files and commit changes, the tag will not move along with the increasing revision numbers. It stays fixed, "stickily", at the revision number of each file at the time the tag was made.

Given their importance as descriptors, it's a bit unfortunate that log messages can't be included with tags or that the tags themselves can't be full paragraphs of prose. In the preceding example, the tag is fairly obviously stating that the project was in a releasable state as of a certain date. However, sometimes you may want to make snapshots of a more complex state, which can result in ungainly tag names such as:

     floss$ cvs tag testing-release-3_pre-19990525-public-release
     

As a general rule, you should try to keep tags as terse as possible while still including all necessary information about the event that you're trying to record. When in doubt, err on the side of being overly descriptive - you'll be glad later when you're able to tell from some verbose tag name exactly what circumstance was recorded.

You've probably noticed that no periods or spaces were used in the tag names. CVS is rather strict about what constitutes a valid tag name. The rules are that it must start with a letter and contain letters, digits, hyphens ("-"), and underscores ("_"). No spaces, periods, colons, commas, or any other symbols may be used.

To retrieve a snapshot by tag name, the tag name is used just like a revision number. There are two ways to retrieve snapshots: You can check out a new working copy with a certain tag, or you can switch an existing working copy over to a tag. Both result in a working copy whose files are at the revisions specified by the tag.

Most of the time, what you're trying to do is take a look at the project as it was at the time of the snapshot. You may not necessarily want to do this in your main working copy, where you presumably have uncommitted changes and other useful states built up, so let's assume you just want to check out a separate working copy with the tag. Here's how (but make sure to invoke this somewhere other than in your existing working copy or its parent directory!):

     floss$ cvs checkout -r Release-1999_05_01 myproj
     cvs checkout: Updating myproj
     U myproj/README.txt
     U myproj/hello.c
     cvs checkout: Updating myproj/a-subdir
     U myproj/a-subdir/whatever.c
     cvs checkout: Updating myproj/a-subdir/subsubdir
     U myproj/a-subdir/subsubdir/fish.c
     cvs checkout: Updating myproj/b-subdir
     U myproj/b-subdir/random.c
     cvs checkout: Updating myproj/c-subdir
     

We've seen the -r option before in the update command, where it preceded a revision number. In many ways a tag is just like a revision number because, for any file, a given tag corresponds to exactly one revision number (it's illegal, and generally impossible, to have two tags of the same name in the same project). In fact, anywhere you can use a revision number as part of a CVS command, you can use a tag name instead (as long as the tag has been set previously). If you want to diff a file's current state against its state at the time of the last release, you can do this:

     floss$ cvs diff -c -r Release-1999_05_01 hello.c
     

And if you want to revert it temporarily to that revision, you can do this:

     floss$ cvs update -r Release-1999_05_01 hello.c
     

The interchangeability of tags and revision numbers explains some of the strict rules about valid tag names. Imagine if periods were legal in tag names; you could have a tag named "1.3" attached to an actual revision number of "1.47". If you then issued the command

     floss$ cvs update -r 1.3 hello.c
     

how would CVS know whether you were referring to the tag named "1.3", or the much earlier revision 1.3 of hello.c? Thus, restrictions are placed on tag names so that they can always be easily distinguished from revision numbers. A revision number has a period; a tag name doesn't. (There are reasons for the other restrictions, too, mostly having to do with making tag names easy for CVS to parse.)

As you've probably guessed by this point, the second method of retrieving a snapshot - that is, switching an existing working directory over to the tagged revisions-is also done by updating:

     floss$ cvs update -r Release-1999_05_01
     cvs update: Updating .
     cvs update: Updating a-subdir
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir
     cvs update: Updating c-subdir
     floss$
     

The preceding command is just like the one we used to revert hello.c to Release-1999_05_01, except that the filename is omitted because we want to revert the entire project over. (You can, if you want, revert just one subtree of the project to the tag by invoking the preceding command in that subtree instead of from the top level, although you hardly ever would want to do that.)

Note that no files appear to have changed when we updated. The working copy was completely up to date when we tagged, and no changes had been committed since the tagging.

However, this does not mean that nothing changed at all. The working copy now knows that it's at a tagged revision. When you make a change and try to commit it (let's assume we modified hello.c):

     floss$ cvs -q update
     M hello.c
     floss$ cvs -q ci -m "trying to commit from a working copy on a tag"
     cvs commit: sticky tag 'Release-1999_05_01' for file 'hello.c' is not a branch
     cvs [commit aborted]: correct above errors first!
     floss$
     

CVS does not permit the commit to happen. (Don't worry about the exact meaning of that error message yet - we'll cover branches next in this chapter.) It doesn't matter whether the working copy got to be on a tag via a checkout or an update. Once it is on a tag, CVS views the working copy as a static snapshot of a moment in history, and CVS won't let you change history, at least not easily. If you run cvs status or look at the CVS/Entries files, you'll see that there is a sticky tag set on each file. Here's the top level Entries file, for example:

     floss$ cat CVS/Entries
     D/a-subdir////
     D/b-subdir////
     D/c-subdir////
     /README.txt/1.1.1.1/Sun Apr 18 18:18:22 1999//TRelease-1999_05_01
     /hello.c/1.5/Tue Apr 20 07:24:10 1999//TRelease-1999_05_01
     floss$
     

Tags, like other sticky properties, are removed with the -A flag to update:

     floss$ cvs -q update -A
     M hello.c
     floss$
     

The modification to hello.c did not go away, however; CVS is still aware that the file changed with respect to the repository:

     floss$ cvs -q diff -c hello.c
     Index: hello.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.5
     diff -c -r1.5 hello.c
     *** hello.c   1999/04/20 06:12:56     1.5
     --- hello.c   1999/05/04 20:09:17
     ***************
     *** 6,9 ****
     --- 6,10 --
         printf ("Hello, world!\n");
         printf ("between hello and goodbye\n");
         printf ("Goodbye, world!\n");
     +   /* a comment on the last line */
       }
     floss$
     

Now that you've reset with update, CVS will accept a commit:

     floss$ cvs ci -m "added comment to end of main function"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     cvs commit: Examining c-subdir
     Checking in hello.c;
     /usr/local/cvs/myproj/hello.c,v  <-  hello.c
     new revision: 1.6; previous revision: 1.5
     done
     floss$
     

The tag Release-1999_05_01 is still attached to revision 1.5, of course. Compare the file's status before and after a reversion to the tag:

     floss$ cvs -q status hello.c
     ===================================================================
     File: hello.c                 Status: Up-to-date
        Working revision:  1.6     Tue May  4 20:09:17 1999
        Repository revision:       1.6     /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:                (none)
        Sticky Date:               (none)
        Sticky Options:            (none)
     floss$ cvs -q update -r Release-1999_05_01
     U hello.c
     floss$ cvs -q status hello.c
     ===================================================================
     File: hello.c                 Status: Up-to-date
        Working revision:  1.5     Tue May  4 20:21:12 1999
        Repository revision:       1.5     /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:                Release-1999_05_01 (revision: 1.5)
        Sticky Date:               (none)
        Sticky Options:            (none)
     floss$
     

Now, having just told you that CVS doesn't let you change history, I'll show you how to change history.


Node: Branches, Previous: Other Useful CVS Commands, Up: An Overview of CVS

Branches

We've been viewing CVS as a kind of intelligent, coordinating library. However, it can also be thought of as a time machine (thanks to Jim Blandy for the analogy). So far, we've only seen how you can examine the past with CVS, without affecting anything. Like all good time machines, CVS also allows you to go back in time to change the past. What do you get then? Science fiction fans know the answer to that question: an alternate universe, running parallel to ours, but diverging from ours at exactly the point where the past was changed. A CVS branch splits a project's development into separate, parallel histories. Changes made on one branch do not affect the other.


Node: Branching Basics, Next: , Up: Branches

Branching Basics

Why are branches useful?

Let's return for a moment to the scenario of the developer who, in the midst of working on a new version of the program, receives a bug report about an older released version. Assuming the developer fixes the problem, she still needs a way to deliver the fix to the customer. It won't help to just find an old copy of the program somewhere, patch it up without CVS's knowledge, and ship it off. There would be no record of what was done; CVS would be unaware of the fix; and later if something was discovered to be wrong with the patch, no one would have a starting point for reproducing the problem.

It's even more ill-advised to fix the bug in the current, unstable version of the sources and ship that to the customer. Sure, the reported bug may be solved, but the rest of the code is in a half-implemented, untested state. It may run, but it's certainly not ready for prime time.

Because the last released version is thought to be stable, aside from this one bug, the ideal solution is to go back and correct the bug in the old release - that is, to create an alternate universe in which the last public release includes this bug fix.

That's where branches come in. The developer splits off a branch, rooted in the main line of development (the trunk) not at its most recent revisions, but back at the point of the last release. Then she checks out a working copy of this branch, makes whatever changes are necessary to fix the bug, and commits them on that branch, so there's a record of the bug fix. Now she can package up an interim release based on the branch and ship it to the customer.

Her change won't have affected the code on the trunk, nor would she want it to without first finding out whether the trunk needs the same bug fix or not. If it does, she can merge the branch changes into the trunk. In a merge, CVS calculates the changes made on the branch between the point where it diverged from the trunk and the branch's tip (its most recent state), then applies those differences to the project at the tip of the trunk. The difference between the branch's root and its tip works out, of course, to be precisely the bug fix.

Another good way to think of a merge is as a special case of updating. The difference is that in a merge, the changes to be incorporated are derived by comparing the branch's root and tip, instead of by comparing the working copy against the repository.

The act of updating is itself similar to receiving patches directly from their authors and applying them by hand. In fact, to do an update, CVS calculates the difference (that's "difference" as in the diff program) between the working copy and the repository and then applies that diff to the working copy just as the patch program would. This mirrors the way in which a developer takes changes from the outside world, by manually applying patch files sent in by contributors.

Thus, merging the bug fix branch into the trunk is just like accepting some outside contributor's patch to fix the bug. The contributor would have made the patch against the last released version, just as the branch's changes are against that version. If that area of code in the current sources hasn't changed much since the last release, the merge will succeed with no problems. If the code is now substantially different, however, the merge will fail with conflict (that is, the patch will be rejected), and some manual fiddling will be necessary. Usually this is accomplished by reading the conflicting area, making the necessary changes by hand, and committing. Figure 2.3 shows a picture of what happens in a branch and merge.

     
                  (branch on which bug was fixed)
                .---------------->---------------.
               /                                 |
              /                                  |
             /                                   |
            /                                    |
           /                                     V (<------ point of merge)
      ====*===================================================================>
                     (main line of development)
     
     
     [Figure 2.3: A branch and then a merge.  Time flows left to right.]
     
     

We'll now walk through the steps necessary to make this picture happen. Remember that it's not really time that's flowing from left to right in the diagram, but rather the revision history. The branch will not have been made at the time of the release, but is created later, rooted back at the release's revisions.

In our case, let's assume the files in the project have gone through many revisions since they were tagged as Release-1999_05_01, and perhaps files have been added as well. When the bug report regarding the old release comes in, the first thing we'll want to do is create a branch rooted at the old release, which we conveniently tagged Release-1999_05_01.

One way to do this is to first check out a working copy based on that tag, then create the branch by re-tagging with the -b (branch) option:

     floss$ cd ..
     floss$ ls
     myproj/
     floss$ cvs -q checkout -d myproj_old_release -r Release-1999_05_01 myproj
     U myproj_old_release/README.txt
     U myproj_old_release/hello.c
     U myproj_old_release/a-subdir/whatever.c
     U myproj_old_release/a-subdir/subsubdir/fish.c
     U myproj_old_release/b-subdir/random.c
     floss$ ls
     myproj/      myproj_old_release/
     floss$ cd myproj_old_release
     floss$ ls
     CVS/      README.txt  a-subdir/   b-subdir/   hello.c
     floss$ cvs -q tag -b Release-1999_05_01-bugfixes
     T README.txt
     T hello.c
     T a-subdir/whatever.c
     T a-subdir/subsubdir/fish.c
     T b-subdir/random.c
     floss$
     

Take a good look at that last command. It may seem somewhat arbitrary that tag is used to create branches, but there's actually a reason for it: The tag name will serve as a label by which the branch can be retrieved later. Branch tags do not look any different from non-branch tags, and are subject to the same naming restrictions. Some people like to always include the word branch in the tag name itself (for example, Release-1999_05_01-bugfix-branch), so they can distinguish branch tags from other kinds of tags. You may want to do this if you find yourself often retrieving the wrong tag.

(And while we're at it, note the -d myproj_old_release option to checkout in the first CVS command. This tells checkout to put the working copy in a directory called myproj_old_release, so we won't confuse it with the current version in myproj. Be careful not to confuse this use of -d with the global option of the same name, or with the -d option to update.)

Of course, merely running the tag command does not switch this working copy over to the branch. Tagging never affects the working copy; it just records some extra information in the repository to allow you to retrieve that working copy's revisions later on (as a static piece of history or as a branch, as the case may be).

Retrieval can be done one of two ways (you're probably getting used to this motif by now!). You can check out a new working copy on the branch

     floss$ pwd
     /home/whatever
     floss$ cvs co -d myproj_branch -r Release-1999_05_01-bugfixes myproj
     

or switch an existing working copy over to it:

     floss$ pwd
     /home/whatever/myproj
     floss$ cvs update -r Release-1999_05_01-bugfixes
     

The end result is the same (well, the name of the new working copy's top-level directory may be different, but that's not important for CVS's purposes). If your current working copy has uncommitted changes, you'll probably want to use checkout instead of update to access the branch. Otherwise, CVS attempts to merge your changes into the working copy as it switches it over to the branch. In that case, you might get conflicts, and even if you didn't, you'd still have an impure branch. It won't truly reflect the state of the program as of the designated tag, because some files in the working copy will contain modifications made by you.

Anyway, let's assume that by one method or another you get a working copy on the desired branch:

     floss$ cvs -q status hello.c
     ===================================================================
     File: hello.c                 Status: Up-to-date
        Working revision:  1.5     Tue Apr 20 06:12:56 1999
        Repository revision:       1.5     /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:                Release-1999_05_01-bugfixes
     (branch: 1.5.2)
        Sticky Date:               (none)
        Sticky Options:            (none)
     floss$ cvs -q status b-subdir/random.c
     ===================================================================
     File: random.c                Status: Up-to-date
        Working revision:  1.2     Mon Apr 19 06:35:27 1999
        Repository revision:       1.2 /usr/local/cvs/myproj/b-subdir/random.c,v
        Sticky Tag:                Release-1999_05_01-bugfixes (branch: 1.2.2)
        Sticky Date:               (none)
        Sticky Options:            (none)
     floss$
     

(The contents of those Sticky Tag lines will be explained shortly.) If you modify hello.c and random.c, and commit

     floss$ cvs -q update
     M hello.c
     M b-subdir/random.c
     floss$ cvs ci -m "fixed old punctuation bugs"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in hello.c;
     /usr/local/cvs/myproj/hello.c,v  <-  hello.c
     new revision: 1.5.2.1; previous revision: 1.5
     done
     Checking in b-subdir/random.c;
     /usr/local/cvs/myproj/b-subdir/random.c,v  <-  random.c
     new revision: 1.2.2.1; previous revision: 1.2
     done
     floss$
     

you'll notice that there's something funny going on with the revision numbers:

     floss$ cvs -q status hello.c b-subdir/random.c
     ===================================================================
     File: hello.c                 Status: Up-to-date
        Working revision:  1.5.2.1 Wed May  5 00:13:58 1999
        Repository revision:       1.5.2.1 /usr/local/cvs/myproj/hello.c,v
        Sticky Tag:                Release-1999_05_01-bugfixes (branch: 1.5.2)
        Sticky Date:               (none)
        Sticky Options:            (none)
     ===================================================================
     File: random.c                Status: Up-to-date
        Working revision:  1.2.2.1 Wed May  5 00:14:25 1999
        Repository revision:       1.2.2.1 /usr/local/cvs/myproj/b-subdir/random.c,v
        Sticky Tag:                Release-1999_05_01-bugfixes (branch: 1.2.2)
        Sticky Date:               (none)
        Sticky Options:            (none)
     floss$
     

They now have four digits instead of two!

A closer look reveals that each file's revision number is just the branch number (as shown on the Sticky Tag line) plus an extra digit on the end.

What you're seeing is a little bit of CVS's inner workings. Although you almost always use a branch to mark a project-wide divergence, CVS actually records the branch on a per-file basis. This project had five files in it at the point of the branch, so five individual branches were made, all with the same tag name: Release-1999_05_01-bugfixes.

Most people consider this per-file scheme a rather inelegant implementation on CVS's part. It's a bit of the old RCS legacy showing through-RCS didn't know how to group files into projects, and even though CVS does, it still uses code inherited from RCS to handle branches.

Ordinarily, you don't need to be too concerned with how CVS is keeping track of things internally, but in this case, it helps to understand the relationship between branch numbers and revision numbers. Let's look at the hello.c file; everything I'm about to say about hello.c applies to the other files in the branch (with revision/branch numbers adjusted accordingly).

The hello.c file was on revision 1.5 at the point where the branch was rooted. When we created the branch, a new number was tacked onto the end to make a branch number (CVS chooses the first unused even, nonzero integer). Thus, the branch number in this case became 1.5.2. The branch number by itself is not a revision number, but it is the root (that is, the prefix) of all the revision numbers for hello.c along this branch.

However, when we ran that first CVS status in a branched working copy, hello.c's revision number showed up as only 1.5, not 1.5.2.0 or something similar. This is because the initial revision on a branch is always the same as the trunk revision of the file, where the branch sprouts off. Therefore, CVS shows the trunk revision number in status output, for as long as the file is the same on both branch and trunk.

Once we had committed a new revision, hello.c was no longer the same on both trunk and branch - the branch incarnation of the file had changed, while the trunk remained the same. Accordingly, hello.c was assigned its first branch revision number. We saw this in the status output after the commit, where its revision number is clearly 1.5.2.1.

The same story applies to the random.c file. Its revision number at the time of branching was 1.2, so its first branch is 1.2.2, and the first new commit of random.c on that branch received the revision number 1.2.2.1.

There is no numeric relationship between 1.5.2.1 and 1.2.2.1 - no reason to think that they are part of the same branch event, except that both files are tagged with Release-1999_05_01-bugfixes, and the tag is attached to branch numbers 1.5.2 and 1.2.2 in the respective files. Therefore, the tag name is your only handle on the branch as a project-wide entity. Although it is perfectly possible to move a file to a branch by using the revision number directly

     floss$ cvs update -r 1.5.2.1 hello.c
     U hello.c
     floss$
     

it is almost always ill-advised. You would be mixing the branch revision of one file with non-branch revisions of the others. Who knows what losses may result? It is better to use the branch tag to refer to the branch and do all files at once by not specifying any particular file. That way you don't have to know or care what the actual branch revision number is for any particular file.

It is also possible to have branches that sprout off other branches, to any level of absurdity. For example, if a file has a revision number of 1.5.4.37.2.3, its revision history can be diagrammed like this:

                       1.1
                        |
                       1.2
                        |
                       1.3
                        |
                       1.4
                        |
                       1.5
                      /   \
                     /     \
                    /       \
                (1.5.2)   (1.5.4)         <--- (these are branch numbers)
                  /           \
              1.5.2.1        1.5.4.1
                 |              |
              1.5.2.2        1.5.4.2
                 |              |
               (etc)          (...)       <--- (collapsed 34 revisions for brevity)
                                |
                             1.5.4.37
                               /
                              /
                        (1.5.4.37.2)      <--- (this is also a branch number)
                            /
                           /
                    1.5.4.37.2.1
                          |
                    1.5.4.37.2.2
                          |
                    1.5.4.37.2.3
     
     [Figure 2.4: An unusually high degree of branching.  Time flows downward.]
     
     

Admittedly, only the rarest circumstances make such a branching depth necessary, but isn't it nice to know that CVS will go as far as you're willing to take it? Nested branches are created the same way as any other branch: Check out a working copy on branch N, run cvs tag -b branchname in it, and you'll create branch N.M in the repository (where N represents the appropriate branch revision number in each file, such as 1.5.2.1, and M represents the next available branch at the end of that number, such as 2).


Node: Merging Changes From Branch To Trunk, Next: , Previous: Branching Basics, Up: Branches

Merging Changes From Branch To Trunk

Now that the bug fix has been committed on the branch, let's switch the working copy over to the highest trunk revisions and see if the bug fix needs to be done there, too. We'll move the working copy off the branch by using update -A (branch tags are like other sticky properties in this respect) and then diffing against the branch we just left:

     floss$ cvs -q update -d -A
     U hello.c
     U b-subdir/random.c
     floss$ cvs -q diff -c -r Release-1999_05_01-bugfixes
     Index: hello.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.5.2.1
     retrieving revision 1.6
     diff -c -r1.5.2.1 -r1.6
     *** hello.c   1999/05/05 00:15:07     1.5.2.1
     --- hello.c   1999/05/04 20:19:16     1.6
     ***************
     *** 4,9 ****
       main ()
       {
         printf ("Hello, world!\n");
     !   printf ("between hello and good-bye\n");
         printf ("Goodbye, world!\n");
       }
     --- 4,10 --
       main ()
       {
         printf ("Hello, world!\n");
     !   printf ("between hello and goodbye\n");
         printf ("Goodbye, world!\n");
     +   /* a comment on the last line */
       }
     Index: b-subdir/random.c
     ===================================================================
     RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
     retrieving revision 1.2.2.1
     retrieving revision 1.2
     diff -c -r1.2.2.1 -r1.2
     *** b-subdir/random.c 1999/05/05 00:15:07     1.2.2.1
     --- b-subdir/random.c 1999/04/19 06:35:27     1.2
     ***************
     *** 4,8 ****
       void main ()
       {
     !   printf ("A random number.\n");
       }
     --- 4,8 --
       void main ()
       {
     !   printf ("a random number\n");
       }
     floss$
     

The diff shows that good-bye is spelled with a hyphen in the branch revision of hello.c, and that the trunk revision of that file has a comment near the end that the branch revision doesn't have. Meanwhile, in random.c, the branch revision has a capital "A" and a period, whereas the trunk doesn't.

To actually merge the branch changes into the current working copy, run update with the -j flag (the same j for "join" that we used to revert a file to an old revision before):

     floss$ cvs -q update -d -j Release-1999_05_01-bugfixes
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.5
     retrieving revision 1.5.2.1
     Merging differences between 1.5 and 1.5.2.1 into hello.c
     RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
     retrieving revision 1.2
     retrieving revision 1.2.2.1
     Merging differences between 1.2 and 1.2.2.1 into random.c
     floss$ cvs -q update
     M hello.c
     M b-subdir/random.c
     floss$ cvs -q ci -m "merged from branch Release-1999_05_01-bugfixes"
     Checking in hello.c;
     /usr/local/cvs/myproj/hello.c,v  <-  hello.c
     new revision: 1.7; previous revision: 1.6
     done
     Checking in b-subdir/random.c;
     /usr/local/cvs/myproj/b-subdir/random.c,v  <-  random.c
     new revision: 1.3; previous revision: 1.2
     done
     floss$
     

This takes the changes from the branch's root to its tip and merges them into the current working copy (which subsequently shows those modifications just as though the files had been hand-edited into that state). The changes are then committed onto the trunk, since nothing in the repository changed when a working copy underwent a merge.

Although no conflicts were encountered in this example, it's quite possible (even probable) that there would be some in a normal merge. If that happens, they need to be resolved like any other conflict, and then committed.


Node: Multiple Merges, Next: , Previous: Merging Changes From Branch To Trunk, Up: Branches

Multiple Merges

Sometimes a branch will continue to be actively developed even after the trunk has undergone a merge from it. For example, this can happen if a second bug in the previous public release is discovered and has to be fixed on the branch. Maybe someone didn't get the joke in random.c, so on the branch, you have to add a line explaining it

     floss$ pwd
     /home/whatever/myproj_branch
     floss$ cat b-subdir/random.c
     /* Print out a random number. */
     #include <stdio.h>
     void main ()
     {
       printf ("A random number.\n");
       printf ("Get the joke?\n");
     }
     floss$
     

and commit. If that bug fix also needs to be merged into the trunk, you might be tempted to try the same update command as before in the trunk working copy to "re-merge":

     floss$ cvs -q update -d -j Release-1999_05_01-bugfixes
     RCS file: /usr/local/cvs/myproj/hello.c,v
     retrieving revision 1.5
     retrieving revision 1.5.2.1
     Merging differences between 1.5 and 1.5.2.1 into hello.c
     RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
     retrieving revision 1.2
     retrieving revision 1.2.2.2
     Merging differences between 1.2 and 1.2.2.2 into random.c
     rcsmerge: warning: conflicts during merge
     floss$
     

As you can see, that didn't have quite the desired effect-we got a conflict, even though the trunk copy hadn't been modified there and, therefore, no conflict was expected.

The trouble was that the update command behaved exactly as described: It tried to take all the changes between the branch's root and tip and merge them into the current working copy. The only problem is, some of those changes had already been merged into this working copy. That's why we got the conflict:

     floss$ pwd
     /home/whatever/myproj
     floss$ cat b-subdir/random.c
     /* Print out a random number. */
     #include <stdio.h
     void main ()
     {
     <<<<<<< random.c
       printf ("A random number.\n");
     =======
       printf ("A random number.\n");
       printf ("Get the joke?\n");
     >>>>>>> 1.2.2.2
     }
     floss$
     

You could go through resolving all such conflicts by hand-it's usually not hard to tell what you need to do in each file. Nevertheless, it is even better to avoid a conflict in the first place. By passing two -j flags instead of one, you'll get only those changes from where you last merged to the tip instead of all of the changes on the branch, from root to tip. The first -j gives the starting point on the branch, and the second is just the plain branch name (which implies the tip of the branch).

The question then is, how can you specify the point on the branch from which you last merged? One way is to qualify by using a date along with the branch tag name. CVS provides a special syntax for this:

     floss$ cvs -q update -d -j "Release-1999_05_01-bugfixes:2 days ago" \
                          -j Release-1999_05_01-bugfixes
     RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
     retrieving revision 1.2.2.1
     retrieving revision 1.2.2.2
     Merging differences between 1.2.2.1 and 1.2.2.2 into random.c
     floss$
     

If the branch tag name is followed by a colon and then a date (in any of the usual CVS date syntaxes), CVS will include only changes later than that date. So if you knew that the original bug fix was committed on the branch three days ago, the preceding command would merge the second bug fix only.

A better way, if you plan ahead, is to tag the branch after each bug fix (just a regular tag - we're not starting a new branch here or anything like that). Suppose after fixing the bug in the branch and committing, you do this in the branch's working copy:

     floss$ cvs -q tag Release-1999_05_01-bugfixes-fix-number-1
     T README.txt
     T hello.c
     T a-subdir/whatever.c
     T a-subdir/subsubdir/fish.c
     T b-subdir/random.c
     floss$
     

Then, when it's time to merge the second change into the trunk, you can use that conveniently placed tag to delimit the earlier revision:

     floss$ cvs -q update -d -j Release-1999_05_01-bugfixes-fix-number-1 \
                          -j Release-1999_05_01-bugfixes
     RCS file: /usr/local/cvs/myproj/b-subdir/random.c,v
     retrieving revision 1.2.2.1
     retrieving revision 1.2.2.2
     Merging differences between 1.2.2.1 and 1.2.2.2 into random.c
     floss$
     

This way, of course, is much better than trying to recall how long ago you made one change versus another, but it only works if you remember to tag the branch every time it is merged to the trunk. The lesson, therefore, is to tag early and tag often! It's better to err on the side of too many tags (as long as they all have descriptive names) than to have too few. In these last examples, for instance, there was no requirement that the new tag on the branch have a name similar to the branch tag itself. Although I named it Release-1999_05_01-bugfixes-fix-number-1, it could just as easily have been fix1. However, the former is preferable, because it contains the name of the branch and thus won't ever be confused with a tag on some other branch. (Remember that tag names are unique within files, not within branches. You can't have two tags named fix1 in the same file, even if they refer to revisions on different branches.)


Node: Creating A Tag Or Branch Without A Working Copy, Previous: Multiple Merges, Up: Branches

Creating A Tag Or Branch Without A Working Copy

As stated earlier, tagging affects the repository, not the working copy. That begs the question: Why require a working copy at all when tagging? The only purpose that it serves is to designate which project and which revisions of the various files in the project are being tagged. If you could specify the project and revisions independently of the working copy, no working copy would be necessary.

There is such a way: the rtag command (for "repository tag"). It's very similar to tag; a couple of examples will explain its usage. Let's go back to the moment when the first bug report came in and we needed to create a branch rooted at the last public release. We checked out a working copy at the release tag and then ran tag -b on it:

     floss$ cvs tag -b Release-1999_05_01-bugfixes
     

This created a branch rooted at Release-1999_05_01. However, because we know the release tag, we could have used it in an rtag command to specify where to root the branch, not even bothering with a working copy:

     floss$ cvs rtag -b -r Release-1999_05_01 Release-1999_05_01-bugfixes myproj
     

That's all there is to it. That command can be issued from anywhere, inside or outside a working copy. However, your CVSROOT environment variable would have to point to the repository, of course, or you can specify it with the global -d option. It works for non-branch tagging, too, but it's less useful that way because you have to specify each file's revision number, one by one. (Or you can refer to it by tag, but then you'd obviously already have a tag there, so why would you want to set a second one on the exact same revisions?)

You now know enough to get around in CVS and probably enough to start working with other people on a project. There are still a few minor features that haven't been introduced, as well as some unmentioned but useful options to features already seen. These will all be presented as appropriate in chapters to come, in scenarios that will demonstrate both how and why to use them. When in doubt, don't hesitate to consult the Cederqvist manual; it is an indispensable resource for serious CVS users.


Node: Repository Administration, Next: , Previous: An Overview of CVS, Up: Top

Repository Administration

In An Overview of CVS, you learned enough CVS to use it effectively as a project participant. If you're going to be a project maintainer, however, you'll need to know how to install CVS and administer repositories. In this chapter, we'll throw back the curtain and look in detail at how the repository is structured, and how CVS uses it. You'll learn all the major steps CVS goes through during updates and commits, and how you can modify its behavior. By understanding how CVS works, you'll also be able to trace problems to their causes, and fix them in maintainable ways.

This may sound very involved, but remember that CVS has already proven quite long-lived, and will probably be around for many years to come. Whatever you learn now will be useful for a long time. CVS also tends to become more indispensable the more you use it. If you're going to be that dependent on something (and trust me, you are), it's worth really getting to know it.

With that in mind, let's begin at the beginning: putting CVS on your system.


Node: Getting And Installing CVS, Next: , Up: Repository Administration

Getting And Installing CVS

In many cases, you won't have to go out and get CVS, because it will already be on your system. If you run one of the major Linux or FreeBSD distributions, it's probably already installed in /usr/bin or some other likely location. If not, Red Hat Linux users can usually find an RPM (Red Hat Package) for the latest, or nearly latest, version of CVS in their distributions. And Debian users can install the latest Debian package with these commands:

     floss$ apt-get update
     floss$ apt-get install cvs
     

If CVS isn't already on your machine, you'll probably have to build it from source. If you're a non-Unix user, you'll probably find it easier to get a prebuilt binary for your operating system (more on that later). Fortunately, CVS is fully autoconfiscated - that is, it uses the GNU autoconfiguration mechanism, making compilation from source surprisingly easy.


Node: Getting And Building CVS Under Unix, Next: , Up: Getting And Installing CVS

Getting And Building CVS Under Unix

As of this writing, there are two canonical sites from which you can download CVS. One is the Free Software Foundation's FTP site, ftp://ftp.gnu.org/gnu/cvs/, which offers CVS as an official GNU tool. The other is Cyclic Software's download site. Cyclic Software is, if not the maintainer of CVS, then the "maintainer of the maintainers", by providing a repository server and download access for users and developers. They distribute releases from http://download.cyclic.com/pub/.

Either location is fine. In the following example, I use Cyclic Software's site. If you point your FTP client (probably your Web browser) there, you'll see a list of directories, something like this:

     Index of /pub
         cvs-1.10.5/            18-Feb-99 21:36      -
         cvs-1.10.6/            17-May-99 10:34      -
         cvs-1.10/              09-Dec-98 17:26      -
         macintosh/             23-Feb-99 00:53      -
         os2/                   09-Dec-98 17:26      -
         packages/              09-Dec-98 17:26      -
         rcs/                   09-Dec-98 17:26      -
         tkcvs/                 09-Dec-98 17:26      -
         training/              09-Dec-98 17:26      -
         unix/                  09-Dec-98 17:26      -
         vms/                   09-Dec-98 17:26      -
     

Pay attention to the directories beginning with "cvs-" (you can ignore most of the others). There are three such directories, which means that you're already faced with a choice: Get the designated "stable" release, or go with a newer (but less-tested) interim release. The stable releases have only one decimal point, as in "cvs-1.10", whereas the interim releases have minor version increments tacked on the end, as in "1.10.5".

The GNU site usually only offers the major releases, not the interim ones, so you won't see all of this if you get CVS from there. In general, the interim releases have been pretty safe, and sometimes contain fixes to bugs that were found in the major release. Your best policy is to go with the highest interim release, but if you encounter any problems with it, be prepared to drop back to the previous release, as many times as necessary. The highest release listed in the earlier example is cvs-1.10.6. Entering that directory, we see this:

     Index of /pub/cvs-1.10.6
         cvs-1.10.6.tar.gz      17-May-99 08:44   2.2M
     

That's it - the full source code to CVS. Just download it to your machine, and you're ready to build. At this point, if you're already familiar with the standard build process for GNU tools, you know what to do and probably don't need to read anything between here and the section Anatomy Of A CVS Distribution. On the other hand, if you're not sure how to proceed, then read on....

The following compilation instructions and examples assume that you have a fairly standard distribution of Unix. Any of the free versions of Unix (for example, FreeBSD or Linux) should work with no problem, as should the major commercial Unix versions (such as SunOS/Solaris, AIX, HP-UX, or Ultrix). Even if these instructions don't work for you exactly as written, don't give up hope. Although covering the details of compiling on every operating system is beyond the scope of this book, I'll give some pointers to other help resources later in this chapter.

Anyway, to proceed with the compilation, first unpack the tar file using GNU gunzip and tar (if you don't have these installed on your system, you can get gunzip from ftp://ftp.gnu.org/gnu/gzip/ and GNU's version of tar from ftp://ftp.gnu.org/gnu/tar/):

     floss$ gunzip cvs-1.10.6.tar.gz
     floss$ tar xvf cvs-1.10.6.tar
     

You'll see a lot of file names fly by on your screen.

Now you have a new directory on your machine - cvs-1.10.6 - and it is populated with the CVS source code. Go into that directory and configure CVS for your system, by using the provided configure script:

     floss$ cd cvs-1.10.6
     floss$  ./configure
     creating cache ./config.cache
     checking for gcc... gcc
     checking whether we are using GNU C... yes
     checking whether gcc accepts -g... yes
     checking how to run the C preprocessor... gcc -E
       (etc)
     

When the configure command finishes, the source tree will know everything it needs to know about compiling on your machine. The next step is to type:

     floss$ make
     

You'll see lots of output fly by, then type:

     floss$ make install
     

You'll see yet more output fly by; when it's all over, CVS will be installed on your system. (You will probably need to do that last step as the superuser.)

By default, the CVS executable will end up as /usr/local/bin/cvs. This assumes you have a decent make program installed on your system (again, if you don't have one, get the GNU project's make from ftp://ftp.gnu.org/gnu/make/).

If you want CVS to install to a location other than /usr/local/bin, you should change how you run the initial configuration step. For example,

     floss$ ./configure --prefix=/usr
     

results in CVS being installed as /usr/bin/cvs (it always ends up as PREFIX/bin/cvs). The default prefix is /usr/local, which is fine for most installations.

Note To Experienced Users: Although older versions of CVS consisted of more than just an executable in that they depended on having RCS installed as well, this has not been the case since Version 1.10. Therefore, you don't need to worry about any libraries or executables other than cvs itself.

If you just intend to use CVS to access remote repositories, the preceding is all you need to do. If you also plan to serve a repository from this system, a few additional steps are necessary, which are covered later in this chapter.


Node: Getting And Installing CVS Under Windows, Next: , Previous: Getting And Building CVS Under Unix, Up: Getting And Installing CVS

Getting And Installing CVS Under Windows

Unless you're truly religious about having the source code to your executable, you don't need to compile CVS from source on your Windows box. Unlike Unix, the necessary compilation tools probably do not already exist on your system, so a source build would involve first going out and getting those tools. Because such a project is beyond the scope of this book, I'll just give instructions for getting a precompiled CVS binary.

First, note that Windows binary distributions of CVS are usually made only for major releases of CVS - not for the interim releases - and are not found on the GNU FTP site. So you'll need to go to Cyclic Software's download site, where in the major version directory, http://download.cyclic.com/pub/cvs-1.10/, you'll see an extra subdirectory

     Index of /pub/cvs-1.10
         cvs-1.10.tar.gz        14-Aug-98 09:35   2.4M
         windows/
     

inside of which is a ZIP file:

     Index of /pub/cvs-1.10/windows
         cvs-1.10-win.zip       14-Aug-98 10:10   589k
     

This ZIP file contains a binary distribution of CVS. Download and extract that ZIP file:

     floss$ unzip cvs-1.10-win.zip
     
     Archive:  cvs-1.10-win.zip
       inflating: cvs.html
       inflating: cvs.exe
       inflating: README
       inflating: FAQ
       inflating: NEWS
       inflating: patch.exe
       inflating: win32gnu.dll
     

The README there contains detailed instructions. For most installations, they can be summarized as follows: Put all of the EXE and DLL files in a directory in your PATH. Additionally, if you're going to be using the pserver method to access a remote repository, you may need to put the following in your C:\AUTOEXEC.BAT file and reboot:

     set HOME=C:
     

This tells CVS where to store the .cvspass file.

CVS running under Windows cannot currently serve repositories to remote machines; it can be a client (connecting to remote repositories), and operate in local mode (using a repository on the same machine). For the most part, this book assumes that CVS under Windows is operating as a client. However, it shouldn't be too hard to set up a local repository under Windows after reading the Unix-oriented instructions in the rest of this chapter.

If you are only accessing remote repositories, you may not even need to run CVS. There is a tool called WinCvs that implements only the client-side portion of CVS. It is distributed separately from CVS itself but, like CVS, is freely available under the GNU General Public License. More information is available from http://www.wincvs.org.


Node: Getting And Installing CVS On A Macintosh, Next: , Previous: Getting And Installing CVS Under Windows, Up: Getting And Installing CVS

Getting And Installing CVS On A Macintosh

CVS is available for the Macintosh, but not as part of the main distribution. At the moment, there are actually three separate Macintosh CVS clients available:

Frankly, I have no idea which one is best. Try them all, not necessarily in the order given, and see which one you like. MacCVS Pro seems to be under active development. MacCvs is apparently a companion project of WinCVS and shares a home page with it. (As of this writing, a notice on the WinCVS page states, "Development of MacCvs will be resumed soon.", whatever that means.)


Node: Limitations Of The Windows And Macintosh Versions, Previous: Getting And Installing CVS On A Macintosh, Up: Getting And Installing CVS

Limitations Of The Windows And Macintosh Versions

The Windows and Macintosh distributions of CVS are generally limited in functionality. They can all act as clients, meaning that they can contact a repository server to obtain a working copy, commit, update, and so on. But they can't serve repositories themselves. If you set it up right, the Windows port can use a local-disk repository, but it still can't serve projects from that repository to other machines. In general, if you want to have a network-accessible CVS repository, you must run the CVS server on a Unix box.


Node: Anatomy Of A CVS Distribution, Next: , Previous: Getting And Installing CVS, Up: Repository Administration

Anatomy Of A CVS Distribution

The preceding instructions are designed to get you up and running quickly, but there's a lot more inside a CVS source distribution than just the code. Here's a quick road map to the source tree, so you'll know which parts are useful resources and which can be ignored.


Node: Informational Files, Next: , Up: Anatomy Of A CVS Distribution

Informational Files

In the top level of the distribution tree, you'll find several files containing useful information (and pointers to further information). They are, in approximate order of importance:


Node: Subdirectories, Next: , Previous: Informational Files, Up: Anatomy Of A CVS Distribution

Subdirectories

The CVS distribution contains a number of subdirectories. In the course of a normal installation, you won't have to navigate among them, but if you want to go poking around in the sources, it's nice to know what each one does. Here they are:

     contrib/
     diff/
     doc/
     emx/
     lib/
     man/
     os2/
     src/
     tools/
     vms/
     windows-NT/
     zlib/
     

The majority of these can be ignored. The emx/, os2/, vms/, and windows-NT/ subdirectories all contain operating-system-specific source code, which you would only need if you're actually trying to debug a code-level problem in CVS (an unlikely situation, though not unheard of). The diff/ and zlib/ subdirectories contain CVS's internal implementations of the diff program and the GNU gzip compression library, respectively. (CVS uses the latter to reduce the number of bits it has to send over the network when accessing remote repositories.)

The contrib/ and tools/ subdirectories contain free third-party software meant to be used with CVS. In contrib/, you will find an assortment of small, specialized shell scripts (read contrib/README to find out what they do). The tools/ subdirectory used to contain contributed software, but now contains a README file, which says in part:

     This subdirectory formerly contained tools that can be used with CVS.
     In particular, it used to contain a copy of pcl-cvs version 1.x.
     Pcl-cvs is an Emacs interface to CVS.
     
     If you are looking for pcl-cvs, we'd suggest pcl-cvs version 2.x, at:
         ftp://ftp.weird.com/pub/local/
     

The PCL-CVS package it's referring to is very handy, and I'll have more to say about it in Third-Party Tools.

The src/ and lib/ subdirectories contain the bulk of the CVS source code, which involves the CVS internals. The main data structures and commands are implemented in src/, whereas lib/ contains small code modules of general utility that CVS uses.

The man/ subdirectory contains the CVS man pages (intended for the Unix online manual system). When you ran make install, they were incorporated into your Unix system's regular man pages, so you can type

     floss$ man cvs
     

and get a rather terse introduction and subcommand reference to CVS. Although useful as a quick reference, the man pages may not be as up to date or complete as the Cederqvist manual (see the next section); however, the man pages are more likely to be incomplete than actually wrong, if it's any comfort.


Node: The Cederqvist Manual, Next: , Previous: Subdirectories, Up: Anatomy Of A CVS Distribution

The Cederqvist Manual

That leaves the doc/ subdirectory, whose most important inhabitant is the famed Cederqvist. These days, it's probably a stretch to call it "the Cederqvist". Although Per Cederqvist (of Signum Support, Linkoping Sweden, www.signum.se) wrote the first version around 1992, it has been updated since then by many other people. For example, when contributors add a new feature to CVS, they usually also document it in the Cederqvist.

The Cederqvist manual is written in Texinfo format, which is used by the GNU project because it's relatively easy to produce both online and printed output from it (in Info and PostScript formats, respectively). The Texinfo master file is doc/cvs.texinfo, but CVS distributions come with the Info and PostScript pregenerated, so you don't have to worry about running any Texinfo tools yourself.

Although the Cederqvist can be used as an introduction and tutorial, it is probably most useful as a reference document. For that reason, most people browse it online instead of printing it out (although the PostScript file is doc/cvs.ps, for those with paper to spare). If this is the first time you've installed CVS on your system, you'll have to take an extra step to make sure the manual is accessible online.

The Info files (doc/cvs.info, doc/cvs.info-1, doc/cvs.info-2, and so on) were installed for you when you ran make install. Although the files were copied into the system's Info tree, you may still have to add a line for CVS to the Info table of contents, the "Top" node. (This will only be necessary if this is the first time CVS has been installed on your system; otherwise, the entry from previous installations should already be in the table of contents.)

If you've added new Info documentation before, you may be familiar with the process. First figure out where the Info pages were installed. If you used the default installation (in /usr/local/), then the Info files are /usr/local/info/cvs.info*. If you installed using

     floss$ ./configure --prefix=/usr
     

the files ended up as /usr/info/cvs.*. After you locate the files, you'll need to add a line for CVS to the Info table of contents, which is in a file named dir in that directory (so in the latter case, it would be /usr/info/dir). If you don't have root access, ask your system administrator to do it. Here is an excerpt from dir before the reference to CVS documentation was added:

     * Bison: (bison).         The Bison parser generator.
     * Cpp: (cpp).             The GNU C preprocessor.
     * Flex: (flex).           A fast scanner generator
     

And here is the same region of dir afterwards:

     * Bison: (bison).         The Bison parser generator.
     * Cpp: (cpp).             The GNU C preprocessor.
     * Cvs: (cvs).             Concurrent Versions System
     * Flex: (flex).           A fast scanner generator
     

The format of the line is very important. You must include the asterisk, spaces, and colon in * Cvs: and the parentheses and period in (cvs). after it. If any of these elements are missing, the Info dir format will be corrupt, and you'll be unable to read the Cederqvist.

Once the manual is installed and referred to from the table of contents, you can read it with any Info-compatible browser. The ones most likely to be installed on a typical Unix system are either the command-line Info reader, which can be invoked this way if you want to go straight to the CVS pages

     floss$ info cvs
     

and the one within Emacs, which is invoked by typing

     M-x info
     

or

     C-h i
     

Take whatever time is necessary to get the Cederqvist set up properly on your system when you install CVS; it will pay off many times down the road when you need to look something up.


Node: Other Sources Of Information, Previous: The Cederqvist Manual, Up: Anatomy Of A CVS Distribution

Other Sources Of Information

In addition to the Cederqvist, the FAQ, and the other files in the distribution itself, there are Internet resources devoted to CVS. If you're going to administrate a CVS server, you'll probably want to join the info-cvs mailing list. To subscribe, send email to [email protected] (the list itself is [email protected]). Traffic can be medium to heavy, around 10 to 20 emails a day, most of them questions seeking answers. The majority of these can be deleted without reading (unless you want to help people by answering their questions, which is always nice), but every now and then someone will announce the discovery of a bug or announce a patch that implements some feature you've been wanting.

You can also join the formal bug report mailing list, which includes every bug report sent in. This probably isn't necessary, unless you intend to help fix the bugs, which would be great, or you're terrifically paranoid and want to know about every problem other people find with CVS. If you do want to join, send email to [email protected].

There's also a Usenet newsgroup, comp.software.config-mgmt, which is about version control and configuration management systems in general, in which there is a fair amount of discussion about CVS.

Finally, there are at least three Web sites devoted to CVS. Cyclic Software's http://www.cyclic.com has been CVS's informal home site for a few years, and probably will continue to be for the foreseeable future. Cyclic Software also provides server space and Net access for the repository where the CVS sources are kept. The Cyclic Web pages contain comprehensive links to experimental patches for CVS, third-party tools that work with CVS, documentation, mailing list archives, and just about everything else. If you can't find what you need in the distribution, http://www.cyclic.com is the place to start looking.

Two other good sites are Pascal Molli's http://www.loria.fr/~molli/cvs-index.html and Sean Dreilinger's http://durak.org/cvswebsites/. The biggest attraction at Molli's site is, of course, the FAQ, but it also has links to CVS-related tools and mailing list archives. Dreilinger's site specializes in information about using CVS to manage Web documents and also has a CVS-specific search engine.


Node: Starting A Repository, Next: , Previous: Anatomy Of A CVS Distribution, Up: Repository Administration

Starting A Repository

Once the CVS executable is installed on your system, you can start using it right away as a client to access remote repositories, following the procedures described in An Overview of CVS. However, if you want to serve revisions from your machine, you have to create a repository there. The command to do that is

     floss$ cvs -d /usr/local/newrepos init
     

where /usr/local/newrepos is a path to wherever you want the repository to be (of course, you must have write permission to that location, which may imply running the command as the root user). It may seem somewhat counterintuitive that the location of the new repository is specified before the init subcommand instead of after it, but by using the -d option, it stays consistent with other CVS commands.

The command will return silently after it is run. Let's examine the new directory:

     floss$ ls -ld /usr/local/newrepos
     drwxrwxr-x   3 root     root         1024 Jun 19 17:59 /usr/local/newrepos/
     floss$ cd /usr/local/newrepos
     floss$ ls
     CVSROOT
     floss$ cd CVSROOT
     floss$ ls
     checkoutlist     config,v        history     notify     taginfo,v
     checkoutlist,v   cvswrappers     loginfo     notify,v   verifymsg
     commitinfo       cvswrappers,v   loginfo,v   rcsinfo    verifymsg,v
     commitinfo,v     editinfo        modules     rcsinfo,v
     config           editinfo,v      modules,v   taginfo
     
     floss$
     

The single subdirectory in the new repository - CVSROOT/ - contains various administrative files that control CVS's behavior. Later on, we'll examine those files one by one; for now, the goal is just to get the repository working. In this case, "working" means users can import, check out, update, and commit projects.

Don't confuse the CVSROOT environment variable introduced in An Overview of CVS with this CVSROOT subdirectory in the repository. They are unrelated - it is an unfortunate coincidence that they share the same name. The former is a way for users to avoid having to type -d <repository-location> every time they use CVS; the latter is the administrative subdirectory of a repository.

Once the repository is created, you must take care of its permissions. CVS does not require any particular, standardized permission or file ownership scheme; it merely needs write access to the repository. However - partly for security reasons, but mainly for your own sanity as an administrator - I highly recommend that you take the following steps:

  1. Add a Unix group cvs to your system. Any users who need to access the repository should be in this group. For example, here's the relevant line from my machine's /etc/group file:
              cvs:*:105:kfogel,sussman,jimb,noel,lefty,fitz,craig,anonymous,jrandom
              
  2. Make the repository's group ownership and permissions reflect this new group:
              floss$ cd /usr/local/newrepos
              floss$ chgrp -R cvs .
              floss$ chmod ug+rwx . CVSROOT
              

Now any of the users listed in that group can start a project by running cvs import, as described in An Overview of CVS. Checkout, update, and commit should work as well. They can also reach the repository from remote locations by using the :ext: method, assuming that they have rsh or ssh access to the repository machine. (You may have noticed that the chgrp and chmod commands in that example gave write access to a user named anonymous, which is not what one would expect. The reason is that even anonymous, read-only repository users need system-level write access, so that their CVS processes can create temporary lockfiles inside the repository. CVS enforces the "read-only" restriction of anonymous access not through Unix file permissions, but by other means, which will be covered in Anonymous Access.)

If your repository is intended to serve projects to the general public, where contributors won't necessarily have accounts on the repository machine, you should set up the password-authenticating server now (see The Password-Authenticating Server). It's necessary for anonymous read-only access, and it's also probably the easiest way to grant commit access to certain people without giving them full accounts on the machine.


Node: The Password-Authenticating Server, Next: , Previous: Starting A Repository, Up: Repository Administration

The Password-Authenticating Server

Before running through the steps needed to set up the password server, let's examine how such connections work in the abstract. When a remote CVS client uses the :pserver: method to connect to a repository, the client is actually contacting a specific port number on the server machine - specifically, port number 2401 (which is 49 squared, if you like that sort of thing). Port 2401 is the designated default port for the CVS pserver, although one could arrange for a different port to be used as long as both client and server agree on it.

The CVS server is not actually waiting for connections at that port - the server won't get started up until a connection actually arrives. Instead, the Unix inetd (InterNET Daemon) program is listening on that port, and needs to know that when it receives a connection request there, it should start up the CVS server and connect it to the incoming client.

This is accomplished by modifying inetd's configuration files: /etc/services and /etc/inetd.conf. The services file maps raw port numbers to service names and then inetd.conf tells inetd what to do for a given service name.

First, put a line like this into /etc/services (after checking to make sure it isn't already there):

     cvspserver	2401/tcp
     

Then in /etc/inetd.conf, put this:

     cvspserver stream tcp nowait root /usr/local/bin/cvs cvs \
        --allow-root=/usr/local/newrepos pserver
     

(In the actual file, this should be all one long line, with no backslash.) If your system uses tcpwrappers, you may want to use something like this instead:

     cvspserver stream tcp nowait root /usr/sbin/tcpd /usr/local/bin/cvs \
        --allow-root=/usr/local/newrepos pserver
     

Now, restart inetd so it notices the changes to its configuration files (if you don't know how to restart the daemon, just reboot the machine - that will work too).

That's enough to permit connections, but you'll also want to set up special CVS passwords - separate from the users' regular login passwords - so people can access the repository without compromising overall system security.

The CVS password file is CVSROOT/passwd in the repository. It was not created by default when you ran cvs init, because CVS doesn't know for sure that you'll be using pserver. Even if the password file had been created, CVS would have no way of knowing what usernames and passwords to create. So, you'll have to create one yourself; here's a sample CVSRoot/passwd file:

     kfogel:rKa5jzULzmhOo
     anonymous:XR4EZcEs0szik
     melissa:tGX1fS8sun6rY:pubcvs
     

The format is as simple as it looks. Each line is:

     <USERNAME>:<ENCRYPTED_PASSWORD>:<OPTIONAL_SYSTEM_USERNAME>
     

The extra colon followed by an optional system username tells CVS that connections authenticated with USERNAME should run as the system account SYSTEM_USERNAME - in other words, that CVS session would only be able to do things in the repository that someone logged in as SYSTEM_USERNAME could do.

If no system username is given, USERNAME must match an actual login account name on the system, and the session will run with that user's permissions. In either case, the encrypted password should not be the same as the user's actual login password. It should be an independent password used only for CVS pserver connections.

The password is encrypted using the same algorithm as the standard Unix system passwords stored in /etc/passwd. You may be wondering at this point, how does one acquire an encrypted version of a password? For Unix system passwords, the passwd command takes care of the encryption in /etc/passwd for you. Unfortunately, there is no corresponding cvs passwd command (it has been proposed several times, but no one's gotten around to writing it - perhaps you'll do it?).

This is an inconvenience, but only a slight one. If nothing else, you can always temporarily change a regular user's system password using passwd, cut and paste the encrypted text from /etc/passwd into CVSROOT/passwd, and then restore the old password (note that on some systems, the encrypted passwords are found in /etc/shadow and are readable only by root.)

That scheme is workable but rather cumbersome. It would be much easier to have a command-line utility that takes a plain text password as its argument and outputs the encrypted version. Here is such a tool, written in Perl:

     #!/usr/bin/perl
     
     srand (time());
     my $randletter = "(int (rand (26)) + (int (rand (1) + .5) % 2 ? 65 : 97))";
     my $salt = sprintf ("%c%c", eval $randletter, eval $randletter);
     my $plaintext = shift;
     my $crypttext = crypt ($plaintext, $salt);
     
     print "${crypttext}\n";
     

I keep the preceding script in /usr/local/bin/cryptout.pl:

     floss$ ls -l /usr/local/bin/cryptout.pl
     
     -rwxr-xr-x   1   root   root   265  Jun 14 20:41 /usr/local/bin/cryptout.pl
     floss$ cryptout.pl "some text"
     sB3A79YDX5L4s
     
     floss$
     

If I took the output of this example and used it to create the following entry in CVSROOT/passwd

     jrandom:sB3A79YDX5L4s:craig
     

then someone could connect to the repository with the following command:

     remote$ cvs -d :pserver:[email protected]:/usr/local/newrepos login
     

They could then type some text as their password and thereafter be able to execute CVS commands with the same access privileges as the system user craig.

If someone attempts to authenticate with a username and password that don't appear in CVSROOT/passwd, CVS will check to see if that username and password are present in /etc/passwd. If they are (and if the password matches, of course), CVS will grant access. It behaves this way for the administrator's convenience, so that separate CVSROOT/passwd entries don't have to be set up for regular system users. However, this behavior is also a security hole, because it means that if one of those users does connect to the CVS server, her regular login password will have crossed over the network in cleartext, potentially vulnerable to the eyes of password sniffers. A bit further on, you'll learn how to turn off this "fallback" behavior, so that CVS consults only its own passwd file. Whether you leave it on or off, you should probably force any CVS users who also have login accounts to maintain different passwords for the two functions.

Although the passwd file authenticates for the whole repository, with a little extra work you can still use it to grant project-specific access. Here's one method:

Suppose you want to grant some remote developers access to project foo, and others access to project bar, and you don't want developers from one project to have commit access to the other. You can accomplish this by creating project-specific user accounts and groups on the system and then mapping to those accounts in the CVSROOT/passwd file.

Here's the relevant excerpt from /etc/passwd

     cvs-foo:*:600:600:Public CVS Account for Project Foo:/usr/local/cvs:/bin/false
     cvs-bar:*:601:601:Public CVS Account for Project Bar:/usr/local/cvs:/bin/false
     

and from /etc/group

     cvs-foo:*:600:cvs-foo
     cvs-bar:*:601:cvs-bar
     

and, finally, CVSROOT/passwd:

     kcunderh:rKa5jzULzmhOo:cvs-foo
     jmankoff:tGX1fS8sun6rY:cvs-foo
     brebard:cAXVPNZN6uFH2:cvs-foo
     xwang:qp5lsf7nzRzfs:cvs-foo
     dstone:JDNNF6HeX/yLw:cvs-bar
     twp:glUHEM8KhcbO6:cvs-bar
     ffranklin:cG6/6yXbS9BHI:cvs-bar
     yyang:YoEqcCeCUq1vQ:cvs-bar
     

Some of the CVS usernames map onto the system user account cvs-foo and some onto cvs-bar. Because CVS runs under the user ID of the system account, you just have to make sure that the relevant parts of the repository are writeable only by the appropriate users and groups. If you just make sure that the user accounts are locked down pretty tight (no valid login password, /bin/false as the shell), then this system is reasonably secure (but see later in this chapter about CVSROOT permissions!). Also, CVS does record changes and log messages under the CVS username, not the system username, so you can still tell who is responsible for a given change.


Node: Anonymous Access, Next: , Previous: The Password-Authenticating Server, Up: Repository Administration

Anonymous Access

So far we've only seen how to use the password-authenticating server to grant normal full access to the repository (although admittedly one can restrict that access through carefully arranged Unix file permissions). Turning this into anonymous, read-only access is a simple step: You just have to add a new file, or possibly two, in CVSROOT/. The files' names are readers and writers - the former containing a list of usernames who can only read the repository, the latter users who can read and write.

If you list a username in CVSROOT/readers, that user will have only read access to all projects in the repository. If you list a username in CVSROOT/writers, that user will have write access, and every pserver user not listed in writers will have read-only access (that is, if the writers file exists at all, it implies read-only access for all those not listed in it). If the same username is listed in both files, CVS resolves the conflict in the more conservative way: the user will have read-only access.

The format of the files is very simple: one user per line (don't forget to put a newline after the last user). Here is a sample readers file:

     anonymous
     splotnik
     guest
     jbrowse
     

Note that the files apply to CVS usernames, not system usernames. If you use user aliasing in the CVSROOT/passwd file (putting a system username after a second colon), the leftmost username is the one to list in a readers or writers file.

Just to be painfully accurate about it, here is a formal description of the server's behavior in deciding whether to grant read-only or read-write access:

If a readers file exists and this user is listed in it, then she gets read-only access. If a writers file exists and this user is not listed in it, then she also gets read-only access (this is true even if a readers file exists but that person is not listed there). If that person is listed in both, she gets read-only access. In all other cases, that person gets full read-write access.

Thus, a typical repository with anonymous CVS access has this (or something like it) in CVSROOT/passwd

     anonymous:XR4EZcEs0szik
     

this (or something like it) in /etc/passwd

     anonymous:!:1729:105:Anonymous CVS User:/usr/local/newrepos:/bin/false
     

and this in CVSROOT/readers:

     anonymous
     

And, of course, the aforementioned setup in /etc/services and /etc/inetd.conf. That's all there is to it!

Note that some older Unix systems don't support usernames longer than eight characters. One way to get around this would be to call the user anon instead of anonymous in CVSROOT/passwd and in the system files, because people often assume that anon is short for anonymous anyway. But it might be better to put something like this into the CVSROOT/passwd file

     anonymous:XR4EZcEs0szik:cvsanon
     

(and then of course use cvsanon in the system files). That way, you'd be able to publish a repository address that uses anonymous, which is more or less standard now. People accessing the repository with

     cvs -d :pserver:[email protected]:/usr/local/newrepos (etc...)
     

would actually run on the server as cvsanon (or whatever). But they wouldn't need to know or care about how things are set up on the server side - they'd only see the published address.


Node: Repository Structure, Next: , Previous: Anonymous Access, Up: Repository Administration

Repository Structure

The new repository still has no projects in it. Let's re-run the initial import from An Overview of CVS, watching what happens to the repository. (For simplicity's sake, all commands will assume that the CVSROOT environment variable has been set to /usr/local/newrepos, so there's no need to specify the repository with -d on imports and checkouts.)

     floss$ ls /usr/local/newrepos
     CVSROOT/
     floss$ pwd
     /home/jrandom/src/
     floss$ ls
     myproj/
     floss$ cd myproj
     floss$ cvs import -m "initial import into CVS" myproj jrandom start
     N myproj/README.txt
     N myproj/hello.c
     cvs import: Importing /usr/local/newrepos/myproj/a-subdir
     N myproj/a-subdir/whatever.c
     cvs import: Importing /usr/local/newrepos/myproj/a-subdir/subsubdir
     N myproj/a-subdir/subsubdir/fish.c
     cvs import: Importing /usr/local/newrepos/myproj/b-subdir
     N myproj/b-subdir/random.c
     
     No conflicts created by this import
     
     floss$ ls /usr/local/newrepos
     CVSROOT/  myproj/
     floss$ cd /usr/local/newrepos/myproj
     floss$ ls
     README.txt,v  a-subdir/     b-subdir/	  hello.c,v
     floss$ cd a-subdir
     floss$ ls
     subsubdir/    whatever.c,v
     floss$ cd ..
     
     floss$
     

Before the import, the repository contained only its administrative area, CVSROOT. After the import, a new directory - myproj - appeared. The files and subdirectories inside that new directory look suspiciously like the project we imported, except that the files have the suffix ,v. These are RCS-format version control files (the ,v stands for "version"), and they are the backbone of the repository. Each RCS file stores the revision history of its corresponding file in the project, including all branches and tags.


Node: RCS Format, Next: , Previous: Repository Structure, Up: Repository Administration

RCS Format

You do not need to know any of the RCS format to use CVS (although there is an excellent writeup included with the source distribution, see doc/RCSFILES). However, a basic understanding of the format can be of immense help in troubleshooting CVS problems, so we'll take a brief peek into one of the files, hello.c,v. Here are its contents:

     head     1.1;
     branch   1.1.1;
     access   ;
     symbols  start:1.1.1.1 jrandom:1.1.1;
     locks    ; strict;
     comment  @ * @;
     
     1.1
     date     99.06.20.17.47.26;  author jrandom;  state Exp;
     branches 1.1.1.1;
     next;
     
     1.1.1.1
     date     99.06.20.17.47.26;  author jrandom;  state Exp;
     branches ;
     next;
     
     desc
     @@
     
     1.1
     log
     @Initial revision
     @
     text
     @#include <stdio.h>
     
     void
     main ()
     {
       printf ("Hello, world!\n");
     }
     @
     
     1.1.1.1
     log
     @initial import into CVS
     @
     text
     @@
     

Whew! Most of that you can ignore; don't worry about the relationship between 1.1 and 1.1.1.1, for example, or the implied 1.1.1 branch - they aren't really significant from a user's or even an administrator's point of view. What you should try to grok is the overall format. At the top is a collection of header fields:

     head     1.1;
     branch   1.1.1;
     access   ;
     symbols  start:1.1.1.1 jrandom:1.1.1;
     locks    ; strict;
     comment  @ * @;
     

Farther down in the file are groups of meta-information about each revision (but still not showing the contents of that revision), such as:

     1.1
     date     99.06.20.17.47.26;  author jrandom;  state Exp;
     branches 1.1.1.1;
     next     ;
     

And finally, the log message and text of an actual revision:

     1.1
     log
     @Initial revision
     @
     text
     @#include <stdio.h>
     
     void
     main ()
     {
       printf ("Hello, world!\n");
     }
     @
     
     1.1.1.1
     log
     @initial import into CVS
     @
     text
     @@
     

If you look closely, you'll see that the first revision's contents are stored under the heading 1.1, but that the log message there is "Initial revision", whereas the log message we actually used at import time was "initial import into CVS", which appears farther down, under Revision 1.1.1.1. You don't need to worry about this discrepancy right now. It happens because imports are a special circumstance: In order to make repeated imports into the same project have a useful effect, import actually places the initial revision on both the main trunk and on a special branch (the reasons for this will become clearer when we look at vendor branches in Advanced CVS). For now, you can treat 1.1 and 1.1.1.1 as the same thing.

The file becomes even more revealing after we commit the first modification to hello.c:

     floss$ cvs -Q co myproj
     floss$ cd myproj
     floss$ emacs hello.c
         (make some changes to the file)
     
     floss$ cvs ci -m "print goodbye too"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in hello.c;
     /usr/local/newrepos/myproj/hello.c,v  <--  hello.c
     new revision: 1.2; previous revision: 1.1
     done
     

If you look at hello.c,v in the repository now, you can see the effect of the commit:

     head  1.2;
     access;
     symbols
           start:1.1.1.1 jrandom:1.1.1;
     locks; strict;
     comment   @ * @;
     
     1.2
     date   99.06.21.01.49.40;   author jrandom;   state Exp;
     branches;
     next   1.1;
     
     1.1
     date   99.06.20.17.47.26;   author jrandom;   state Exp;
     branches
            1.1.1.1;
     next   ;
     
     1.1.1.1
     date   99.06.20.17.47.26;   author jrandom;   state Exp;
     branches;
     next   ;
     
     desc
     @@
     
     1.2
     log
     @print goodbye too
     @
     text
     @#include <stdio.h>
     
     void
     main ()
     {
       printf ("Hello, world!\n");
       printf ("Goodbye, world!\n");
     }
     @
     
     1.1
     log
     @Initial revision
     @
     text
     @d7 1
     @
     
     1.1.1.1
     log
     @initial import into CVS
     @
     text
     @@
     

Now the full contents of Revision 1.2 are stored in the file, and the text for Revision 1.1 has been replaced with the cryptic formula:

     d7 1
     

The d7 1 is a diff code that means "starting at line 7, delete 1 line". In other words, to derive Revision 1.1, delete line 7 from Revision 1.2! Try working through it yourself. You'll see that it does indeed produce Revision 1.1 - it simply does away with the line we added to the file.

This demonstrates the basic principle of RCS format: It stores only the differences between revisions, thereby saving a lot of space compared with storing each revision in full. To go backwards from the most recent revision to the previous one, it patches the later revision using the stored diff. Of course, this means that the further back you travel in the revision history, the more patch operations must be performed (for example, if the file is on Revision 1.7 and CVS is asked to retrieve Revision 1.4, it has to produce 1.6 by patching backwards from 1.7, then 1.5 by patching 1.6, then 1.4 by patching 1.5). Fortunately, old revisions are also the ones least often retrieved, so the RCS system works out pretty well in practice: The more recent the revision, the cheaper it is to obtain.

As for the header information at the top of the file, you don't need to know what all of it means. However, the effects of certain operations show up very clearly in the headers, and a passing familiarity with them may prove useful.

When you commit a new revision on the trunk, the head label is updated (note how it became 1.2 in the preceding example, when the second revision to hello.c was committed). When you add a file as binary or tag it, those operations are recorded in the headers as well. As an example, we'll add foo.jpg as a binary file and then tag it a couple of times:

     floss$ cvs add -kb foo.jpg
     cvs add: scheduling file 'foo.jpg' for addition
     cvs add: use 'cvs commit' to add this file permanently
     floss$ cvs -q commit -m "added a random image; ask [email protected] why"
     RCS file: /usr/local/newrepos/myproj/foo.jpg,v
     done
     Checking in foo.jpg;
     /usr/local/newrepos/myproj/foo.jpg,v  <--  foo.jpg
     initial revision: 1.1
     done
     floss$ cvs tag some_random_tag foo.jpg
     T foo.jpg
     floss$ cvs tag ANOTHER-TAG foo.jpg
     T foo.jpg
     floss$
     

Now examine the header section of foo.jpg,v in the repository:

     head   1.1;
     access;
     symbols
           ANOTHER-TAG:1.1
           some_random_tag:1.1;
     locks; strict;
     comment   @# @;
     expand	@b@;
     

Notice the b in the expand line at the end - it's due to our having used the -kb flag when adding the file, and means the file won't undergo any keyword or newline expansions, which would normally occur during checkouts and updates if it were a regular text file. The tags appear in the symbols section, one tag per line - both of them are attached to the first revision, since that's what was tagged both times. (This also helps explain why tag names can only contain letters, numbers, hyphens, and underscores. If the tag itself contained colons or dots, the RCS file's record of it might be ambiguous, because there would be no way to find the textual boundary between the tag and the revision to which it is attached.)

RCS Format Always Quotes @ Signs

The @ symbol is used as a field delimiter in RCS files, which means that if one appears in the text of a file or in a log message, it must be quoted (otherwise, CVS would incorrectly interpret it as marking the end of that field). It is quoted by doubling - that is, CVS always interprets @@ as "literal @ sign", never as "end of current field". When we committed foo.jpg, the log message was

     "added a random image; ask [email protected] why"
     

which is stored in foo.jpg,v like this:

     1.1
     log
     @added a random image; ask jrandom@@red-bean.com why
     @
     

The @ sign in jrandom@@red-bean.com will be automatically unquoted whenever CVS retrieves the log message:

     floss$ cvs log foo.jpg
     RCS file: /usr/local/newrepos/myproj/foo.jpg,v
     Working file: foo.jpg
     head: 1.1
     branch:
     locks: strict
     access list:
     symbolic names:
           ANOTHER-TAG: 1.1
           some_random_tag: 1.1
     keyword substitution: b
     total revisions: 1;	selected revisions: 1
     description:
     ----------------------------
     revision 1.1
     date: 1999/06/21 02:56:18;  author: jrandom;  state: Exp;
     added a random image; ask [email protected] why
     ============================================================================
     
     floss$
     

The only reason you should care is that if you ever find yourself hand-editing RCS files (a rare circumstance, but not unheard of), you must remember to use double @ signs in revision contents and log messages. If you don't, the RCS file will be corrupt and will probably exhibit strange and undesirable behaviors.

Speaking of hand-editing RCS files, don't be fooled by the permissions in the repository:

     floss$ ls -l
     total 6
     -r--r--r--   1 jrandom   users         410 Jun 20 12:47 README.txt,v
     drwxrwxr-x   3 jrandom   users        1024 Jun 20 21:56 a-subdir/
     drwxrwxr-x   2 jrandom   users        1024 Jun 20 21:56 b-subdir/
     -r--r--r--   1 jrandom   users         937 Jun 20 21:56 foo.jpg,v
     -r--r--r--   1 jrandom   users         564 Jun 20 21:11 hello.c,v
     
     floss$
     

(For those not fluent in Unix ls output, the -r--r--r-- lines on the left essentially mean that the files can be read but not changed.) Although the files appear to be read-only for everyone, the directory permissions must also be taken into account:

     floss$ ls -ld .
     drwxrwxr-x   4 jrandom   users        1024 Jun 20 22:16 ./
     floss$
     

The myproj/ directory itself - and its subdirectories - are all writeable by the owner (jrandom) and the group (users). This means that CVS (running as jrandom, or as anyone in the users group) can create and delete files in those directories, even if it can't directly edit files already present. CVS edits an RCS file by making a separate copy of it, so you should also make all of your changes in a temporary copy, and then replace the existing RCS file with the new one. (But please don't ask why the files themselves are read-only - there are historical reasons for that, having to do with the way RCS works when run as a standalone program.)

Incidentally, having the files' group be users is probably not what you want, considering that the top-level directory of the repository was explicitly assigned group cvs. You can correct the problem by running this command inside the repository:

     floss$ cd /usr/local/newrepos
     floss$ chgrp -R cvs myproj
     

The usual Unix file-creation rules govern which group is assigned to new files that appear in the repository, so once in a while you may need to run chgrp or chmod on certain files or directories in the repository (setting the SGID bit with chmod g+s is often a good strategy: it makes children of a directory inherit the directory's group ownership, which is usually what you want in the repository). There are no hard and fast rules about how you should structure repository permissions; it just depends on who is working on what projects.


Node: What Happens When You Remove A File, Next: , Previous: RCS Format, Up: Repository Administration

What Happens When You Remove A File

When you remove a file from a project, it doesn't just disappear. CVS must be able to retrieve such files when you request an old snapshot of the project. Instead, the file gets put in the Attic, literally:

     floss$ pwd
     /home/jrandom/src/myproj
     floss$ ls /usr/local/newrepos/myproj/
     README.txt,v  a-subdir/     b-subdir/     foo.jpg,v   hello.c,v
     floss$ rm foo.jpg
     floss$ cvs rm foo.jpg
     cvs remove: scheduling 'foo.jpg' for removal
     cvs remove: use 'cvs commit' to remove this file permanently
     floss$ cvs ci -m "Removed foo.jpg" foo.jpg
     Removing foo.jpg;
     /usr/local/newrepos/myproj/foo.jpg,v  <--  foo.jpg
     new revision: delete; previous revision: 1.1
     done
     floss$ cd /usr/local/newrepos/myproj/
     floss$ ls
     Attic/      README.txt,v  a-subdir/     b-subdir/   hello.c,v
     floss$ cd Attic
     floss$ ls
     foo.jpg,v
     floss$
     

In each repository directory of a project, the presence of an Attic/ subdirectory means that at least one file has been removed from that directory (this means that you shouldn't use directories named Attic in your projects). CVS doesn't merely move the RCS file into Attic/, however; it also commits a new revision into the file, with a special revision state of dead. Here's the relevant section from Attic/foo.jpg,v:

     1.2
     date   99.06.21.03.38.07;   author jrandom;   state dead;
     branches;
     next	1.1;
     

If the file is later brought back to life, CVS has a way of recording that it was dead at some point in the past and is now alive again.

This means that if you want to restore a removed file, you can't just take it out of the Attic/ and put it back into the project. Instead, you have to do something like this in a working copy:

     floss$ pwd
     /home/jrandom/src/myproj
     floss$ cvs -Q update -p -r 1.1 foo.jpg > foo.jpg
     floss$ ls
     CVS/       README.txt   a-subdir/   b-subdir/   foo.jpg     hello.c
     floss$ cvs add -kb foo.jpg
     cvs add: re-adding file foo.jpg (in place of dead revision 1.2)
     cvs add: use 'cvs commit' to add this file permanently
     floss$ cvs ci -m "revived jpg image" foo.jpg
     Checking in foo.jpg;
     /usr/local/newrepos/myproj/foo.jpg,v  <-- foo.jpg
     new revision: 1.3; previous revision: 1.2
     done
     floss$ cd /usr/local/newrepos/myproj/
     floss$ ls
     Attic/	      a-subdir/     foo.jpg,v
     README.txt,v  b-subdir/     hello.c,v
     floss$ ls Attic/
     floss$
     

There's a lot more to know about RCS format, but this is sufficient for a CVS adminstrator to maintain a repository. It's quite rare to actually edit an RCS file; you'll usually just have to tweak file permissions in the repository, at least if my own experience is any guide. Nevertheless, when CVS starts behaving truly weirdly (rare, but not completely outside the realm of possibility), you may want to actually look inside the RCS files to figure out what's going on.


Node: The CVSROOT/ Administrative Directory, Next: , Previous: What Happens When You Remove A File, Up: Repository Administration

The CVSROOT/ Administrative Directory

The files in newrepos/CVSROOT/ are not part of any project, but are used to control CVS's behavior in the repository. The best way to edit those files is to check out a working copy of CVSROOT, just like a regular project:

     floss$ cvs co CVSROOT
     cvs checkout: Updating CVSROOT
     U CVSROOT/checkoutlist
     U CVSROOT/commitinfo
     U CVSROOT/config
     U CVSROOT/cvswrappers
     U CVSROOT/editinfo
     U CVSROOT/loginfo
     U CVSROOT/modules
     U CVSROOT/notify
     U CVSROOT/rcsinfo
     U CVSROOT/taginfo
     U CVSROOT/verifymsg
     floss$
     

We'll take the files in their approximate order of importance. Note that each of the files comes with an explanatory comment at the beginning (the comment convention is the same across all of them: A # sign at the beginning of the line signifies a comment, and CVS ignores such lines when parsing the files). Remember that any change you make to the administrative files in your checked out working copy won't affect CVS's behavior until you commit the changes.

If you're extremely security conscious, you may want to arrange the Unix-level permissions on CVSROOT to be different from permissions elsewhere in the repository, in order to have fine-grained control over who can commit changes to CVSROOT. As you'll see a little later, being able to modify the files in CVSROOT essentially gives any CVS user - even remote ones - the ability to run arbitrary commands on the repository machine.


Node: The config File, Next: , Up: The CVSROOT/ Administrative Directory

The config File

The config file allows you to configure certain global behavioral parameters. It follows a very strict format

     PARAMETER=VALUE
     (etc)
     

with no extra spaces allowed. For example, here is a possible config file:

     SystemAuth=yes
     TopLevelAdmin=no
     PreservePermissions=no
     

(An absent entry would be equivalent to no.)

The SystemAuth parameter governs whether CVS should look in the system passwd file if it fails to find a given username in the CVSROOT/passwd file. CVS distributions are shipped with this set to no to be conservative about your system's security.

TopLevelAdmin tells CVS whether to make a sibling CVS/ directory when it checks out a working copy. This CVS/ directory would not be inside the working copy, but rather next to it. It might be convenient to turn this on if you tend (and your repository's users tend) to check out many different projects from the same repository. Otherwise, you should leave it off, as it can be disconcerting to see an extra CVS/ directory appear where you don't expect it.

PreservePermissions governs whether to preserve file permissions and similar metadata in the revision history. This is a somewhat obscure feature that probably isn't worth describing in detail. See the node Special Files in the Cederqvist if you're interested (node is Texinfo-speak for a particular location within an Info document. To go to a node while reading Info, just type g followed by the name of the node, from anywhere inside the document).

LockDir is also a rarely used feature. In special circumstances, you may want to tell CVS to create its lockfiles somewhere other than directly in the project subdirectories, in order to avoid permission problems. These lockfiles keep CVS from tripping over itself when multiple operations are performed on the same repository directory simultaneously. Generally, you never need to worry about them, but sometimes users may have trouble updating or checking out from a repository directory because they're unable to create a lockfile (even on read-only operations, CVS needs to create a lockfile to avoid situations where it could end up reading while another invocation of CVS is writing). The usual fix for this is to change repository permissions, but when that's not feasible, the LockDir parameter can come in handy.

There are no other parameters at this time, but future versions of CVS may add new ones; you should always check the Cederqvist or the distribution config file itself for updates.


Node: The modules File, Next: , Previous: The config File, Up: The CVSROOT/ Administrative Directory

The modules File

In modules, you can define aliases and alternate groupings for projects in the repository. The most basic module line is of the form:

     MODULE_NAME   DIRECTORY_IN_REPOSITORY
     

for example,

     mp    myproj
     asub  myproj/a-subdir
     

(The paths given on the right are relative to the top of the repository.) This gives developers an alternate name by which to check out a project or a portion of a project:

     floss$ cvs co mp
     cvs checkout: Updating mp
     U mp/README.txt
     U mp/foo.jpg
     U mp/hello.c
     cvs checkout: Updating mp/a-subdir
     U mp/a-subdir/whatever.c
     cvs checkout: Updating mp/a-subdir/subsubdir
     U mp/a-subdir/subsubdir/fish.c
     cvs checkout: Updating mp/b-subdir
     U mp/b-subdir/random.c
     

or

     floss$ cvs -d /usr/local/newrepos/ co asub
     cvs checkout: Updating asub
     U asub/whatever.c
     cvs checkout: Updating asub/subsubdir
     U asub/subsubdir/fish.c
     

Notice how in both cases the module's name became the name of the directory created for the working copy. In the case of asub, it didn't even bother with the intermediate myproj/ directory, but created a top-level asub/ instead, even though it came from myproj/a-subdir in the repository. Updates, commits, and all other CVS commands will behave normally in those working copies - the only thing unusual about them are their names.

By putting file names after the directory name, you can define a module consisting of just some of the files in a given repository directory. For example

     readme  myproj  README.txt
     

and

     no-readme  myproj  hello.c  foo.jpg
     

would permit the following checkouts, respectively:

     floss$ cvs -q co readme
     U readme/README.txt
     floss$ cvs -q co no-readme
     U no-readme/hello.c
     U no-readme/foo.jpg
     floss$
     

You can define a module that will include multiple repository directories by using the -a (for alias) flag, but note that the directories will get them checked out under their original names. For example, this line

     twoproj  -a  myproj  yourproj
     

would allow you to do this (assuming that both myproj/ and yourproj/ are in the repository):

     floss$ cvs co twoproj
     U myproj/README.txt
     U myproj/foo.jpg
     U myproj/hello.c
     U myproj/a-subdir/whatever.c
     U myproj/a-subdir/subsubdir/fish.c
     U myproj/b-subdir/random.c
     U yourproj/README
     U yourproj/foo.c
     U yourproj/some-subdir/file1.c
     U yourproj/some-subdir/file2.c
     U yourproj/some-subdir/another-subdir/blah.c
     

The name twoproj was a convenient handle to pull in both projects, but it didn't affect the names of the working copies. (There is no requirement that alias modules refer to multiple directories, by the way; we could have omitted twoproj, in which case myproj would still have been checked out under the name myproj.)

Modules can even refer to other modules, by prefixing them with an ampersand:

     mp    myproj
     asub  myproj/a-subdir
     twoproj -a myproj yourproj
     tp  &twoproj
     

Doing a checkout of tp would have exactly the same result as the checkout of twoproj did.

There are a few other tricks you can do with modules, most of them less frequently used than the ones just presented. See the node modules in the Cederqvist for information about them.


Node: The commitinfo And loginfo And rcsinfo Files, Next: , Previous: The modules File, Up: The CVSROOT/ Administrative Directory

The commitinfo And loginfo And rcsinfo Files

Most of the other administrative files provide programmatic hooks into various parts of the commit process (for example, the ability to validate log messages or file states before permitting the commit, or the ability to notify a group of developers whenever a commit happens in a certain directory of the repository).

The files generally share a common syntax. Each line is of the form:

     REGULAR_EXPRESSION    PROGRAM_TO_RUN
     

The regular expression will be tested against the directory into which the commit is taking place (with the directory name relative to the top of the repository). If it matches, the designated program will be run. The program will be passed the names of each of the files in the commit; it can do whatever it likes with those names, including opening up the files and examining their contents. If the program returns with a nonzero exit status, the commit is prevented from taking place.

(Regular expressions are a system for concisely describing classes of strings. If you aren't familiar with regular expressions, you can get by with the following short summary: foo would match any file whose name contains the string foo; and foo.*bar would match any file whose name contains foo, followed by any number of characters, followed by the string bar. That's because normal substrings match themselves, but . and * are special. . matches any character, and * means match any number of the preceding character, including zero. The ^ and $ signs mean match at the beginning and end of the string, respectively; thus, ^foo.*bar.*baz$ would match any string beginning with foo, containing bar somewhere in the middle, and ending with baz. That's all we'll go into here; this summary is a very abbreviated subset of full regular expression syntax.)

The commitinfo file is for generic hooks you want run on every commit. Here are some example commitinfo lines:

     ^a-subdir*     /usr/local/bin/check-asubdir.sh
     ou             /usr/local/bin/validate-project.pl
     

So any commit into myproj/a-subdir/ would match the first line, which would then run the check-asubdir.sh script. A commit in any project whose name (actual repository directory name, not necessarily module name) contained the string ou would run the validate-project.pl script, unless the commit had already matched the previous a-subdir line.

In place of a regular expression, the word DEFAULT or ALL may be used. The DEFAULT line (or the first DEFAULT line, if there are more than one) will be run if no regular expression matches, and each of the ALL lines will be run in addition to any other lines that may match.

The file names passed to the program do not refer to RCS files - they point to normal files, whose contents are exactly the same as the working-copy files being committed. The only unusual aspect is that CVS has them temporarily placed inside the repository, so they'll be available to programs running on the machine where the repository is located.

The loginfo file is similar to commitinfo, except that instead of acting on the files' contents, it acts on the log message. The left side of the loginfo file contains regular expressions, including possibly DEFAULT and ALL lines. The program invoked on the right side receives the log message on its standard input; it can do whatever it wants with that input.

The program on the right side can also take an arbitrary number of command-line arguments. One of those arguments can be a special % code, to be expanded by CVS at runtime, as follows:

     %s    ------>      name(s) of the file(s) being committed
     %V    ------>      revision number(s) before the commit
     %v    ------>      revision number(s) after the commit
     

The expansion always begins with the repository subdirectory (relative to the top of the repository), followed by the per-file information. For example, if the files committed were foo, bar, and baz, all in myproj/a-subdir, then %s would expand into

     myproj/a-subdir  foo  bar  baz
     

whereas %V would expand to show their old revision numbers

     myproj/a-subdir  1.7  1.134  1.12
     

and %v their new revision numbers:

     myproj/a-subdir  1.8  1.135  1.13
     

You can combine % expressions by enclosing them in curly braces following % sign - this will expand them into a series of comma-separated sublists, each containing the corresponding information for one file in the commit. For instance, %{sv} would expand to

     myproj/a-subdir  foo,1.8  bar,1.135  baz,1.13
     

and %{sVv} would expand to

     myproj/a-subdir  foo,1.7,1.8  bar,1.134,1.135  baz,1.12,1.13
     

(You may have to look carefully to distinguish the commas from the periods in those examples.)

Here is a sample loginfo file:

     ^myproj$   /usr/local/newrepos/CVSROOT/log.pl -m [email protected] %s
     ou         /usr/local/bin/ou-notify.pl  %{sv}
     DEFAULT    /usr/local/bin/default-notify.pl  %{sVv}
     

In the first line, any commit in the myproj subdirectory of the repository invokes log.pl, passing it an email address (to which log.pl will send a mail containing the log message), followed by the repository, followed by all the files in the commit.

In the second line, any commit in a repository subdirectory containing the string ou will invoke the (imaginary) ou-notify.pl script, passing it the repository followed by the file names and new revision numbers of the files in the commit.

The third line invokes the (equally imaginary) default-notify.pl script for any commit that didn't match either of the two previous lines, passing it all possible information (path to repository, file names, old revisions, and new revisions).


Node: The verifymsg And rcsinfo Files, Next: , Previous: The commitinfo And loginfo And rcsinfo Files, Up: The CVSROOT/ Administrative Directory

The verifymsg And rcsinfo Files

Sometimes you may just want a program to automatically verify that the log message conforms to a certain standard and to stop the commit if that standard is not met. This can be accomplished by using verifymsg, possibly with some help from rcsinfo.

The verifymsg file is the usual combination of regular expressions and programs. The program receives the log message on standard input; presumably it runs some checks to verify that the log message meets certain criteria, then it exits with status zero or nonzero. If the latter, the commit will fail.

Meanwhile, the left side of rcsinfo has the usual regular expressions, but the right side points to template files instead of programs. A template file might be something like this

     Condition:
     Fix:
     Comments:
     

or some other collection of fields that a developer is supposed to fill out to form a valid log message. The template is not very useful if everyone commits using the -m option explicitly, but many developers prefer not to do that. Instead, they run

     floss$ cvs commit
     

and wait for CVS to automatically fire up a text editor (as specified in the EDITOR environment variable). There they write a log message, then save the file and exit the editor, after which CVS continues with the commit.

In that scenario, an rcsinfo template would insert itself into the editor before the user starts typing, so the fields would be displayed along with a reminder to fill them in. Then when the user commits, the appropriate program in verifymsg is invoked. Presumably, it will check that the message does follow that format, and its exit status will reflect the results of its inquiry (with zero meaning success).

As an aid to the verification programs, the path to the template from the rcsinfo file is appended as the last argument to the program command line in verifymsg; that way, the program can base its verification process on the template itself, if desired.

Note that when someone checks out a working copy to a remote machine, the appropriate rcsinfo template file is sent to the client as well (it's stored in the CVS/ subdirectory of the working copy). However, this means that if the rcsinfo file on the server is changed after that, the client won't see the changes without re-checking out the project (merely doing an update won't work).

Note also that in the verifymsg file, the ALL keyword is not supported (although DEFAULT still is). This is to make it easier to override default verification scripts with subdirectory-specific ones.


Node: The taginfo File, Next: , Previous: The verifymsg And rcsinfo Files, Up: The CVSROOT/ Administrative Directory

The taginfo File

What loginfo does for log messages, taginfo does for tags. The left side of taginfo is regular expressions, as usual, and the right side is programs. Each program is automatically handed arguments when CVS tag is invoked, in this order:

     arg 1:          tag name
     arg 2:          operation ("add" => tag, "mov" => tag -F, "del" => tag -d)
     arg 3:          repository
     arg 4, 5, etc:  file revision [file revision ...]
     

If the program returns nonzero, the tag is aborted.

We haven't covered the -F option to tag before now, but it's exactly what the above implies: a way to move a tag from one revision to another. For example, if the tag Known_Working is attached to Revision 1.7 of a file and you want it attached to Revision 1.11 instead, you'd do this

     cvs tag -r 1.11 -F Known_Working foo.c
     

which removes the tag from 1.7, or wherever it was previously in that file, and puts it at 1.11.


Node: The cvswrappers File, Next: , Previous: The taginfo File, Up: The CVSROOT/ Administrative Directory

The cvswrappers File

The redundantly-named cvswrappers file gives you a way to specify that certain files should be treated as binary, based on their file name. CVS does not assume that all .jpg files are JPG image data, for example, so it doesn't automatically use -kb when adding JPG files. Nonetheless, certain projects would find it very useful to simply designate all JPG files as binary. Here is a line in cvswrappers to do that:

     *.jpg -k 'b'
     

The b is separate and in quotes because it's not the only possible RCS keyword expansion mode; one could also specify o, which means not to expand $ sign keywords but to do newline conversion. However, b is the most common parameter.

There are a few other modes that can be specified from the wrappers file, but they're for such rare situations that they're probably not worth documenting here (translation: your author has never had to use them). See the node Wrappers in the Cederqvist if you're curious.


Node: The editinfo File, Next: , Previous: The cvswrappers File, Up: The CVSROOT/ Administrative Directory

The editinfo File

This file is obsolete, even though it's still included in distributions. Just ignore it.


Node: The notify File, Next: , Previous: The editinfo File, Up: The CVSROOT/ Administrative Directory

The notify File

This file is used in conjunction with CVS's watch features, which are described in Advanced CVS. Nothing about it will make sense until you understand what watches are (they're a useful but non-essential feature), so see Advanced CVS for details about this file and about watches.


Node: The checkoutlist File, Previous: The notify File, Up: The CVSROOT/ Administrative Directory

The checkoutlist File

If you look inside CVSROOT/, you'll see that working copies of the files exist side by side with their RCS revision files:

     floss$ ls /usr/local/newrepos/CVSROOT
     checkoutlist     config,v       history     notify     taginfo
     checkoutlist,v   cvswrappers    loginfo     notify,v   taginfo,v
     commitinfo       cvswrappers,v  loginfo,v   passwd     verifymsg
     commitinfo,v     editinfo       modules     rcsinfo    verifymsg,v
     config           editinfo,v     modules,v   rcsinfo,v
     
     floss$
     

CVS only pays attention to the working versions, not the RCS files, when it's looking for guidance on how to behave. Therefore, whenever you commit your working copy of CVSROOT/ (which might, after all, even be checked out to a different machine), CVS automatically updates any changed files in the repository itself. You will know that this has happened because CVS will print a message at the end of such commits:

     floss$ cvs ci -m "added mp and asub modules" modules
     Checking in modules;
     /usr/local/newrepos/CVSROOT/modules,v  <--  modules
     new revision: 1.2; previous revision: 1.1
     done
     cvs commit: Rebuilding administrative file database
     

CVS automatically knows about the standard administrative files, and will rebuild them in CVSROOT/ as necessary. If you decide to put custom files in CVSROOT/ (such as programs or rcsinfo template files), you'll have to tell CVS explicitly to treat them the same way.

That's the purpose of the checkoutlist file. It has a different format from most of the files we've looked at so far

     FILENAME     ERROR_MESSAGE_IF_FILE_CANNOT_BE_CHECKED_OUT
     

for example,

     log.pl           unable to check out / update log.pl in CVSROOT
     
     bugfix.tmpl      unable to check out / update bugfix.tmpl in CVSROOT
     

Certain files in CVSROOT are traditionally not kept under revision control. One such is the history file, which keeps a running record of all actions in the repository, for use by the cvs history command (which lists checkout, update, and tag activity for a given file or project directory). Incidentally, if you just remove the history file, CVS will obligingly stop keeping that log.

Note: sometimes the history file is the cause of permission problems, and the easiest way to solve them is to either make it world-writeable or just remove it.

Another unrevisioned administrative file is passwd, the assumption being that having it checked out over the network might compromise the passwords (even though they're encrypted). You'll have to decide based on your own security situation whether you want to add passwd to checkoutlist or not; by default, it is not in checkoutlist.

Two final notes about the CVSROOT/ directory: It is possible, if you make a big enough mistake, to commit an administrative file that is broken in such a way as to prevent any commits from happening at all. If you do that, naturally you won't be able to commit a fixed version of the administrative file! The solution is to go in and hand-edit the repository's working copy of the administrative file to correct the problem; the whole repository may stay inaccessible until you do that.

Also, for security's sake, make sure your CVSROOT/ directory is only writeable by users you trust (by trust, I mean you trust both their intentions and their ability not to compromise their password). The *info files give people the ability to invoke arbitrary programs, so anyone who can commit or edit files in the CVSROOT/ directory can essentially run any command on the system. That's something you should always keep in mind.


Node: Commit Emails, Next: , Previous: The CVSROOT/ Administrative Directory, Up: Repository Administration

Commit Emails

The loginfo file is how one sets up commit emails - automated emails that go out to everyone working on a project whenever a commit takes place. (It may seem counterintuitive that this is done in loginfo instead of commitinfo, but the point is that one wants to include the log message in the email). The program to do the mailing - contrib/log.pl in the CVS source distribution - can be installed anywhere on your system. I customarily put it in the repository's CVSROOT/ subdirectory, but that's just a matter of taste.

You may need to edit log.pl a bit to get it to work on your system, possibly changing the first line to point to your Perl interpreter, and maybe changing this line

     $mailcmd = "| Mail -s 'CVS update: $modulepath'";
     

to invoke your preferred mailer, which may or may not be named Mail. Once you've got it set the way you like it, you can put lines similar to these into your loginfo:

     listerizer CVSROOT/log.pl %s -f CVSROOT/commitlog -m [email protected]
     RoadMail   CVSROOT/log.pl %s -f CVSROOT/commitlog -m [email protected]
     bk/*score  CVSROOT/log.pl %s -f CVSROOT/commitlog -m \
                                             [email protected]
     

The %s expands to the names of the files being committed; the -f option to log.pl takes a file name, to which the log message will be appended (so CVSROOT/commitlog is an ever-growing file of log messages); and the -m flag takes an email address, to which log.pl will send a message about the commit. The address is usually a mailing list, but you can specify the -m option as many times as necessary in one log.pl command line.


Node: Finding Out More, Previous: Commit Emails, Up: Repository Administration

Finding Out More

Although this chapter tries to give a complete introduction to installing and administering CVS, I've left out things that are either too rarely used to be worth mentioning or already well documented in the Cederqvist manual. The latter category includes setting up the other remote access methods: RSH/SSH, kserver (Kerberos 4), and GSSAPI (which includes Kerberos 5, among other things). It should be noted that nothing special needs to be done for RSH/SSH connections, other than making sure that the user in question can log into the repository machine using RSH or SSH. If they can and CVS is installed on both client and server, and they have the right permissions to use the repository directly from the server machine, then they should be able to access the repository remotely via the :ext: method.

Descriptions of certain specialized features of CVS have been deferred to later chapters, so they can be introduced in contexts where their usefulness is obvious. General CVS troubleshooting tips are found in Tips And Troubleshooting. Although it's not necessary to read the entire Cederqvist manual, you should familiarize yourself with it; it will be an invaluable reference tool. If for some reason you don't have Info working on your machine and don't want to print the manual, you can browse it online at http://durak.org/cvswebsites/doc/ or http://www.loria.fr/~molli/cvs/doc/cvs_toc.html.


Node: Advanced CVS, Next: , Previous: Repository Administration, Up: Top

Advanced CVS

Now that we've covered the basic concepts of CVS usage and repository administration, we'll look at how CVS can be incorporated into the entire process of development. The fundamental CVS working cycle - checkout, update, commit, update, commit, and so on - was demonstrated by the examples in An Overview of CVS. This chapter elaborates on the cycle and discusses how CVS can be used to help developers communicate, give overviews of project activity and history, isolate and reunite different branches of development, and automate frequently performed tasks. Some of the techniques covered introduce new CVS commands, but many merely explain better ways to use commands that you already know.


Node: Watches (CVS As Telephone), Next: , Up: Advanced CVS

Watches (CVS As Telephone)

A major benefit of using CVS on a project is that it can function as a communications device as well as a record-keeper. This section concentrates on how CVS can be used to keep participants informed about what's going on in a project. As is true with other aspects of CVS, these features reward cooperation. The participants must want to be informed; if people choose not to use the communications features, there's nothing CVS can do about it.


Node: How Watches Work, Next: , Up: Watches (CVS As Telephone)

How Watches Work

In its default behavior, CVS treats each working copy as an isolated sandbox. No one knows what you're doing in your working copy until you commit your changes. In turn, you don't know what others are doing in theirs - except via the usual methods of communication, such as shouting down the hallway, "Hey, I'm going to work on parse.c now. Let me know if you're editing it so we can avoid conflicts!"

This informality works for projects where people have a general idea of who's responsible for what. However, this process can break down when a large number of developers are active in all parts of a code base and want to avoid conflicts. In such cases, they frequently have to cross each others' areas of responsibility but can't shout down the hallway at each other because they're geographically distributed.

A feature of CVS called watches provides developers with a way to notify each other about who is working on what files at a given time. By "setting a watch" on a file, a developer can have CVS notify her if anyone else starts to work on that file. The notifications are normally sent via email, although it is possible to set up other notification methods.

To use watches, you must modify one or two files in the repository administrative area, and developers must add some extra steps to the usual checkout/update/commit cycle. The changes on the repository side are fairly simple: You may need to edit the CVSROOT/notify file so that CVS knows how notifications are to be performed. You may also have to add lines to the CVSROOT/users file, which supplies external email addresses.

On the working copy side, developers have to tell CVS which files they want to watch so that CVS can send them notifications when someone else starts editing those files. They also need to tell CVS when they start or stop editing a file, so CVS can send out notifications to others who may be watching. The following commands are used to implement these extra steps:

The command watch differs from the usual CVS command pattern in that it requires further subcommands, such as cvs watch add..., cvs watch remove..., and so on.

In the following example, we'll look at how to turn on watches in the repository and then how to use watches from the developer's side. The two example users, jrandom and qsmith, each have their own separate working copies of the same project; the working copies may even be on different machines. As usual, all examples assume that the $CVSROOT environment variable has already been set, so there's no need to pass -d <REPOS> to any CVS commands.


Node: Enabling Watches In The Repository, Next: , Previous: How Watches Work, Up: Watches (CVS As Telephone)

Enabling Watches In The Repository

First, the CVSROOT/notify file must be edited to turn on email notification. One of the developers can do this, or the repository administrator can if the developers don't have permission to change the repository's administrative files. In any case, the first thing to do is check out the administrative area and edit the notify file:

     floss$ cvs -q co CVSROOT
     U CVSROOT/checkoutlist
     U CVSROOT/commitinfo
     U CVSROOT/config
     U CVSROOT/cvswrappers
     U CVSROOT/editinfo
     U CVSROOT/loginfo
     U CVSROOT/modules
     U CVSROOT/notify
     U CVSROOT/rcsinfo
     U CVSROOT/taginfo
     U CVSROOT/verifymsg
     floss$ cd CVSROOT
     floss$ emacs notify
     ...
     

When you edit the notify file for the first time, you'll see something like this:

     # The "notify" file controls where notifications from watches set by
     # "cvs watch add" or "cvs edit" are sent. The first entry on a line is
     # a regular expression which is tested against the directory that the
     # change is being made to, relative to the $CVSROOT. If it matches,
     # then the remainder of the line is a filter program that should contain
     # one occurrence of %s for the user to notify, and information on its
     # standard input.
     #
     # "ALL" or "DEFAULT" can be used in place of the regular expression.
     #
     # For example:
     # ALL mail %s -s "CVS notification"
     

All you really need to do is uncomment the last line by removing the initial # mark. Although the notify file provides the same flexible interface as the other administrative files, with regular expressions matching against directory names, the truth is that you almost never want to use any of that flexibility. The only reason to have multiple lines, with each line's regular expression matching a particular part of the repository, would be if you wanted to use a different notification method for each project. However, normal email is a perfectly good notification mechanism, so most projects just use that.

To specify email notification, the line

     ALL mail %s -s "CVS notification"
     

should work on any standard Unix machine. This command causes notifications to be sent as emails with the subject line CVS notification (the special expression ALL matches any directory, as usual). Having uncommented that line, commit the notify file so the repository is aware of the change:

     floss$ cvs ci -m "turned on watch notification"
     cvs commit: Examining .
     Checking in notify;
     /usr/local/newrepos/CVSROOT/notify,v  <--  notify
     new revision: 1.2; previous revision: 1.1
     done
     cvs commit: Rebuilding administrative file database
     floss$
     

Editing the notify file in this way may be all that you'll need to do for watches in the repository. However, if there are remote developers working on the project, you may need to edit the CVSROOT/users file, too. The purpose of the users file is to tell CVS where to send email notifications for those users who have external email addresses. The format of each line in the users file is:

     CVS_USERNAME:EMAIL_ADDRESS
     

For example,

     qsmith:[email protected]
     

The CVS username at the beginning of the line corresponds to a CVS username in CVSROOT/password (if present and the pserver access method is being used), or failing that, the server-side system username of the person running CVS. Following the colon is an external email address to which CVS should send watch notifications for that user.

Unfortunately, as of this writing, the users file does not exist in the stock CVS distribution. Because it's an administrative file, you must not only create, cvs add, and commit it in the usual way, but also add it to CVSROOT/checkoutlist so that a checked-out copy is always maintained in the repository.

Here is a sample session demonstrating this:

     floss$ emacs checkoutlist
       ... (add the line for the users file) ...
     floss$ emacs users
       ... (add the line for qsmith) ...
     floss$ cvs add users
     floss$ cvs ci -m "added users to checkoutlist, qsmith to users"
     cvs commit: Examining .
     Checking in checkoutlist;
     /usr/local/newrepos/CVSROOT/checkoutlist,v  <--  checkoutlist
     new revision: 1.2; previous revision: 1.1
     done
     Checking in users;
     /usr/local/newrepos/CVSROOT/users,v  <--  users
     new revision: 1.2; previous revision: 1.1
     done
     cvs commit: Rebuilding administrative file database
     floss$
     

It's possible to use expanded-format email addresses in CVSROOT/users, but you have to be careful to encapsulate all whitespace within quotes. For example, the following will work

     qsmith:"Quentin Q. Smith <[email protected]>"
     

or

     qsmith:'Quentin Q. Smith <[email protected]>'
     

However, this will not work:

     qsmith:"Quentin Q. Smith" <[email protected]>
     

When in doubt, you should test by running the command line given in the notify file manually. Just replace the %s in

     mail %s -s "CVS notification"
     

with what you have following the colon in users. If it works when you run it at a command prompt, it should work in the users file, too.

When it's over, the checkout file will look like this:

     # The "checkoutlist" file is used to support additional version controlled
     # administrative files in $CVSROOT/CVSROOT, such as template files.
     #
     # The first entry on a line is a filename which will be checked out from
     # the corresponding RCS file in the $CVSROOT/CVSROOT directory.
     # The remainder of the line is an error message to use if the file cannot
     # be checked out.
     #
     # File format:
     #
     #       [<whitespace>]<filename><whitespace><error message><end-of-line>
     #
     # comment lines begin with '#'
     
     users   Unable to check out 'users' file in CVSROOT.
     

The users file will look like this:

     qsmith:[email protected]
     

Now that the repository is set up for watches, let's look at what developers need to do in their working copies.


Node: Using Watches In Development, Next: , Previous: Enabling Watches In The Repository, Up: Watches (CVS As Telephone)

Using Watches In Development

First, a developer checks out a working copy and adds herself to the list of watchers for one of the files in the project:

     floss$ whoami
     jrandom
     floss$ cvs -q co myproj
     U myproj/README.txt
     U myproj/foo.gif
     U myproj/hello.c
     U myproj/a-subdir/whatever.c
     U myproj/a-subdir/subsubdir/fish.c
     U myproj/b-subdir/random.c
     floss$ cd myproj
     floss$ cvs watch add hello.c
     floss$
     

The last command, cvs watch add hello.c, tells CVS to notify jrandom if anyone else starts working on hello.c (that is, it adds jrandom to hello.c's watch list). For CVS to send notifications as soon as a file is being edited, the user who is editing it has to announce the fact by running cvs edit on the file first. CVS has no other way of knowing when someone starts working on a file. Once checkout is done, CVS isn't usually invoked until the next update or commit, which happens after the file has already been edited:

     paste$ whoami
     qsmith
     paste$ cvs -q co myproj
     U myproj/README.txt
     U myproj/foo.gif
     U myproj/hello.c
     U myproj/a-subdir/whatever.c
     U myproj/a-subdir/subsubdir/fish.c
     U myproj/b-subdir/random.c
     paste$ cd myproj
     paste$ cvs edit hello.c
     paste$ emacs hello.c
     ...
     

When qsmith runs cvs edit hello.c, CVS looks at the watch list for hello.c, sees that jrandom is on it, and sends email to jrandom telling her that qsmith has started editing the file. The email even appears to come from qsmith:

     From: qsmith
     Subject: CVS notification
     To: jrandom
     Date: Sat, 17 Jul 1999 22:14:43 -0500
     
     myproj hello.c
     --
     Triggered edit watch on /usr/local/newrepos/myproj
     By qsmith
     
     Furthermore, every time that qsmith (or anyone) commits a new revision of hello.c, jrandom will receive another email:
     
     myproj hello.c
     --
     Triggered commit watch on /usr/local/newrepos/myproj
     By qsmith
     

After receiving these emails, jrandom may want to update hello.c immediately to see what qsmith has done, or perhaps she'll email qsmith to find out why he's working on that file. Note that nothing forced qsmith to remember to run cvs edit - presumably he did it because he wanted jrandom to know what he was up to (anyway, even if he forgot to do cvs edit, his commits would still trigger notifications). The reason to use cvs edit is that it notifies watchers before you start to work on a file. The watchers can contact you if they think there may be a conflict, before you've wasted a lot of time.

CVS assumes that anyone who runs cvs edit on a file wants to be added to the file's watch list, at least temporarily, in case someone else starts to edit it. When qsmith ran cvs edit, he became a watcher of hello.c. Both he and jrandom would have received notification if a third party had run cvs edit on that file (or committed it).

However, CVS also assumes that the person editing the file only wants to be on its watch list while he or she is editing it. Such users are taken off the watch list when they're done editing. If they prefer to be permanent watchers of the file, they would have to run cvs watch add. CVS makes a default assumption that someone is done editing when he or she commits a file (until the next time, anyway).

Anyone who gets on a file's watch list solely by virtue of having run cvs edit on that file is known as a temporary watcher and is taken off the watch list as soon as she commits a change to the file. If she wants to edit it again, she has to rerun cvs edit.

CVS's assumption that the first commit ends the editing session is only a best guess, of course, because CVS doesn't know how many commits the person will need to finish their changes. The guess is probably accurate for one-off changes - changes where someone just needs to make one quick fix to a file and commit it. For more prolonged editing sessions involving several commits, users should add themselves permanently to the file's watch list:

     paste$ cvs watch add hello.c
     paste$ cvs edit hello.c
     paste$ emacs hello.c
     ...
     paste$ cvs commit -m "print hello in Sanskrit"
     

Even after the commit, qsmith remains a watcher of hello.c because he ran watch add on it. (By the way, qsmith will not receive notification of his own edits; only other watchers will. CVS is smart enough not to notify you about actions that you took.)


Node: Ending An Editing Session, Next: , Previous: Using Watches In Development, Up: Watches (CVS As Telephone)

Ending An Editing Session

If you don't want to commit but want to explicitly end an editing session, you can do so by running cvs unedit:

     paste$ cvs unedit hello.c
     

But beware! This does more than just notify all watchers that you're done editing - it also offers to revert any uncommitted changes that you've made to the file:

     paste$ cvs unedit hello.c
     hello.c has been modified; revert changes? y
     paste$
     

If you answer y, CVS undoes all your changes and notifies watchers that you're not editing the file anymore. If you answer n, CVS keeps your changes and also keeps you registered as an editor of the file (so no notification goes out - in fact, it's as if you never ran cvs unedit at all). The possibility of CVS undoing all of your changes at a single keystroke is a bit scary, but the rationale is easy to understand: If you declare to the world that you're ending an editing session, then any changes you haven't committed are probably changes you don't mean to keep. At least, that's the way CVS sees it. Needless to say, be careful!


Node: Controlling What Actions Are Watched, Next: , Previous: Ending An Editing Session, Up: Watches (CVS As Telephone)

Controlling What Actions Are Watched

By default, watchers are notified about three kinds of action: edits, commits, and unedits. However, if you only want to be notified about, say, commits, you can restrict notifications by adjusting your watch with the -a flag (a for action):

     floss$ cvs watch add -a commit hello.c
     

Or if you want to watch edits and commits but don't care about unedits, you could pass the -a flag twice:

     floss$ cvs watch add -a edit -a commit hello.c
     

Adding a watch with the -a flag will never cause any of your existing watches to be removed. If you were watching for all three kinds of actions on hello.c, running

     floss$ cvs watch add -a commit hello.c
     

has no effect - you'll still be a watcher for all three actions. To remove watches, you should run

     floss$ cvs watch remove hello.c
     

which is similar to add in that, by default, it removes your watches for all three actions. If you pass -a arguments, it removes only the watches you specify:

     floss$ cvs watch remove -a commit hello.c
     

This means that you want to stop receiving notifications about commits but continue to receive notifications about edits and unedits (assuming you were watching edits and unedits to begin with, that is).

There are two special actions you can pass to the -a flag: all or none. The former means all actions that are eligible for watching (edits, commits, and unedits, as of this writing), and the latter means none of these. Because CVS's default behavior, in the absence of -a, is to watch all actions, and because watching none is the same as removing yourself from the watch list entirely, it's hard to imagine a situation in which it would be useful to specify either of these two special actions. However, cvs edit also takes the -a option, and in this case, it can be useful to specify all or none. For example, someone working on a file very briefly may not want to receive any notifications about what other people do with the file. Thus, this command

     paste$ whoami
     qsmith
     paste$ cvs edit -a none README.txt
     

causes watchers of README.txt to be notified that qsmith is about to work on it, but qsmith would not be added as a temporary watcher of README.txt during his editing session (which he normally would have been), because he asked not to watch any actions.

Remember that you can only affect your own watches with the cvs watch command. You may stop watching a certain file yourself, but that won't change anyone else's watches.


Node: Finding Out Who Is Watching What, Next: , Previous: Controlling What Actions Are Watched, Up: Watches (CVS As Telephone)

Finding Out Who Is Watching What

Sometimes you may want to know who's watching before you even run cvs edit or want to see who is editing what without adding yourself to any watch lists. Or you may have forgotten exactly what your own status is. After setting and unsetting a few watches and committing some files, it's easy to lose track of what you're watching and editing.

CVS provides two commands to show who's watching and who's editing files - cvs watchers and cvs editors:

     floss$ whoami
     jrandom
     floss$ cvs watch add hello.c
     floss$ cvs watchers hello.c
     hello.c jrandom  edit unedit  commit
     floss$ cvs watch remove -a unedit hello.c
     floss$ cvs watchers hello.c
     hello.c jrandom  edit commit
     floss$ cvs watch add README.txt
     floss$ cvs watchers
     README.txt      jrandom edit    unedit  commit
     hello.c jrandom edit    commit
     floss$
     

Notice that the last cvs watchers command doesn't specify any files and, therefore, shows watchers for all files (all those that have watchers, that is).

All of the watch and edit commands have this behavior in common with other CVS commands. If you specify file names, they act on those files. If you specify directory names, they act on everything in that directory and its subdirectories. If you don't specify anything, they act on the current directory and everything underneath it, to as many levels of depth as are available. For example (continuing with the same session):

     floss$ cvs watch add a-subdir/whatever.c
     floss$ cvs watchers
     README.txt      jrandom edit    unedit  commit
     hello.c jrandom edit    commit
     a-subdir/whatever.c     jrandom edit    unedit  commit
     floss$ cvs watch add
     floss$ cvs watchers
     README.txt      jrandom edit    unedit  commit
     foo.gif jrandom edit    unedit  commit
     hello.c jrandom edit    commit  unedit
     a-subdir/whatever.c     jrandom edit    unedit  commit
     a-subdir/subsubdir/fish.c       jrandom edit    unedit  commit
     b-subdir/random.c       jrandom edit    unedit  commit
     floss$
     

The last two commands made jrandom a watcher of every file in the project and then showed the watch list for every file in the project, respectively. The output of cvs watchers doesn't always line up perfectly in columns because it mixes tab stops with information of varying length, but the lines are consistently formatted:

     [FILENAME] [whitespace] WATCHER [whitespace] ACTIONS-BEING-WATCHED...
     

Now watch what happens when qsmith starts to edit one of the files:

     paste$ cvs edit hello.c
     paste$ cvs watchers
     README.txt      jrandom edit    unedit  commit
     foo.gif jrandom edit    unedit  commit
     hello.c jrandom edit    commit  unedit
            qsmith  tedit   tunedit tcommit
     a-subdir/whatever.c     jrandom edit    unedit  commit
     a-subdir/subsubdir/fish.c       jrandom edit    unedit  commit
     b-subdir/random.c       jrandom edit    unedit  commit
     

The file hello.c has acquired another watcher: qsmith himself (note that the file name is not repeated but is left as white space at the beginning of the line - this would be important if you ever wanted to write a program that parses watchers output). Because he's editing hello.c, qsmith has a temporary watch on the file; it goes away as soon as he commits a new revision of hello.c. The prefix t in front of each of the actions indicates that these are temporary watches. If qsmith adds himself as a regular watcher of hello.c as well

     paste$ cvs watch add hello.c
     README.txt      jrandom edit    unedit  commit
     foo.gif jrandom edit    unedit  commit
     hello.c jrandom edit    commit  unedit
            qsmith  tedit   tunedit tcommit edit    unedit  commit
     a-subdir/whatever.c     jrandom edit    unedit  commit
     a-subdir/subsubdir/fish.c       jrandom edit    unedit  commit
     b-subdir/random.c       jrandom edit    unedit  commit
     

he is listed as both a temporary watcher and a permanent watcher. You may think that the permanent watch status would simply override the temporary, so that the line would look like this:

             qsmith  edit    unedit  commit
     

However, CVS can't just replace the temporary watches because it doesn't know in what order things happen. Will qsmith remove himself from the permanent watch list before ending his editing session, or will he finish the edits while still remaining a watcher? If the former, the edit/unedit/commit actions disappear while the tedit/tunedit/tcommit ones remain; if the latter, the reverse would happen.

Anyway, that side of the watch list is usually not of great concern. Most of the time, what you want to do is run

     floss$ cvs watchers
     

or

     floss$ cvs editors
     

from the top level of a project and see who's doing what. You don't really need to know the details of who cares about what actions: the important things are people and files.


Node: Reminding People To Use Watches, Next: , Previous: Finding Out Who Is Watching What, Up: Watches (CVS As Telephone)

Reminding People To Use Watches

You've probably noticed that the watch features are utterly dependent on the cooperation of all the developers. If someone just starts editing a file without first running cvs edit, no one else will know about it until the changes get committed. Because cvs edit is an additional step, not part of the normal development routine, people can easily forget to do it.

Although CVS can't force someone to use cvs edit, it does have a mechanism for reminding people to do so - the watch on command:

     floss$ cvs -q co myproj
     U myproj/README.txt
     U myproj/foo.gif
     U myproj/hello.c
     U myproj/a-subdir/whatever.c
     U myproj/a-subdir/subsubdir/fish.c
     U myproj/b-subdir/random.c
     floss$ cd myproj
     floss$ cvs watch on hello.c
     floss$
     

By running cvs watch on hello.c, jrandom causes future checkouts of myproj to create hello.c read-only in the working copy. When qsmith tries to work on it, he'll discover that it's read-only and be reminded to run cvs edit first:

     paste$ cvs -q co myproj
     U myproj/README.txt
     U myproj/foo.gif
     U myproj/hello.c
     U myproj/a-subdir/whatever.c
     U myproj/a-subdir/subsubdir/fish.c
     U myproj/b-subdir/random.c
     paste$ cd myproj
     paste$ ls -l
     total 6
     drwxr-xr-x   2 qsmith    users        1024 Jul 19 01:06 CVS/
     -rw-r--r--   1 qsmith    users          38 Jul 12 11:28 README.txt
     drwxr-xr-x   4 qsmith    users        1024 Jul 19 01:06 a-subdir/
     drwxr-xr-x   3 qsmith    users        1024 Jul 19 01:06 b-subdir/
     -rw-r--r--   1 qsmith    users         673 Jun 20 22:47 foo.gif
     -r--r--r--   1 qsmith    users         188 Jul 18 01:20 hello.c
     paste$
     

When he does so, the file becomes read-write. He can then edit it, and when he commits, it becomes read-only again:

     paste$ cvs edit hello.c
     paste$ ls -l hello.c
     -rw-r--r--   1 qsmith    users         188 Jul 18 01:20 hello.c
     paste$ emacs hello.c
       ...
     paste$ cvs commit -m "say hello in Aramaic" hello.c
     Checking in hello.c;
     /usr/local/newrepos/myproj/hello.c,v  <--  hello.c
     new revision: 1.12; previous revision: 1.11
     done
     paste$ ls -l hello.c
     -r--r--r--   1 qsmith    users         210 Jul 19 01:12 hello.c
     paste$
     

His edit and commit will send notification to all watchers of hello.c. Note that jrandom isn't necessarily one of them. By running cvs watch on hello.c, jrandom did not add herself to the watch list for that file; she merely specified that it should be checked out read-only. People who want to watch a file must remember to add themselves to its watch list - CVS cannot help them with that.

Turning on watches for a single file may be the exception. Generally, it's more common to turn on watches project-wide:

     floss$ cvs -q co myproj
     U myproj/README.txt
     U myproj/foo.gif
     U myproj/hello.c
     U myproj/a-subdir/whatever.c
     U myproj/a-subdir/subsubdir/fish.c
     U myproj/b-subdir/random.c
     floss$ cd myproj
     floss$ cvs watch on
     floss$
     

This action amounts to announcing a policy decision for the entire project: "Please use cvs edit to tell watchers what you're working on, and feel free to watch any file you're interested in or responsible for." Every file in the project will be checked out read-only, and thus people will be reminded that they're expected to use cvs edit before working on anything.

Curiously, although checkouts of watched files make them read-only, updates do not. If qsmith had checked out his working copy before jrandom ran cvs watch on, his files would have stayed read-write, remaining so even after updates. However, any file he commits after jrandom turns watching on will become read-only. If jrandom turns off watches

     floss$ cvs watch off
     

qsmith's read-only files do not magically become read-write. On the other hand, after he commits one, it will not revert to read-only again (as it would have if watches were still on).

It's worth noting that qsmith could, were he truly devious, make files in his working copy writeable by using the standard Unix chmod command, bypassing cvs edit entirely

     paste$ chmod u+w hello.c
     

or if he wanted to get everything in one fell swoop:

     paste$ chmod -R u+w .
     

There is nothing CVS can do about this. Working copies by their nature are private sandboxes - the watch features can open them up to public scrutiny a little bit, but only as far as the developer permits. Only when a developer does something that affects the repository (such as commits) is her privacy unconditionally lost.

The relationship among watch add, watch remove, watch on, and watch off probably seems a bit confusing. It may help to summarize the overall scheme: add and remove are about adding or removing users from a file's watch list; they don't have anything to do with whether files are read-only on checkout or after commits. on and off are only about file permissions. They don't have anything to do with who is on a file's watch list; rather, they are tools to help remind developers of the watch policy by causing working-copy files to become read-only.

All of this may seem a little inconsistent. In a sense, using watches works against the grain of CVS. It deviates from the idealized universe of multiple developers editing freely in their working copies, hidden from each other until they choose to commit. With watches, CVS gives developers convenient shortcuts for informing each other of what's going on in their working copies; however, it has no way to enforce observation policies, nor does it have a definitive concept of what constitutes an editing session. Nevertheless, watches can be helpful in certain circumstances if developers work with them.


Node: What Watches Look Like In The Repository, Previous: Reminding People To Use Watches, Up: Watches (CVS As Telephone)

What Watches Look Like In The Repository

In the interests of stamping out black boxes and needless mystery, let's take a quick look at how watches are implemented in the repository. We'll only take a quick look, though, because it's not pretty.

When you set a watch

     floss$ pwd
     /home/jrandom/myproj
     floss$ cvs watch add hello.c
     floss$ cvs watchers
     hello.c jrandom edit    unedit  commit
     floss$
     

CVS records it in the special file, CVS/fileattr, in the appropriate repository subdirectory:

     floss$ cd /usr/local/newrepos
     floss$ ls
     CVSROOT/   myproj/
     floss$ cd myproj
     floss$ ls
     CVS/          a-subdir/     foo.gif,v
     README.txt,v  b-subdir/     hello.c,v
     floss$ cd CVS
     floss$ ls
     fileattr
     floss$ cat fileattr
     Fhello.c        _watchers=jrandom>edit+unedit+commit
     floss$
     

The fact that fileattr is stored in a CVS subdirectory in the repository does not mean that the repository has become a working copy. It's simply that the name CVS was already reserved for bookkeeping in the working copy, so CVS can be sure no project will ever need a subdirectory of that name in the repository.

I won't describe the format of fileattr formally; you can probably grok it pretty well just by watching it change from command to command:

     floss$ cvs watch add hello.c
     floss$ cat /usr/local/newrepos/myproj/CVS/fileattr
     Fhello.c        _watchers=jrandom>edit+unedit+commit
     floss$ cvs watch add README.txt
     floss$ cat /usr/local/newrepos/myproj/CVS/fileattr
     Fhello.c        _watchers=jrandom>edit+unedit+commit
     FREADME.txt     _watchers=jrandom>edit+unedit+commit
     floss$ cvs watch on hello.c
     floss$ cat /usr/local/newrepos/myproj/CVS/fileattr
     Fhello.c        _watchers=jrandom>edit+unedit+commit;_watched=
     FREADME.txt     _watchers=jrandom>edit+unedit+commit
     floss$ cvs watch remove hello.c
     floss$ cat /usr/local/newrepos/myproj/CVS/fileattr
     Fhello.c        _watched=
     FREADME.txt     _watchers=jrandom>edit+unedit+commit
     floss$ cvs watch off hello.c
     floss$ cat /usr/local/newrepos/myproj/CVS/fileattr
     FREADME.txt     _watchers=jrandom>edit+unedit+commit
     floss$
     

Edit records are stored in fileattr, too. Here's what happens when qsmith adds himself as an editor:

     paste$ cvs edit hello.c
     
     floss$ cat /usr/local/newrepos/myproj/CVS/fileattr
     Fhello.c        _watched=;_editors=qsmith>Tue Jul 20 04:53:23 1999 GMT+floss\
     +/home/qsmith/myproj;_watchers=qsmith>tedit+tunedit+tcommit
     FREADME.txt     _watchers=jrandom>edit+unedit+commit
     

Finally, note that CVS removes fileattr and the CVS subdirectory when there are no more watchers or editors for any of the files in that directory:

     paste$ cvs unedit
     
     floss$ cvs watch off
     floss$ cvs watch remove
     floss$ cat /usr/local/newrepos/myproj/CVS/fileattr
     cat: /usr/local/newrepos/myproj/CVS/fileattr: No such file or directory
     floss$
     

It should be clear after this brief exposure that the details of parsing fileattr format are better left to CVS. The main reason to have a basic understanding of the format - aside from the inherent satisfaction of knowing what's going on behind the curtain - is if you try to write an extension to the CVS watch features or debug some problem in them. It's sufficient to know that you shouldn't be alarmed if you see CVS/ subdirectories popping up in your repository. They're the only safe place CVS has to store meta-information such as watch lists.


Node: Log Messages And Commit Emails, Next: , Previous: Watches (CVS As Telephone), Up: Advanced CVS

Log Messages And Commit Emails

Commit emails are notices sent out at commit time, showing the log message and files involved in the commit. They usually go to all project participants and sometimes to other interested parties. The details of setting up commit emails were covered in Repository Administration, so I won't repeat them here. I have noticed, however, that commit emails can sometimes result in unexpected side effects to projects, effects that you may want to take into account if you set up commit emails for your project.

First, be prepared for the messages to be mostly ignored. Whether people read them depends, at least partly, on the frequency of commits in your project. Do developers tend to commit one big change at the end of the day, or many small changes throughout the day? The closer your project is to the latter, the thicker the barrage of tiny commit notices raining down on the developers all day long, and the less inclined they will be to pay attention to each message.

This doesn't mean the notices aren't useful, just that you shouldn't count on every person reading every message. It's still a convenient way for people to keep tabs on who's doing what (without the intrusiveness of watches). When the emails go to a publicly subscribable mailing list, they are a wonderful mechanism for giving interested users (and future developers!) a chance to see what happens in the code on a daily basis.

You may want to consider having a designated developer who watches all log messages and has an overview of activity across the entire project (of course, a good project leader will probably be doing this anyway). If there are clear divisions of responsibility - say, certain developers are "in charge of" certain subdirectories of the project - you could do some fancy scripting in CVSROOT/loginfo to see that each responsible party receives specially marked notices of changes made in their area. This will help ensure that the developers will at least read the email that pertains to their subdirectories.

A more interesting side effect happens when commit emails aren't ignored. People start to use them as a realtime communications method. Here's the kind of log message that can result:

     Finished feedback form; fixed the fonts and background colors on the
     home page.  Whew!  Anyone want to go to Mon Lung for lunch?
     

There's nothing wrong with this, and it makes the logs more fun to read over later. However, people need to be aware that log messages, such as the following, are not only distributed by email but is also preserved forever in the project's history. For example, griping about customer specifications is a frequent pastime among programmers; it's not hard to imagine someone committing a log message like this one, knowing that the other programmers will soon see it in their email:

     Truncate four-digit years to two-digits in input.  What the customer
     wants, the customer gets, no matter how silly & wrong.  Sigh.
     

This makes for an amusing email, but what happens if the customer reviews the logs someday? (I'll bet similar concerns have led more than one site to set up CVSROOT/loginfo so that it invokes scripts to guard against offensive words in log messages!)

The overall effect of commit emails seems to be that people become less willing to write short or obscure log messages, which is probably a good thing. However, they may need to be reminded that their audience is anyone who might ever read the logs, not just the people receiving commit emails.


Node: Changing A Log Message After Commit, Next: , Previous: Log Messages And Commit Emails, Up: Advanced CVS

Changing A Log Message After Commit

Just in case someone does commit a regrettable log message, CVS enables you to rewrite logs after they've been committed. It's done with the -m option to the admin command (this command is covered in more detail later in this chapter) and allows you to change one log message (per revision, per file) at a time. Here's how it works:

     floss$ cvs admin -m 1.7:"Truncate four-digit years to two in input." date.c
     RCS file: /usr/local/newrepos/someproj/date.c,v
     done
     floss$
     

The original, offensive log message that was committed with revision 1.7 has been replaced with a perfectly innocent - albeit duller - message. Don't forget the colon separating the revision number from the new log message.

If the new log message consists of multiple lines, put it in a file and do this:

     floss$ cvs admin -m 1.7:"`cat new-log-message.txt`" date.c
     

(This example was sent in by Peter Ross <[email protected]>; note that it only works for Unix users.)

If the bad message was committed into multiple files, you'll have to run cvs admin separately for each one, because the revision number is different for each file. Therefore, this is one of the few commands in CVS that requires you to pass a single file name as argument:

     floss$ cvs admin -m 1.2:"very boring log message" hello.c README.txt foo.gif
     cvs admin: while processing more than one file:
     cvs [admin aborted]: attempt to specify a numeric revision
     floss$
     

Confusingly, you get the same error if you pass no file names (because CVS then assumes all the files in the current directory and below are implied arguments):

     floss$ cvs admin -m 1.2:"very boring log message"
     cvs admin: while processing more than one file:
     cvs [admin aborted]: attempt to specify a numeric revision
     floss$
     

(As is unfortunately often the case with CVS error messages, you have to see things from CVS's point of view before the message makes sense!)

Invoking admin -m actually changes the project's history, so use it with care. There will be no record that the log message was ever changed - it will simply appear as if that revision had been originally committed with the new log message. No trace of the old message will be left anywhere (unless you saved the original commit email).

Although its name might seem to imply that only the designated CVS administrator can use it, in fact anyone can run cvs admin, as long as they have write access to the project in question. Nevertheless, it is best used with caution; the ability to change log messages is mild compared with other potentially damaging things it can do. See CVS Reference for more about admin, as well as a way to restrict its use.


Node: Getting Rid Of A Working Copy, Next: , Previous: Changing A Log Message After Commit, Up: Advanced CVS

Getting Rid Of A Working Copy

In typical CVS usage, the way to get rid of a working copy directory tree is to remove it like any other directory tree:

     paste$ rm -rf myproj
     

However, if you eliminate your working copy this way, other developers will not know that you have stopped using it. CVS provides a command to relinquish a working copy explicitly. Think of release as the opposite of checkout - you're telling the repository that you're done with the working copy now. Like checkout, release is invoked from the parent directory of the tree:

     paste$ pwd
     /home/qsmith/myproj
     paste$ cd ..
     paste$ ls
     myproj
     paste$ cvs release myproj
     You have [0] altered files in this repository.
     Are you sure you want to release directory 'myproj': y
     paste$
     

If there are any uncommitted changes in the repository, the release fails, meaning that it just lists the modified files and otherwise has no effect. Assuming the tree is clean (totally up to date), release records in the repository that the working copy has been released.

You can also have release automatically delete the working tree for you, by passing the -d flag:

     paste$ ls
     myproj
     paste$ cvs release -d myproj
     You have [0] altered files in this repository.
     Are you sure you want to release (and delete) directory 'myproj: y
     paste$ ls
     paste$
     

As of CVS version 1.10.6, the release command is not able to deduce the repository's location by examining the working copy (this is because release is invoked from above the working copy, not within it). You must pass the -d <REPOS> global option or make sure that your CVSROOT environment variable is set correctly. (This bug may be fixed in future versions of CVS.)

The Cederqvist claims that if you use release instead of just deleting the working tree, people with watches set on the released files will be notified just as if you had run unedit. However, I tried to verify this experimentally, and it does not seem to be true.


Node: History -- A Summary Of Repository Activity, Next: , Previous: Getting Rid Of A Working Copy, Up: Advanced CVS

History - A Summary Of Repository Activity

In Repository Administration, I briefly mentioned the cvs history command. This command displays a summary of all checkouts, commits, updates, rtags, and releases done in the repository (at least, since logging was enabled by the creation of the CVSROOT/history file in the repository). You can control the format and contents of the summary with various options.

The first step is to make sure that logging is enabled in your repository. The repository administrator should first make sure there is a history file

     floss$ cd /usr/local/newrepos/CVSROOT
     floss$ ls -l history
     ls: history: No such file or directory
     floss$
     

and if there isn't one, create it, as follows:

     floss$ touch history
     floss$ ls -l history
     -rw-r--r--   1 jrandom   cvs           0 Jul 22 14:57 history
     floss$
     

This history file also needs to be writeable by everyone who uses the repository, otherwise they'll get an error every time they try to run a CVS command that modifies that file. The easiest way is simply to make the file world-writeable:

     floss$ chmod a+rw history
     floss$ ls -l history
     -rw-rw-rw-   1 jrandom   cvs           0 Jul 22 14:57 history
     floss$
     

If the repository was created with the cvs init command, the history file already exists. You may still have to fix its permissions, however.

The rest of these examples assume that history logging has been enabled for a while, so that data has had time to accumulate in the history file.

The output of cvs history is somewhat terse (it's probably intended to be parsed by programs rather than humans, although it is readable with a little study). Let's run it once and see what we get:

     paste$ pwd
     /home/qsmith/myproj
     paste$ cvs history -e -a
     O 07/25 15:14 +0000 qsmith  myproj =mp=     ~/*
     M 07/25 15:16 +0000 qsmith  1.14 hello.c    myproj == ~/mp
     U 07/25 15:21 +0000 qsmith  1.14 README.txt myproj == ~/mp
     G 07/25 15:21 +0000 qsmith  1.15 hello.c    myproj == ~/mp
     A 07/25 15:22 +0000 qsmith  1.1  goodbye.c  myproj == ~/mp
     M 07/25 15:23 +0000 qsmith  1.16 hello.c    myproj == ~/mp
     M 07/25 15:26 +0000 qsmith  1.17 hello.c    myproj == ~/mp
     U 07/25 15:29 +0000 qsmith  1.2  goodbye.c  myproj == ~/mp
     G 07/25 15:29 +0000 qsmith  1.18 hello.c    myproj == ~/mp
     M 07/25 15:30 +0000 qsmith  1.19 hello.c    myproj == ~/mp
     O 07/23 03:45 +0000 jrandom myproj =myproj= ~/src/*
     F 07/23 03:48 +0000 jrandom        =myproj= ~/src/*
     F 07/23 04:06 +0000 jrandom        =myproj= ~/src/*
     M 07/25 15:12 +0000 jrandom 1.13 README.txt myproj == ~/src/myproj
     U 07/25 15:17 +0000 jrandom 1.14 hello.c    myproj == ~/src/myproj
     M 07/25 15:18 +0000 jrandom 1.14 README.txt myproj == ~/src/myproj
     M 07/25 15:18 +0000 jrandom 1.15 hello.c    myproj == ~/src/myproj
     U 07/25 15:23 +0000 jrandom 1.1  goodbye.c  myproj == ~/src/myproj
     U 07/25 15:23 +0000 jrandom 1.16 hello.c    myproj == ~/src/myproj
     U 07/25 15:26 +0000 jrandom 1.1  goodbye.c  myproj == ~/src/myproj
     G 07/25 15:26 +0000 jrandom 1.17 hello.c    myproj == ~/src/myproj
     M 07/25 15:27 +0000 jrandom 1.18 hello.c    myproj == ~/src/myproj
     C 07/25 15:30 +0000 jrandom 1.19 hello.c    myproj == ~/src/myproj
     M 07/25 15:31 +0000 jrandom 1.20 hello.c    myproj == ~/src/myproj
     M 07/25 16:29 +0000 jrandom 1.3  whatever.c myproj/a-subdir == ~/src/myproj
     paste$
     

There, isn't that clear?

Before we examine the output, notice that the invocation included two options: -e and -a. When you run history, you almost always want to pass options telling it what data to report and how to report it. In this respect, it differs from most other CVS commands, which usually do something useful when invoked without any options. In this example, the two flags meant "everything" (show every kind of event that happened) and "all" (for all users), respectively.

Another way that history differs from other commands is that, although it is usually invoked from within a working copy, it does not restrict its output to that working copy's project. Instead, it shows all history events from all projects in the repository - the working copy merely serves to tell CVS from which repository to retrieve the history data. (In the preceding example, the only history data in that repository is for the myproj project, so that's all we see.)

The general format of the output is:

     CODE DATE USER [REVISION] [FILE] PATH_IN_REPOSITORY ACTUAL_WORKING_COPY_NAME
     

The code letters refer to various CVS operations, as shown in Table 6.1.

For operations (such as checkout) that are about the project as a whole rather than about individual files, the revision and file are omitted, and the repository path is placed between the equal signs.

Although the output of the history command was designed to be compact, parseable input for other programs, CVS still gives you a lot of control over its scope and content. The options shown in Table 6.2 control what types of events get reported.

     Table 6.1  The meaning of the code letters.
     
     Letter	        Meaning
     ======          =========================================================
     O		Checkout
     T		Tag
     F		Release
     W		Update (no user file, remove from entries file)
     U		Update (file overwrote unmodified user file)
     G		Update (file was merged successfully into modified user file)
     C		Update (file was merged, but conflicts w/ modified user file)
     M		Commit (from modified file)
     A		Commit (an added file)
     R		Commit (the removal of a file)
     E		Export
     
     Table 6.2  Options to filter by event type.
     
     Option	        Meaning
     ==========      =========================================================
     -m MODULE	Show historical events affecting MODULE.
     -c		Show commit events.
     -o		Show checkout events.
     -T		Show tag events.
     -x CODE(S)	Show all events of type CODE (one or more of OTFWUGCMARE).
     -e		Show all types of events, period.  Once you have
                     selected what type of events you want reported, you can
                     filter further with the options shown in Table 6.3.
     
     Table 6.3  Options to filter by user.
     
     Option	        Meaning
     ==========      =========================================================
     -a		Show actions taken by all users
     -w		Show only actions taken from within this working copy
     -l		Show only the last time this user took the action
     -u USER 	Show records for USER
     


Node: Annotations -- A Detailed View Of Project Activity, Next: , Previous: History -- A Summary Of Repository Activity, Up: Advanced CVS

Annotations - A Detailed View Of Project Activity

The annotate Command

If the history command gives an overview of project activity, the annotate command is a way of attaching a zoom lens to the view. With annotate, you can see who was the last person to touch each line of a file, and at what revision they touched it:

     floss$ cvs annotate
     Annotations for README.txt
     ***************
     1.14         (jrandom  25-Jul-99): blah
     1.13         (jrandom  25-Jul-99): test 3 for history
     1.12         (qsmith   19-Jul-99): test 2
     1.11         (qsmith   19-Jul-99): test
     1.10         (jrandom  12-Jul-99): blah
     1.1          (jrandom  20-Jun-99): Just a test project.
     1.4          (jrandom  21-Jun-99): yeah.
     1.5          (jrandom  21-Jun-99): nope.
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.15         (jrandom  25-Jul-99):   /* another test for history */
     1.13         (qsmith   19-Jul-99):   /* random change number two */
     1.10         (jrandom  12-Jul-99):   /* test */
     1.21         (jrandom  25-Jul-99):   printf ("Hellooo, world!\n");
     1.3          (jrandom  21-Jun-99):   printf ("hmmm\n");
     1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
     1.11         (qsmith   18-Jul-99):   /* added this comment */
     1.16         (qsmith   25-Jul-99):   /* will merge these changes */
     1.18         (jrandom  25-Jul-99):   /* will merge these changes too */
     1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
     1.1          (jrandom  20-Jun-99): }
     Annotations for a-subdir/whatever.c
     ***************
     1.3          (jrandom  25-Jul-99): /* A completely non-empty C file. */
     Annotations for a-subdir/subsubdir/fish.c
     ***************
     1.2          (jrandom  25-Jul-99): /* An almost completely empty C file. */
     Annotations for b-subdir/random.c
     ***************
     1.1          (jrandom  20-Jun-99): /* A completely empty C file. */
     floss$
     

The output of annotate is pretty intuitive. On the left are the revision number, developer, and date on which the line in question was added or last modified. On the right is the line itself, as of the current revision. Because every line is annotated, you can actually see the entire contents of the file, pushed over to the right by the annotation information.

If you specify a revision number or tag, the annotations are given as of that revision, meaning that it shows the most recent modification to each line at or before that revision. This is probably the most common way to use annotations - examining a particular revision of a single file to determine which developers were active in which parts of the file.

For example, in the output of the previous example, you can see that the most recent revision of hello.c is 1.21, in which jrandom did something to the line:

     printf ("Hellooo, world!\n");
     

One way to find out what she did is to diff that revision against the previous one:

     floss$ cvs diff -r 1.20 -r 1.21 hello.c
     Index: hello.c
     ===================================================================
     RCS file: /usr/local/newrepos/myproj/hello.c,v
     retrieving revision 1.20
     retrieving revision 1.21
     diff -r1.20 -r1.21
     9c9
     <   printf ("Hello, world!\n");
     --
     >   printf ("Hellooo, world!\n");
     floss$
     

Another way to find out, while still retaining a file-wide view of everyone's activity, is to compare the current annotations with the annotations from a previous revision:

     floss$ cvs annotate -r 1.20 hello.c
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.15         (jrandom  25-Jul-99):   /* another test for history */
     1.13         (qsmith   19-Jul-99):   /* random change number two */
     1.10         (jrandom  12-Jul-99):   /* test */
     1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
     1.3          (jrandom  21-Jun-99):   printf ("hmmm\n");
     1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
     1.11         (qsmith   18-Jul-99):   /* added this comment */
     1.16         (qsmith   25-Jul-99):   /* will merge these changes */
     1.18         (jrandom  25-Jul-99):   /* will merge these changes too */
     1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
     1.1          (jrandom  20-Jun-99): }
     floss$
     

Although the diff reveals the textual facts of the change more concisely, the annotation may be preferable because it places them in their historical context by showing how long the previous incarnation of the line had been present (in this case, all the way since revision 1.1). That knowledge can help you decide whether to look at the logs to find out the motivation for the change:

     floss$ cvs log -r 1.21 hello.c
     RCS file: /usr/local/newrepos/myproj/hello.c,v
     Working file: hello.c
     head: 1.21
     branch:
     locks: strict
     access list:
     symbolic names:
            random-tag: 1.20
            start: 1.1.1.1
            jrandom: 1.1.1
     keyword substitution: kv
     total revisions: 22;    selected revisions: 1
     description:
     ----------------------------
     revision 1.21
     date: 1999/07/25 20:17:42;  author: jrandom;  state: Exp;  lines: +1 -1
     say hello with renewed enthusiasm
     ============================================================================
     floss$
     

In addition to -r, you can also filter annotations using the -D DATE option:

     floss$ cvs annotate -D "5 weeks ago" hello.c
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
     1.1          (jrandom  20-Jun-99): }
     floss$ cvs annotate -D "3 weeks ago" hello.c
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
     1.3          (jrandom  21-Jun-99):   printf ("hmmm\n");
     1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
     1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
     1.1          (jrandom  20-Jun-99): }
     floss$
     


Node: Annotations And Branches, Next: , Previous: Annotations -- A Detailed View Of Project Activity, Up: Advanced CVS

Annotations And Branches

By default, annotation always shows activity on the main trunk of development. Even when invoked from a branch working copy, it shows annotations for the trunk unless you specify otherwise. (This tendency to favor the trunk is either a bug or a feature, depending on your point of view.) You can force CVS to annotate a branch by passing the branch tag as an argument to -r. Here is an example from a working copy in which hello.c is on a branch named Brancho_Gratuito, with at least one change committed on that branch:

     floss$ cvs status hello.c
     ===================================================================
     File: hello.c           Status: Up-to-date
     
       Working revision:    1.10.2.2        Sun Jul 25 21:29:05 1999
       Repository revision: 1.10.2.2        /usr/local/newrepos/myproj/hello.c,v
       Sticky Tag:          Brancho_Gratuito (branch: 1.10.2)
       Sticky Date:         (none)
       Sticky Options:      (none)
     
     floss$ cvs annotate hello.c
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.10         (jrandom  12-Jul-99):   /* test */
     1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
     1.3          (jrandom  21-Jun-99):   printf ("hmmm\n");
     1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
     1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
     1.1          (jrandom  20-Jun-99): }
     floss$ cvs annotate -r Brancho_Gratuito hello.c
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.10         (jrandom  12-Jul-99):   /* test */
     1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
     1.10.2.2     (jrandom  25-Jul-99):   printf ("hmmmmm\n");
     1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
     1.10.2.1     (jrandom  25-Jul-99):   printf ("added this line");
     1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
     1.1          (jrandom  20-Jun-99): }
     floss$
     

You can also pass the branch number itself:

     floss$ cvs annotate -r 1.10.2 hello.c
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.10         (jrandom  12-Jul-99):   /* test */
     1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
     1.10.2.2     (jrandom  25-Jul-99):   printf ("hmmmmm\n");
     1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
     1.10.2.1     (jrandom  25-Jul-99):   printf ("added this line");
     1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
     1.1          (jrandom  20-Jun-99): }
     floss$
     

or a full revision number from the branch:

     floss$ cvs annotate -r 1.10.2.1 hello.c
     Annotations for hello.c
     ***************
     1.1          (jrandom  20-Jun-99): #include <stdio.h>
     1.1          (jrandom  20-Jun-99):
     1.1          (jrandom  20-Jun-99): void
     1.1          (jrandom  20-Jun-99): main ()
     1.1          (jrandom  20-Jun-99): {
     1.10         (jrandom  12-Jul-99):   /* test */
     1.1          (jrandom  20-Jun-99):   printf ("Hello, world!\n");
     1.3          (jrandom  21-Jun-99):   printf ("hmmm\n");
     1.4          (jrandom  21-Jun-99):   printf ("double hmmm\n");
     1.10.2.1     (jrandom  25-Jul-99):   printf ("added this line");
     1.2          (jrandom  21-Jun-99):   printf ("Goodbye, world!\n");
     1.1          (jrandom  20-Jun-99): }
     floss$
     

If you do this, remember that the numbers are only valid for that particular file. In general, it's probably better to use the branch name wherever possible.


Node: Using Keyword Expansion, Next: , Previous: Annotations And Branches, Up: Advanced CVS

Using Keyword Expansion

You may recall a brief mention of keyword expansion in An Overview of CVS. RCS keywords are special words, surrounded by dollar signs, that CVS looks for in text files and expands into revision-control information. For example, if a file contains

     $Author$
     

then when updating the file to a given revision, CVS will expand it to the username of the person who committed that revision:

     $Author: jrandom $
     

CVS is also sensitive to keywords in their expanded form, so that once expanded, they continue to be updated as appropriate.

Although keywords don't actually offer any information that's not available by other means, they give people a convenient way to see revision control facts embedded in the text of the file itself, rather than by invoking some arcane CVS operation.

Here are a few other commonly used keywords:

     $Date$       ==>  date of last commit, expands to ==>
     $Date: 1999/07/26 06:39:46 $
     
     $Id$         ==>  filename, revision, date, and author; expands to ==>
     $Id: hello.c,v 1.11 1999/07/26 06:39:46 jrandom Exp $
     
     $Revision$   ==>  exactly what you think it is, expands to ==>
     $Revision: 1.11 $
     
     $Source$     ==> path to corresponding repository file, expands to ==>
     $Source: /usr/local/newrepos/tossproj/hello.c,v $
     
     $Log$        ==>  accumulating log messages for the file, expands to ==>
     $Log: hello.c,v $
     Revision 1.2  1999/07/26 06:47:52  jrandom
     ...and this is the second log message.
     
     Revision 1.1  1999/07/26 06:39:46  jrandom
     This is the first log message...
     

The $Log$ keyword is the only one of these that expands to cover multiple lines, so its behavior is unique. Unlike the others, it does not replace the old expansion with the new one, but instead inserts the latest expansion, plus an additional blank line, right after the keyword (thereby pushing any previous expansions downward). Furthermore, any text between the beginning of the line and $Log is used as a prefix for the expansions (this is done to ensure that the log messages stay commented in program code). For example, if you put this into the file

     // $Log$
     

it will expand to something like this on the first commit:

     // $Log: hello.c,v $
     // Revision 1.14  1999/07/26 07:03:20  jrandom
     // this is the first log message...
     //
     

this on the second:

     // $Log: hello.c,v $
     // Revision 1.15  1999/07/26 07:04:40  jrandom
     // ...and this is the second log message...
     //
     // Revision 1.14  1999/07/26 07:03:20  jrandom
     // this is the first log message...
     //
     

and so on:

     // $Log: hello.c,v $
     // Revision 1.16  1999/07/26 07:05:34  jrandom
     // ...and this is the third!
     //
     // Revision 1.15  1999/07/26 07:04:40  jrandom
     // ...and this is the second log message...
     //
     // Revision 1.14  1999/07/26 07:03:20  jrandom
     // this is the first log message...
     //
     

You may not want to keep your entire log history in the file all the time; if you do, you can always remove the older sections when it starts to get too lengthy. It's certainly more convenient than running cvs log, and it may be worthwhile in projects where people must constantly read over the logs.

A more common technique may be to include $Revision$ in a file and use it as the version number for the program. This can work if the project consists of essentially one file or undergoes frequent releases and has at least one file that is guaranteed to be modified between every release. You can even use an RCS keyword as a value in program code:

     VERSION = "$Revision: 1.114 $";
     

CVS expands that keyword just like any other; it has no concept of the programming language's semantics and does not assume that the double quotes protect the string in any way.

A complete list of keywords (there are a few more, rather obscure ones) is given in CVS Reference.


Node: Going Out On A Limb (How To Work With Branches And Survive), Next: , Previous: Using Keyword Expansion, Up: Advanced CVS

Going Out On A Limb (How To Work With Branches And Survive)

Branches are simultaneously one of the most important and most easily misused features of CVS. Isolating risky or disruptive changes onto a separate line of development until they stabilize can be immensely helpful. If not properly managed, however, branches can quickly propel a project into confusion and cascading chaos, as people lose track of what changes have been merged when.


Node: Some Principles For Working With Branches, Next: , Up: Going Out On A Limb (How To Work With Branches And Survive)

Some Principles For Working With Branches

To work successfully with branches, your development group should adhere to these principles:

With those principles in mind, let's take a look at a typical branch development scenario. We'll have jrandom on the trunk and qsmith on the branch, but note that there could just as well be multiple developers on the trunk and/or on the branch. Regular development along either line can involve any number of people; however, the tagging and merging are best done by one person on each side, as you'll see.


Node: Merging Repeatedly Into The Trunk, Next: , Previous: Some Principles For Working With Branches, Up: Going Out On A Limb (How To Work With Branches And Survive)

Merging Repeatedly Into The Trunk

Let's assume qsmith needs to do development on a branch for a while, to avoid destabilizing the trunk that he shares with jrandom. The first step is to create the branch. Notice how qsmith creates a regular (non-branch) tag at the branch point first, and then creates the branch:

     paste$ pwd
     /home/qsmith/myproj
     paste$ cvs tag Root-of-Exotic_Greetings
     cvs tag: Tagging .
     T README.txt
     T foo.gif
     T hello.c
     cvs tag: Tagging a-subdir
     T a-subdir/whatever.c
     cvs tag: Tagging a-subdir/subsubdir
     T a-subdir/subsubdir/fish.c
     cvs tag: Tagging b-subdir
     T b-subdir/random.c
     paste$ cvs tag -b Exotic_Greetings-branch
     cvs tag: Tagging .
     T README.txt
     T foo.gif
     T hello.c
     cvs tag: Tagging a-subdir
     T a-subdir/whatever.c
     cvs tag: Tagging a-subdir/subsubdir
     T a-subdir/subsubdir/fish.c
     cvs tag: Tagging b-subdir
     T b-subdir/random.c
     paste$
     

The point of tagging the trunk first is that it may be necessary someday to retrieve the trunk as it was the moment the branch was created. If you ever need to do that, you'll have to have a way of referring to the trunk snapshot without referring to the branch itself. Obviously, you can't use the branch tag because that would retrieve the branch, not the revisions in the trunk that form the root of the branch. The only way to do it is to make a regular tag at the same revisions the branch sprouts from. (Some people stick to this rule so faithfully that I considered listing it as "Branching Principle Number 4: Always create a non-branch tag at the branch point." However, many sites don't do it, and they generally seem to do okay, so it's really a matter of taste.) From here on, I will refer to this non-branch tag as the branch point tag.

Notice also that a naming convention is being adhered to: The branch point tag begins with Root-of-, then the actual branch name, which uses underscores instead of hyphens to separate words. When the actual branch is created, its tag ends with the suffix -branch so that you can identify it as a branch tag just by looking at the tag name. (The branch point tag Root-of-Exotic_Greetings does not include the -branch because it is not a branch tag.) You don't have to use this particular naming convention, of course, but you should use some convention.

Of course, I'm being extra pedantic here. In smallish projects, where everyone knows who's doing what and confusion is easy to recover from, these conventions don't have to be used. Whether you use a branch point tag or have a strict naming convention for your tags depends on the complexity of the project and the branching scheme. (Also, don't forget that you can always go back later and update old tags to use new conventions by retrieving an old tagged version, adding the new tag, and then deleting the old tag.)

Now, qsmith is ready to start working on the branch:

     paste$ cvs update -r Exotic_Greetings-branch
     cvs update: Updating .
     cvs update: Updating a-subdir
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir
     paste$
     

He makes some changes to a couple of files and commits them on the branch:

     paste$ emacs README.txt a-subdir/whatever.c b-subdir/random.c
     ...
     paste$ cvs ci -m "print greeting backwards, etc"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in README.txt;
     /usr/local/newrepos/myproj/README.txt,v  <--  README.txt
     new revision: 1.14.2.1; previous revision: 1.14
     done
     Checking in a-subdir/whatever.c;
     /usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
     new revision: 1.3.2.1; previous revision: 1.3
     done
     Checking in b-subdir/random.c;
     /usr/local/newrepos/myproj/b-subdir/random.c,v  <--  random.c
     new revision: 1.1.1.1.2.1; previous revision: 1.1.1.1
     done
     paste$
     

Meanwhile, jrandom is continuing to work on the trunk. She modifies two of the three files that qsmith touched. Just for kicks, we'll have her make changes that conflict with qsmith's work:

     floss$ emacs README.txt whatever.c
      ...
     floss$ cvs ci -m "some very stable changes indeed"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in README.txt;
     /usr/local/newrepos/myproj/README.txt,v  <--  README.txt
     new revision: 1.15; previous revision: 1.14
     done
     Checking in a-subdir/whatever.c;
     /usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
     new revision: 1.4; previous revision: 1.3
     done
     floss$
     

The conflict is not apparent yet, of course, because neither developer has tried to merge branch and trunk. Now, jrandom does the merge:

     floss$ cvs update -j Exotic_Greetings-branch
     cvs update: Updating .
     RCS file: /usr/local/newrepos/myproj/README.txt,v
     retrieving revision 1.14
     retrieving revision 1.14.2.1
     Merging differences between 1.14 and 1.14.2.1 into README.txt
     rcsmerge: warning: conflicts during merge
     cvs update: Updating a-subdir
     RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v
     retrieving revision 1.3
     retrieving revision 1.3.2.1
     Merging differences between 1.3 and 1.3.2.1 into whatever.c
     rcsmerge: warning: conflicts during merge
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir
     RCS file: /usr/local/newrepos/myproj/b-subdir/random.c,v
     retrieving revision 1.1.1.1
     retrieving revision 1.1.1.1.2.1
     Merging differences between 1.1.1.1 and 1.1.1.1.2.1 into random.c
     floss$ cvs update
     cvs update: Updating .
     C README.txt
     cvs update: Updating a-subdir
     C a-subdir/whatever.c
     cvs update: Updating a-subdir/subsubdir
     cvs update: Updating b-subdir
     M b-subdir/random.c
     floss$
     

Two of the files conflict. No big deal; with her usual savoir-faire, jrandom resolves the conflicts, commits, and tags the trunk as successfully merged:

     floss$ emacs README.txt a-subdir/whatever.c
      ...
     floss$ cvs ci -m "merged from Exotic_Greetings-branch (conflicts resolved)"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in README.txt;
     /usr/local/newrepos/myproj/README.txt,v  <--  README.txt
     new revision: 1.16; previous revision: 1.15
     done
     Checking in a-subdir/whatever.c;
     /usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
     new revision: 1.5; previous revision: 1.4
     done
     Checking in b-subdir/random.c;
     /usr/local/newrepos/myproj/b-subdir/random.c,v  <--  random.c
     new revision: 1.2; previous revision: 1.1
     done
     floss$ cvs tag merged-Exotic_Greetings
     cvs tag: Tagging .
     T README.txt
     T foo.gif
     T hello.c
     cvs tag: Tagging a-subdir
     T a-subdir/whatever.c
     cvs tag: Tagging a-subdir/subsubdir
     T a-subdir/subsubdir/fish.c
     cvs tag: Tagging b-subdir
     T b-subdir/random.c
     floss$
     

Meanwhile, qsmith needn't wait for the merge to finish before continuing development, as long as he makes a tag for the batch of changes from which jrandom merged (later, jrandom will need to know this tag name; in general, branches depend on frequent and thorough developer communications):

     paste$ cvs tag Exotic_Greetings-1
     cvs tag: Tagging .
     T README.txt
     T foo.gif
     T hello.c
     cvs tag: Tagging a-subdir
     T a-subdir/whatever.c
     cvs tag: Tagging a-subdir/subsubdir
     T a-subdir/subsubdir/fish.c
     cvs tag: Tagging b-subdir
     T b-subdir/random.c
     paste$ emacs a-subdir/whatever.c
      ...
     paste$ cvs ci -m "print a randomly capitalized greeting"
     cvs commit: Examining .
     cvs commit: Examining a-subdir
     cvs commit: Examining a-subdir/subsubdir
     cvs commit: Examining b-subdir
     Checking in a-subdir/whatever.c;
     /usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
     new revision: 1.3.2.2; previous revision: 1.3.2.1
     done
     paste$
     

And of course, qsmith should tag those changes once he's done:

     paste$ cvs -q tag Exotic_Greetings-2
     T README.txt
     T foo.gif
     T hello.c
     T a-subdir/whatever.c
     T a-subdir/subsubdir/fish.c
     T b-subdir/random.c
     paste$
     

While all this is going on, jrandom makes a change in a different file, one that qsmith hasn't touched in his new batch of edits:

     floss$ emacs README.txt
      ...
     floss$ cvs ci -m "Mention new Exotic Greeting features" README.txt
     Checking in README.txt;
     /usr/local/newrepos/myproj/README.txt,v  <--  README.txt
     new revision: 1.17; previous revision: 1.16
     done
     floss$
     

At this point, qsmith has committed a new change on the branch, and jrandom has committed a nonconflicting change in a different file on the trunk. Watch what happens when jrandom tries to merge from the branch again:

     floss$ cvs -q update -j Exotic_Greetings-branch
     RCS file: /usr/local/newrepos/myproj/README.txt,v
     retrieving revision 1.14
     retrieving revision 1.14.2.1
     Merging differences between 1.14 and 1.14.2.1 into README.txt
     rcsmerge: warning: conflicts during merge
     RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v
     retrieving revision 1.3
     retrieving revision 1.3.2.2
     Merging differences between 1.3 and 1.3.2.2 into whatever.c
     rcsmerge: warning: conflicts during merge
     RCS file: /usr/local/newrepos/myproj/b-subdir/random.c,v
     retrieving revision 1.1
     retrieving revision 1.1.1.1.2.1
     Merging differences between 1.1 and 1.1.1.1.2.1 into random.c
     floss$ cvs -q update
     C README.txt
     C a-subdir/whatever.c
     floss$
     

There are conflicts! Is that what you expected?

The problem lies in the semantics of merging. Back in An Overview of CVS, I explained that when you run

     floss$ cvs update -j BRANCH
     

in a working copy, CVS merges into the working copy the differences between BRANCH's root and its tip. The trouble with that behavior, in this situation, is that most of those changes had already been incorporated into the trunk the first time that jrandom did a merge. When CVS tried to merge them in again (over themselves, as it were), it naturally registered a conflict.

What jrandom really wanted to do was merge into her working copy the changes between the branch's most recent merge and its current tip. You can do this by using two -j flags to update, as you may recall from An Overview of CVS, as long as you know what revision to specify with each flag. Fortunately, qsmith made a tag at exactly the last merge point (hurrah for planning ahead!), so this will be no problem. First, let's have jrandom restore her working copy to a clean state, from which she can redo the merge:

     floss$ rm README.txt a-subdir/whatever.c
     floss$ cvs -q update
     cvs update: warning: README.txt was lost
     U README.txt
     cvs update: warning: a-subdir/whatever.c was lost
     U a-subdir/whatever.c
     floss$
     

Now she's ready to do the merge, this time using qsmith's conveniently placed tag:

     floss$ cvs -q update -j Exotic_Greetings-1 -j Exotic_Greetings-branch
     RCS file: /usr/local/newrepos/myproj/a-subdir/whatever.c,v
     retrieving revision 1.3.2.1
     retrieving revision 1.3.2.2
     Merging differences between 1.3.2.1 and 1.3.2.2 into whatever.c
     floss$ cvs -q update
     M a-subdir/whatever.c
     floss$
     

Much better. The change from qsmith has been incorporated into whatever.c; jrandom can now commit and tag:

     floss$ cvs -q ci -m "merged again from Exotic_Greetings (1)"
     Checking in a-subdir/whatever.c;
     /usr/local/newrepos/myproj/a-subdir/whatever.c,v  <--  whatever.c
     new revision: 1.6; previous revision: 1.5
     done
     floss$ cvs -q tag merged-Exotic_Greetings-1
     T README.txt
     T foo.gif
     T hello.c
     T a-subdir/whatever.c
     T a-subdir/subsubdir/fish.c
     T b-subdir/random.c
     floss$
     

Even if qsmith had forgotten to tag at the merge point, all hope would not be lost. If jrandom knew approximately when qsmith's first batch of changes had been committed, she could try filtering by date:

     floss$ cvs update -j Exotic_Greetings-branch:3pm -j Exotic_Greetings_branch
     

Although useful as a last resort, filtering by date is less than ideal because it selects the changes based on people's recollections rather than dependable developer designations. If qsmith's first mergeable set of changes had happened over several commits instead of in one commit, jrandom may mistakenly choose a date or time that would catch some of the changes, but not all of them.

There's no reason why each taggable point in qsmith's changes needs to be sent to the repository in a single commit - it just happens to have worked out that way in these examples. In real life, qsmith may make several commits between tags. He can work on the branch in isolation, as he pleases. The point of the tags is to record successive points on the branch where he considers the changes to be mergeable into the trunk. As long as jrandom always merges using two -j flags and is careful to use qsmith's merge tags in the right order and only once each, the trunk should never experience the double-merge problem. Conflicts may occur, but they will be the unavoidable kind that requires human resolution - situations in which both branch and trunk made changes to the same area of code.


Node: The Dovetail Approach -- Merging In And Out Of The Trunk, Next: , Previous: Merging Repeatedly Into The Trunk, Up: Going Out On A Limb (How To Work With Branches And Survive)

The Dovetail Approach - Merging In And Out Of The Trunk

Merging repeatedly from branch to trunk is good for the people on the trunk, because they see all of their own changes and all the changes from the branch. However, the developer on the branch never gets to incorporate any of the work being done on the trunk.

To allow that, the branch developer needs to add an extra step every now and then (meaning whenever he feels like merging in recent trunk changes and dealing with the inevitable conflicts):

     paste$ cvs update -j HEAD
     

The special reserved tag HEAD means the tip of the trunk. The preceding command merges in all of the trunk changes between the root of the current branch (Exotic_Greetings-branch) and the current highest revisions of each file on the trunk. Of course, qsmith should tag again after doing this, so that the trunk developers can avoid accidentally merging in their own changes when they're trying to get qsmith's.

The branch developer can likewise use the trunk's merge tags as boundaries, allowing the branch to merge exactly those trunk changes between the last merge and the trunk's current state (the same way the trunk does merges). For example, supposing jrandom had made some changes to hello.c after merging from the branch:

     floss$ emacs hello.c
      ...
     floss$ cvs ci -m "clarify algorithm" hello.c
     Checking in hello.c;
     /usr/local/newrepos/myproj/hello.c,v  <--  hello.c
     new revision: 1.22; previous revision: 1.21
     done
     floss$
     

Then, qsmith can merge those changes into his branch, commit, and, of course, tag:

     paste$ cvs -q update -j merged-Exotic_Greetings-1 -j HEAD
     RCS file: /usr/local/newrepos/myproj/hello.c,v
     retrieving revision 1.21
     retrieving revision 1.22
     Merging differences between 1.21 and 1.22 into hello.c
     paste$ cvs -q update
     M hello.c
     paste$ cvs -q ci -m "merged trunk, from merged-Exotic_Greetings-1 to HEAD"
     Checking in hello.c;
     /usr/local/newrepos/myproj/hello.c,v  <--  hello.c
     new revision: 1.21.2.1; previous revision: 1.21
     done
     paste$ cvs -q tag merged-merged-Exotic_Greetings-1
     T README.txt
     T foo.gif
     T hello.c
     T a-subdir/whatever.c
     T a-subdir/subsubdir/fish.c
     T b-subdir/random.c
     paste$
     

Notice that jrandom did not bother to tag after committing the changes to hello.c, but qsmith did. The principle at work here is that although you don't need to tag after every little change, you should always tag after a merge or after committing your line of development up to a mergeable state. That way, other people - perhaps on other branches - have a reference point against which to base their own merges.


Node: The Flying Fish Approach -- A Simpler Way To Do It, Next: , Previous: The Dovetail Approach -- Merging In And Out Of The Trunk, Up: Going Out On A Limb (How To Work With Branches And Survive)

The Flying Fish Approach - A Simpler Way To Do It

There is a simpler, albeit slightly limiting, variant of the preceding. In it, the branch developers freeze while the trunk merges, and then the trunk developers create an entirely new branch, which replaces the old one. The branch developers move onto that branch and continue working. The cycle continues until there is no more need for branch development. It goes something like this (in shorthand - we'll assume jrandom@floss has the trunk and qsmith@paste has the branch, as usual):

     floss$ cvs tag -b BRANCH-1
     paste$ cvs checkout -r BRANCH-1 myproj
     

Trunk and branch both start working; eventually, the developers confer and decide it's time to merge the branch into the trunk:

     paste$ cvs ci -m "committing all uncommitted changes"
     floss$ cvs update -j BRANCH-1
     

All the changes from the branch merge in; the branch developers stop working while the trunk developers resolve any conflicts, commit, tag, and create a new branch:

     floss$ cvs ci -m "merged from BRANCH-1"
     floss$ cvs tag merged-from-BRANCH-1
     floss$ cvs tag -b BRANCH-2
     

Now the branch developers switch their working copies over to the new branch; they know they won't lose any uncommitted changes by doing so, because they were up-to-date when the merge happened, and the new branch is coming out of a trunk that has incorporated the changes from the old branch:

     paste$ cvs update -r BRANCH-2
     

And the cycle continues in that way, indefinitely; just substitute BRANCH-2 for BRANCH-1 and BRANCH-3 for BRANCH-2.

I call this the Flying Fish technique, because the branch is constantly emerging from the trunk, traveling a short distance, then rejoining it. The advantages of this approach are that it's simple (the trunk always merges in all the changes from a given branch) and the branch developers never need to resolve conflicts (they're simply handed a new, clean branch on which to work each time). The disadvantage, of course, is that the branch people must sit idle while the trunk is undergoing merge (which can take an arbitrary amount of time, depending on how many conflicts need to be resolved). Another minor disadvantage is that it results in many little, unused branches laying around instead of many unused non-branch tags. However, if having millions of tiny, obsolete branches doesn't bother you, and you anticipate fairly trouble-free merges, Flying Fish may be the easiest way to go in terms of mental bookkeeping.

Whichever way you do it, you should try to keep the separations as short as possible. If the branch and the trunk go too long without merging, they could easily begin to suffer not just from textual drift, but semantic drift as well. Changes that conflict textually are the easiest ones to resolve. Changes that conflict conceptually, but not textually, often prove hardest to find and fix. The isolation of a branch, so freeing to the developers, is dangerous precisely because it shields each side from the effects of others' changes...for a time. When you use branches, communication becomes more vital than ever: Everyone needs to make extra sure to review each others' plans and code to ensure that they're all staying on the same track.


Node: Branches And Keyword Expansion -- Natural Enemies, Previous: The Flying Fish Approach -- A Simpler Way To Do It, Up: Going Out On A Limb (How To Work With Branches And Survive)

Branches And Keyword Expansion - Natural Enemies

If your files contain RCS keywords that expand differently on branch and trunk, you're almost guaranteed to get spurious conflicts on every merge. Even if nothing else changed, the keywords are overlapping, and their expansions won't match. For example, if README.txt contains this on the trunk

     $Revision: 1.14 $
     

and this on the branch

     $Revision: 1.14.2.1 $
     

then when the merge is performed, you'll get the following conflict:

     floss$ cvs update -j Exotic_Greetings-branch
     RCS file: /usr/local/newrepos/myproj/README.txt,v
     retrieving revision 1.14
     retrieving revision 1.14.2.1
     Merging differences between 1.14 and 1.14.2.1 into README.txt
     rcsmerge: warning: conflicts during merge
     floss$ cat README.txt
      ...
     <<<<<<< README.txt
     key $Revision: 1.14 $
     =======
     key $Revision: 1.14.2.1 $
     >>>>>>> 1.14.2.1
      ...
     floss$
     

To avoid this, you can temporarily disable expansion by passing the -kk option (I don't know what it stands for; "kill keywords" maybe?) when you do the merge:

     floss$ cvs update -kk -j Exotic_Greetings-branch
     RCS file: /usr/local/newrepos/myproj/README.txt,v
     retrieving revision 1.14
     retrieving revision 1.14.2.1
     Merging differences between 1.14 and 1.14.2.1 into README.txt
     floss$ cat README.txt
      ...
     $Revision$
      ...
     floss$
     

There is one thing to be careful of, however: If you use -kk, it overrides whatever other keyword expansion mode you may have set for that file. Specifically, this is a problem for binary files, which are normally -kb (which suppresses all keyword expansion and line-end conversion). So if you have to merge binary files in from a branch, don't use -kk. Just deal with the conflicts by hand instead.


Node: Tracking Third-Party Sources (Vendor Branches), Next: , Previous: Going Out On A Limb (How To Work With Branches And Survive), Up: Advanced CVS

Tracking Third-Party Sources (Vendor Branches)

Sometimes a site will make local changes to a piece of software received from an outside source. If the outside source does not incorporate the local changes (and there might be many legitimate reasons why it can't), the site has to maintain its changes in each received upgrade of the software.

CVS can help with this task, via a feature known as vendor branches. In fact, vendor branches are the explanation behind the puzzling (until now) final two arguments to cvs import: the vendor tag and release tag that I glossed over in An Overview of CVS.

Here's how it works. The initial import is just like any other initial import of a CVS project (except that you'll want to choose the vendor tag and release tag with a little care):

     floss$ pwd
     /home/jrandom/theirproj-1.0
     floss$ cvs import -m "Import of TheirProj 1.0" theirproj Them THEIRPROJ_1_0
     N theirproj/INSTALL
     N theirproj/README
     N theirproj/src/main.c
     N theirproj/src/parse.c
     N theirproj/src/digest.c
     N theirproj/doc/random.c
     N theirproj/doc/manual.txt
     
     No conflicts created by this import
     
     floss$
     

Then you check out a working copy somewhere, make your local modifications, and commit:

     floss$ cvs -q co theirproj
     U theirproj/INSTALL
     U theirproj/README
     U theirproj/doc/manual.txt
     U theirproj/doc/random.c
     U theirproj/src/digest.c
     U theirproj/src/main.c
     U theirproj/src/parse.c
     floss$ cd theirproj
     floss$ emacs src/main.c src/digest.c
      ...
     floss$ cvs -q update
     M src/digest.c
     M src/main.c
     floss$ cvs -q ci -m "changed digestion algorithm; added comment to main"
     Checking in src/digest.c;
     /usr/local/newrepos/theirproj/src/digest.c,v  <--  digest.c
     new revision: 1.2; previous revision: 1.1
     done
     Checking in src/main.c;
     /usr/local/newrepos/theirproj/src/main.c,v  <--  main.c
     new revision: 1.2; previous revision: 1.1
     done
     floss$
     

A year later, the next version of the software arrives from Them, Inc., and you must incorporate your local changes into it. Their changes and yours overlap slightly. They've added one new file, modified a couple of files that you didn't touch, but also modified two files that you modified.

First you must do another import, this time from the new sources. Almost everything is the same as it was in the initial import - you're importing to the same project in the repository, and on the same vendor branch. The only thing different is the release tag:

     floss$ pwd
     /home/jrandom/theirproj-2.0
     floss$ cvs -q import -m "Import of TheirProj 2.0" theirproj Them THEIRPROJ_2_0
     U theirproj/INSTALL
     N theirproj/TODO
     U theirproj/README
     cvs import: Importing /usr/local/newrepos/theirproj/src
     C theirproj/src/main.c
     U theirproj/src/parse.c
     C theirproj/src/digest.c
     cvs import: Importing /usr/local/newrepos/theirproj/doc
     U theirproj/doc/random.c
     U theirproj/doc/manual.txt
     
     2 conflicts created by this import.
     Use the following command to help the merge:
     
            cvs checkout -jThem:yesterday -jThem theirproj
     
     floss$
     

My goodness - we've never seen CVS try to be so helpful. It's actually telling us what command to run to merge the changes. And it's almost right, too! Actually, the command as given works (assuming that you adjust yesterday to be any time interval that definitely includes the first import but not the second), but I mildly prefer to do it by release tag instead:

     floss$ cvs checkout -j THEIRPROJ_1_0 -j THEIRPROJ_2_0 theirproj
     cvs checkout: Updating theirproj
     U theirproj/INSTALL
     U theirproj/README
     U theirproj/TODO
     cvs checkout: Updating theirproj/doc
     U theirproj/doc/manual.txt
     U theirproj/doc/random.c
     cvs checkout: Updating theirproj/src
     U theirproj/src/digest.c
     RCS file: /usr/local/newrepos/theirproj/src/digest.c,v
     retrieving revision 1.1.1.1
     retrieving revision 1.1.1.2
     Merging differences between 1.1.1.1 and 1.1.1.2 into digest.c
     rcsmerge: warning: conflicts during merge
     U theirproj/src/main.c
     RCS file: /usr/local/newrepos/theirproj/src/main.c,v
     retrieving revision 1.1.1.1
     retrieving revision 1.1.1.2
     Merging differences between 1.1.1.1 and 1.1.1.2 into main.c
     U theirproj/src/parse.c
     floss$
     

Notice how the import told us that there were two conflicts, but the merge only seems to claim one conflict. It seems that CVS's idea of a conflict is a little different when importing than at other times. Basically, import reports a conflict if both you and the vendor modified a file between the last import and this one. However, when it comes time to merge, update sticks with the usual definition of "conflict" - overlapping changes. Changes that don't overlap are merged in the usual way, and the file is simply marked as modified.

A quick diff verifies that only one of the files actually has conflict markers:

     floss$ cvs -q update
     C src/digest.c
     M src/main.c
     floss$ cvs diff -c
     Index: src/digest.c
     ===================================================================
     RCS file: /usr/local/newrepos/theirproj/src/digest.c,v
     retrieving revision 1.2
     diff -c -r1.2 digest.c
     *** src/digest.c        1999/07/26 08:02:18     1.2
     -- src/digest.c        1999/07/26 08:16:15
     ***************
     *** 3,7 ****
     -- 3,11 ----
      void
      digest ()
      {
     + <<<<<<< digest.c
        printf ("gurgle, slorp\n");
     + =======
     +   printf ("mild gurgle\n");
     + >>>>>>> 1.1.1.2
      }
     Index: src/main.c
     ===================================================================
     RCS file: /usr/local/newrepos/theirproj/src/main.c,v
     retrieving revision 1.2
     diff -c -r1.2 main.c
     *** src/main.c  1999/07/26 08:02:18     1.2
     -- src/main.c  1999/07/26 08:16:15
     ***************
     *** 7,9 ****
     -- 7,11 ----
      {
        printf ("Goodbye, world!\n");
      }
     +
     + /* I, the vendor, added this comment for no good reason. */
     floss$
     

From here, it's just a matter of resolving the conflicts as with any other merge:

     floss$ emacs  src/digest.c  src/main.c
      ...
     floss$ cvs -q update
     M src/digest.c
     M src/main.c
     floss$ cvs diff src/digest.c
     cvs diff src/digest.c
     Index: src/digest.c
     ===================================================================
     RCS file: /usr/local/newrepos/theirproj/src/digest.c,v
     retrieving revision 1.2
     diff -r1.2 digest.c
     6c6
     <   printf ("gurgle, slorp\n");
     --
     >   printf ("mild gurgle, slorp\n");
     floss$
     

Then commit the changes

     floss$ cvs -q ci -m "Resolved conflicts with import of 2.0"
     Checking in src/digest.c;
     /usr/local/newrepos/theirproj/src/digest.c,v  <--  digest.c
     new revision: 1.3; previous revision: 1.2
     done
     Checking in src/main.c;
     /usr/local/newrepos/theirproj/src/main.c,v  <--  main.c
     new revision: 1.3; previous revision: 1.2
     done
     floss$
     

and wait for the next release from the vendor. (Of course, you'll also want to test that your local modifications still work!)

-------------------------------------------------------------


Node: Exporting For Public Distribution, Next: , Previous: Tracking Third-Party Sources (Vendor Branches), Up: Advanced CVS

Exporting For Public Distribution

CVS is a good distribution mechanism for developers, but most users will obtain the software through a downloadable package instead. This package is generally not a CVS working copy - it's just a source tree that can be easily configured and compiled on the user's system.

However, CVS does offer a mechanism to help you create that package, namely the cvs export command. To export a project is just like checking out a working copy of the project, except that it checks out the project tree without any CVS administrative subdirectories. That is to say, you don't get a working copy, you just get a source tree that knows nothing about where it came from or what the CVS versions of its files are. Thus, the exported copy is just like what the public sees after it downloads and unpacks a distribution. Assuming the project is arranged to be directly compilable from a working copy (and it certainly should be!), then it will still be compilable from the exported copy.

The export command works like checkout, except that it requires a tag name or date. For example, here we tag the project with a release name, and then export based on that:

     floss$ pwd
     /home/jrandom/myproj
     floss$ cvs -q tag R_1_0
     T README.txt
     T hello.c
     T a-subdir/whatever.c
     T a-subdir/subsubdir/fish.c
     T b-subdir/random.c
     floss$ cd ..
     floss$ cvs -d /usr/local/newrepos -q export -r R_1_0 -d myproj-1.0 myproj
     U myproj-1.0/README.txt
     U myproj-1.0/hello.c
     U myproj-1.0/a-subdir/whatever.c
     U myproj-1.0/a-subdir/subsubdir/fish.c
     U myproj-1.0/b-subdir/random.c
     floss$ cd myproj-1.0
     floss$ ls
     README.txt  a-subdir  b-subdir  hello.c
     

Notice how, since the export command is not invoked from within a working copy, it's necessary to use the global -d option to tell CVS which repository to use. Also, in this particular example, we exported into an explicitly named directory (myproj-1.0) instead of defaulting to the project's name (myproj), since there was a working copy of that name already present. This situation is not uncommon.

After the exported copy is created, as in the above example, the following might be sufficient to complete the release, if the project is a simple one:

     floss$ tar cf myproj-1.0.tar myproj-1.0
     floss$ gzip --best myproj-1.0.tar
     floss$ ls
     myproj/   myproj-1.0/   myproj-1.0.tar.gz
     floss$ rm -rf myproj-1.0
     floss$ mv myproj-1.0.tar.gz /home/ftp/pub/myproj/
     

Of course, running all of these commands by hand is rare. More often, cvs export is called from within some script that handles all aspects of release and packaging process. Given that there are often several "test" releases leading up to each public release, it is desirable that the procedures for creating a releasable package be highly automated.


Node: The Humble Guru, Previous: Exporting For Public Distribution, Up: Advanced CVS

The Humble Guru

If you read and understood (and better yet, experimented with) everything in this chapter, you may rest assured that there are no big surprises left for you in CVS - at least until someone adds a major new feature to CVS. Everything you need to know to use CVS on a major project has been presented.

Before that goes to your head, let me reiterate the suggestion, first made in Chapter 4, that you subscribe to the [email protected] mailing list. Despite having the impoverished signal-to-noise ratio common to most Internet mailing lists, the bits of signal that do come through are almost always worth the wait. I was subscribed during the entire time I wrote this chapter (indeed, for all previous chapters as well), and you would be amazed to know how many important details I learned about CVS's behavior from reading other people's posts. If you're going to be using CVS seriously, and especially if you're the CVS administrator for a group of developers, you can benefit a lot from the shared knowledge of all the other serious users out there.


Node: Tips And Troubleshooting, Next: , Previous: Advanced CVS, Up: Top

Tips And Troubleshooting

I've said in earlier chapters that CVS is not "black box" software. Black boxes don't let you peek inside; they don't give you internal access so that you can fix (or break) things. The premise is that the black box usually doesn't need to be fixed. Most of the time, the software should work perfectly, so users don't need internal access. But when black boxes do fail, they tend to fail completely. Any problem at all is a showstopper, because there aren't many options for repair.

CVS is more like a perfectly transparent box - except without the box. Its moving parts are exposed directly to the environment, not hermetically sealed off, and bits of that environment (unexpected file permissions, interrupted commands, competing processes, whatever) can sometimes get inside the mechanism and gum up the gears. But even though CVS does not always work perfectly, it rarely fails completely, either. It has the advantage of graceful degradation; the degree to which it doesn't work is usually proportional to the number and severity of problems in its environment. If you know enough about what CVS is trying to do - and how it's trying to do it - you'll know what to do when things go wrong.

Although I can't list all of the problems that you might encounter, I've included some of the more common ones here. This chapter is divided into two sections: The first describes those parts of the environment to which CVS is most sensitive (mainly repository permissions and the working copy administrative area), and the second describes some of the most frequently encountered problems and their solutions. By seeing how to handle these common situations, you will get a feeling for how to approach any unexpected problem in CVS.


Node: The Usual Suspects, Next: , Up: Tips And Troubleshooting

The Usual Suspects

As a CVS administrator (read "field doctor"), you will find that 90 percent of your users' problems are caused by inconsistent working copies, and the other 90 percent by incorrect repository permissions. Therefore, before looking at any specific situations, I'll give a quick overview of the working copy administrative area and review a few important things about repository permissions.


Node: The Working Copy Administrative Area, Next: , Up: The Usual Suspects

The Working Copy Administrative Area

You've already seen the basics of working copy structure in An Overview of CVS; in this section, we'll go into a bit more detail. Most of the details concern the files in the CVS/ administrative subdirectories. You already know about Entries, Root, and Repository, but the CVS/ subdirectory can also contain other files, depending on the circumstances. I'll describe those other files here, partly so they don't surprise you when you encounter them, and partly so you can fix them if they ever cause trouble.

CVS/Entries.Log

Sometimes, a file named CVS/Entries.Log will mysteriously appear. The sole purpose of this file is to temporarily cache minor changes to CVS/Entries, until some operation significant enough to be worth rewriting the entire Entries file comes along. CVS has no ability to edit the Entries file in place; it must read the entire file in and write it back out to make any change. To avoid this effort, CVS sometimes records small changes in Entries.Log, until the next time it needs to rewrite Entries.

The format of Entries.Log is like Entries, except for an extra letter at the beginning of each line. A means that the line is to be added to the main Entries file, and R means it is to be removed.

For the most part, you can ignore Entries.Log; it's rare that a human has to understand the information it contains. However, if you're reading over an Entries file to debug some problem in a working copy, you should also examine Entries.Log.

CVS/Entries.Backup

The CVS/Entries.Backup file is where CVS actually writes out a new Entries file, before renaming it to Entries (similar to the way it writes to temporary RCS files in the repository and then moves them to their proper name when they're complete). Because it becomes Entries when it's complete, you'll rarely see an Entries.Backup file; if you do see one, it probably means CVS got interrupted in the middle of some operation.

CVS/Entries.Static

If the CVS/Entries.Static file exists, it means that the entire directory has not been fetched from the repository. (When CVS knows a working directory is in an incomplete state, it will not bring additional files into that directory.)

The Entries.Static file is present during checkouts and updates and removed immediately when the operation is complete. If you see Entries.Static, it means that CVS was interrupted, and its presence prevents CVS from creating any new files in the working copy. (Often, running cvs update -d solves the problem and removes Entries.Static.)

The absence of Entries.Static does not necessarily imply that the working copy contains all of the project's files. Whenever a new directory is created in the project's repository, and someone updates their working copy without passing the -d flag to update, the new directory will not be created in the working copy. Locally, CVS is unaware that there is a new directory in the repository, so it goes ahead and removes the Entries.Static file when the update is complete, even though the new directory is not present in the working copy.

CVS/Tag

If the CVS/Tag file is present, it names a tag associated, in some sense, with the directory. I say "in some sense" because, as you know, CVS does not actually keep any revision history for directories and, strictly speaking, cannot attach tags to them. Tags are attached to regular files only or, more accurately, to particular revisions in regular files.

However, if every file in a directory is on a particular tag, CVS likes to think of the entire directory as being on the tag, too. For example, if you were to check out a working copy on a particular branch:

     floss$ cvs co -r Bugfix_Branch_1
     

and then add a file inside it, you'd want the new file's initial revision to be on that branch, too. For similar reasons, CVS also needs to know if the directory has a nonbranch sticky tag or date set on it.

Tag files contain one line. The first character on the line is a single-letter code telling what kind of tag it is, and the rest of the line is the tag's name. Currently, CVS uses only these three single-letter codes:

(If you see some other single-letter code, it just means that CVS has added a new tag type since this chapter was written.)

You should not remove the Tag file manually; instead, use cvs update -A.

Rarities

There are a few other files you may occasionally find in a CVS/ subdirectory:

These files are usually not the cause of problems, so I'm just listing them (see CVS Reference for their full descriptions).

Portability And Future Extension

As features are added to CVS, new files (not listed here) may appear in working copy administrative areas. As new files are added, they'll probably be documented in the Cederqvist manual, in the node Working Directory Storage. You can also start looking in src/cvs.h in the source distribution, if you prefer to learn from code.

Finally, note that all CVS/* files - present and future - use whatever line-ending convention is appropriate for the working copy's local system (for example, LF for Unix or CRLF for Windows). This means that if you transport a working copy from one kind of machine to the other, CVS won't be able to handle it (but then, you'd have other problems, because the revision-controlled files themselves would have the wrong line-end conventions for their new location).


Node: Repository Permissions, Previous: The Working Copy Administrative Area, Up: The Usual Suspects

Repository Permissions

CVS does not require any particular repository permission scheme - it can handle a wide variety of permission arrangements. However, to avoid getting confusing behaviors, you should make sure your repository setup meets at least the following criteria:


Node: General Troubleshooting Tips, Next: , Previous: The Usual Suspects, Up: Tips And Troubleshooting

General Troubleshooting Tips

The bulk of this chapter is organized into a series of questions and answers, similar to an Internet FAQ (Frequently Asked Questions) document. These are all based on actual CVS experiences. But before we look at individual cases, let's take a moment to consider CVS troubleshooting from a more general point of view.

The first step in solving a CVS problem is usually to determine whether it's a working copy or repository problem. The best technique for doing that, not surprisingly, is to see if the problem occurs in working copies other than the one where it was first noticed. If it does, it's likely a repository issue; otherwise, it's probably just a local issue.

Working copy problems tend to be encountered more frequently, not because working copies are somehow less reliable than repositories, but because each repository usually has many working copies. Although most working copy knots can be untied with enough patience, you may occasionally find it more time-efficient simply to delete the working copy and check it out again.

Of course, if checking out again takes too long, or there is considerable uncommitted state in the working copy that you don't want to lose, or if you just want to know what's wrong, it's worth digging around to find the cause of the problem. When you start digging around, one of the first places to look is in the CVS/ subdirectories. Check the file contents and the file permissions. Very occasionally, the permissions can mysteriously become read-only or even unreadable. (I suspect this is caused by users accidentally mistyping Unix commands rather than any mistake on CVS's part.)

Repository problems are almost always caused by incorrect file and directory permissions. If you suspect a problem may be due to bad repository permissions, first find out the effective repository user ID of the person who's having the trouble. For all local and most remote users, this is either their regular username or the username they specified when they checked out their working copy. If they're using the pserver method with user-aliasing (see the section Anonymous Access in Repository Administration), the effective user ID is the one on the right in the CVSROOT/passwd file. Failure to discover this early on can cause you to waste a lot of time debugging the wrong thing.

And now, without further ado...


Node: Some Real Life Problems (With Solutions), Previous: General Troubleshooting Tips, Up: Tips And Troubleshooting

Some Real Life Problems (With Solutions)

All of these situations are ones I've encountered in my real-life adventures as a CVS troubleshooter (plus a few items that are not really problems, just questions that I've heard asked so often that they may as well be answered here). The list is meant to be fairly comprehensive, and it may repeat material you've seen in earlier chapters.

The situations are listed according to how frequently they seem to arise, with the most common ones first.


Node: CVS says it is waiting for a lock; what does that mean?, Next: , Up: Some Real Life Problems (With Solutions)

CVS says it is waiting for a lock; what does that mean?

If you see a message like this

     cvs update: [22:58:26] waiting for qsmith's lock in /usr/local/newrepos/myproj
     

it means you're trying to access a subdirectory of the repository that is locked by some other CVS process at the moment. A process is being run in that directory so it may not be in a consistent state for other CVS processes to use.

However, if the wait message persists for a long time, it probably means that a CVS process failed to clean up after itself, for whatever reason. It can happen when CVS dies suddenly and unexpectedly, say, due to a power failure on the repository machine.

The solution is to remove the lock files by hand from the repository subdirectory in question. Go into that part of the repository and look for files named #cvs.lock or that begin with #cvs.wfl or #cvs.rfl. Compare the file's timestamps with the start times of any currently running CVS processes. If the files could not possibly have been created by any of those processes, it's safe to delete them. The waiting CVS processes eventually notice when the lock files are gone - this should take about 30 seconds - and allow the requested operation to proceed.

See the node Locks in the Cederqvist manual for more details.


Node: CVS claims a file is failing Up-To-Date check; what do I do?, Next: , Previous: CVS says it is waiting for a lock; what does that mean?, Up: Some Real Life Problems (With Solutions)

CVS claims a file is failing Up-To-Date check; what do I do?

Don't panic - it just means that the file has changed in the repository since the last time you checked it out or updated it.

Run cvs update on the file to merge in the changes from the repository. If the received changes conflict with your local changes, edit the file to resolve the conflict. Then try your commit again - it will succeed, barring the possibility that someone committed yet another revision while you were busy merging the last changes.


Node: The pserver access method is not working, Next: , Previous: CVS claims a file is failing Up-To-Date check; what do I do?, Up: Some Real Life Problems (With Solutions)

The pserver access method is not working

The most common, less obvious cause of this problem is that you forgot to list the repository using an --allow-root option in your inetd configuration file.

Recall this example /etc/inetd.conf line from Repository Administration:

     cvspserver stream tcp nowait root /usr/local/bin/cvs cvs \
               --allow-root=/usr/local/newrepos pserver
     

(In the actual file, this is all one long line, with no backslash.)

The --allow-root=/usr/local/newrepos portion is a security measure, to make sure that people can't use CVS to get pserver access to repositories that are not supposed to be served remotely. Any repository intended to be accessible via pserver must be mentioned in an --allow-root. You can have as many different --allow-root options as you need for all of your system's repositories (or anyway, as many as you want until you bump up against your inetd's argument limit).

See Repository Administration for more details on setting up the password-authenticating server.


Node: The pserver access method is STILL not working, Next: , Previous: The pserver access method is not working, Up: Some Real Life Problems (With Solutions)

The pserver access method is STILL not working

Okay, if the problem is not a missing --allow-root, here are a few other possibilities:


Node: My commits seem to happen in pieces instead of atomically, Next: , Previous: The pserver access method is STILL not working, Up: Some Real Life Problems (With Solutions)

My commits seem to happen in pieces instead of atomically

That's because CVS commits happen in pieces, not atomically. :-)

More specifically, CVS operations happen directory by directory. When you do a commit (or an update, or anything else, for that matter) spanning multiple directories, CVS locks each corresponding repository directory in turn while it performs the operation for that directory.

For small- to medium-sized projects, this is rarely a problem - CVS manages to do its thing in each directory so quickly that you never notice the nonatomicity. Unfortunately, in large projects, scenarios like the following can occur (imagine this taking place in a project with at least two deep, many-filed subdirectories, A and B):

  1. User qsmith starts a commit, involving files from both subdirectories. CVS commits the files in B first (perhaps because qsmith specified the directories on the command line in that order).
  2. User jrandom starts a cvs update. The update, for whatever reason, starts with working copy directory A (CVS makes no guarantees about the order in which it processes directories or files, if left to its own devices). Note that there is no locking contention, because qsmith is not active in A yet.
  3. Then, qsmith's commit finishes B, moves on to A, and finishes A.
  4. Finally, jrandom's update moves on to B and finishes it.

Clearly, when this is all over, jrandom's working copy reflects qsmith's changes to B but not A. Even though qsmith intended the changes to be committed as a single unit, it didn't happen that way. Now jrandom's working copy is in a state that qsmith never anticipated.

The solution, of course, is for jrandom to do another cvs update to fetch the uncaught changes from qsmith's commit. However, that assumes that jrandom has some way of finding out in the first place that he only got part of qsmith's changes.

There's no easy answer to this quandary. You simply have to hope that the inconsistent state of the working copy will somehow become apparent (maybe the software won't build, or jrandom and qsmith will have a conversation that's confusing until they realize what must have happened).

CVS's failure to provide atomic transaction guarantees is widely considered a bug. The only reason that locks are not made at the top level of the repository is that this would result in intolerably frequent lock contentions for large projects with many developers. Therefore, CVS has chosen the lesser of two evils, reducing the contention frequency but allowing the possibility of interleaved reads and writes. Someday, someone may modify CVS (say, speeding up repository operations) so that it doesn't have to choose between two evils; until then, we're stuck with nonatomic actions.

For more information, see the node Concurrency in the Cederqvist manual.


Node: CVS keeps changing file permissions; why does it do that?, Next: , Previous: My commits seem to happen in pieces instead of atomically, Up: Some Real Life Problems (With Solutions)

CVS keeps changing file permissions; why does it do that?

In general, CVS doesn't do a very good job of preserving permissions on files. When you import a project and then check it out, there is no guarantee that the file permissions in the new working copy will be the same as when the project was imported. More likely, the working copy files will be created with the same standard permissions that you normally get on newly created files.

However, there is at least one exception. If you want to store executable shell scripts in the project, you can keep them executable in all working copies by making the corresponding repository file executable:

     floss$ ls -l /usr/local/newrepos/someproj
     total 6
     -r--r--r--   1 jrandom  users         630 Aug 17 01:10 README.txt,v
     -r-xr-xr-x   1 jrandom  users        1041 Aug 17 01:10 scrub.pl,v*
     -r--r--r--   1 jrandom  users         750 Aug 17 01:10 hello.c,v
     

Notice that although the file is executable, it is still read-only, as all repository files should be (remember that CVS works by making a temporary copy of the RCS file, doing everything in the copy, and then replacing the original with the copy when ready).

When you import or add an executable file, CVS preserves the executable bits, so if the permissions were correct from the start, you have nothing to worry about. However, if you accidentally add the file before making it executable, you must go into the repository and manually set the RCS file to be executable.

The repository permissions always dominate. If the file is nonexecutable in the repository, but executable in the working copy, the working copy file will also be nonexecutable after you do an update. Having your files' permissions silently change can be extremely frustrating. If this happens, first check the repository and see if you can solve it by setting the appropriate permissions on the corresponding RCS files.

A feature called PreservePermissions has recently been added to CVS that may alleviate some of these problems. However, using this feature can cause other unexpected results (which is why I'm not recommending it unconditionally here). Make sure you read the nodes config and Special Files in the Cederqvist before putting PreservePermissions=yes in CVSROOT/config.


Node: CVS on Windows complains it cannot find my .cvspass file; why?, Next: , Previous: CVS keeps changing file permissions; why does it do that?, Up: Some Real Life Problems (With Solutions)

CVS on Windows complains it cannot find my .cvspass file; why?

For pserver connections, CVS on the client side tries to find the .cvspass file in your home directory. Windows machines don't have a natural "home" directory, so CVS consults the environment variable %HOME%. However, you have to be very careful about how you set HOME. This will work:

     set HOME=C:
     

This will not:

     set HOME=C:\
     

That extra backslash is enough to confuse CVS, and it will be unable to open C:\\.cvspass.

So, the quick and permanent solution is to put

     set HOME=C:
     

into your autoexec.bat and reboot. CVS pserver should work fine after that.


Node: My working copy is on several different branches; help?, Next: , Previous: CVS on Windows complains it cannot find my .cvspass file; why?, Up: Some Real Life Problems (With Solutions)

My working copy is on several different branches; help?

You mean different subdirectories of your working copy somehow got on different branches? You probably ran updates with the -r flag, but from places other than the top level of the working copy.

No big deal. If you want to return to the trunk, just run this

     cvs update -r HEAD
     

or this

     cvs update -A
     

from the top directory. Or, if you want to put the whole working copy on one of the branches, do this:

     cvs update -r Branch_Name
     

There's nothing necessarily wrong with having one or two subdirectories of your working copy on a different branch than the rest of it, if you need to do some temporary work on that branch just in those locations. However, it's usually a good idea to switch them back when you're done - life is much less confusing when your whole working copy is on the same line of development.


Node: When I do export -d I sometimes miss recent commits, Next: , Previous: My working copy is on several different branches; help?, Up: Some Real Life Problems (With Solutions)

When I do export -d I sometimes miss recent commits

This is due to a clock difference between the repository and local machines. You can solve it by resetting one or both of the clocks, or specifying a different date as the argument to -D. It's perfectly acceptable to specify a date in the future (such as -D tomorrow), if that's what it takes to compensate for the time difference.


Node: I get an error about val-tags; what should I do?, Next: , Previous: When I do export -d I sometimes miss recent commits, Up: Some Real Life Problems (With Solutions)

I get an error about val-tags; what should I do?

If you see an error like this:

     cvs [export aborted]: cannot write /usr/local/myproj/CVSROOT/val-tags: \
        Operation not permitted
     

it means the user CVS is running as does not have permission to write to the CVSROOT/val-tags file. This file stores valid tag names, to give CVS a fast way to determine what tags are valid. Unfortunately, CVS sometimes modifies this file even for operations that are read-only with respect to the repository, such as checking out a project.

This is a bug in CVS and may be fixed by the time you read this. Until then, the solution is either to make val-tags world-writeable or, failing that, to remove it or change its ownership to the user running the CVS operation. (You'd think just changing the permissions would be enough, but on several occasions I've had to change the ownership, too.)


Node: I am having problems with sticky tags; how do I get rid of them?, Next: , Previous: I get an error about val-tags; what should I do?, Up: Some Real Life Problems (With Solutions)

I am having problems with sticky tags; how do I get rid of them?

Various CVS operations cause the working copy to have a sticky tag, meaning a single tag that corresponds to each revision for each file (in the case of a branch, the sticky tag is applied to any new files added in the working copy). You get a sticky tagged working area whenever you check out or update by tag or date, for example:

     floss$ cvs update -r Tag_Name
     

or

     floss$ cvs checkout -D '1999-08-16'
     

If a date or a nonbranch tag name is used, the working copy will be a frozen snapshot of that moment in the project's history - so naturally you will not be able to commit any changes from it.

To remove a sticky tag, run update with the -A flag

     floss$ cvs update -A
     

which clears all the sticky tags and updates each file to its most recent trunk revision.


Node: Checkouts/updates exit with error saying cannot expand modules, Next: , Previous: I am having problems with sticky tags; how do I get rid of them?, Up: Some Real Life Problems (With Solutions)

Checkouts/updates exit with error saying cannot expand modules

This is just a case of a bad error message in CVS; probably someone will get around to fixing it sooner or later, but meanwhile it may bite you. The error message looks something like this:

     floss$ cvs co -d bwf-misc user-space/bwf/writings/misc
     cvs server: cannot find module `user-space/bwf/writings/misc' - ignored
     cvs [checkout aborted]: cannot expand modules
     

CVS appears to be saying that there's something wrong with the CVSROOT/modules file. However, what's really going on is a permission problem in the repository. The directory I'm trying to check out isn't readable, or one of its parents isn't readable. In this case, it was a parent:

     floss$ ls -ld /usr/local/cvs/user-space/bwf
     
     drwx------  19 bwf      users        1024 Aug 17 01:24 bwf/
     

Don't let that egregiously wrong error message fool you - this is a repository permission problem.


Node: I cannot seem to turn off watches, Next: , Previous: Checkouts/updates exit with error saying cannot expand modules, Up: Some Real Life Problems (With Solutions)

I cannot seem to turn off watches

You probably did

     floss$ cvs watch remove
     

on all the files, but forgot to also do:

     floss$ cvs watch off
     

A hint for diagnosing watch problems: Sometimes it can be immensely clarifying to just go into the repository and examine the CVS/fileattr files directly. See Repository Administration for more information about them.


Node: My binary files are messed up, Next: , Previous: I cannot seem to turn off watches, Up: Some Real Life Problems (With Solutions)

My binary files are messed up

Did you remember to use -kb when you added them? If not, CVS may have performed line-end conversion or RCS keyword substitution on them. The easiest solution is usually to mark them as binary

     floss$ cvs admin -kb foo.gif
     

and then commit a fixed version of the file. CVS will not corrupt the new commit or any of the commits thereafter, because it now knows the file is binary.


Node: CVS is not doing line-end conversion correctly, Next: , Previous: My binary files are messed up, Up: Some Real Life Problems (With Solutions)

CVS is not doing line-end conversion correctly

If you're running the CVS client on a non-Unix platform and are not getting the line-end conventions that you want in some working copy files, it's usually because they were accidentally added with -kb when they shouldn't have been. This can be fixed in the repository with, believe it or not, the command:

     floss$ cvs admin -kkv FILE
     

The -kkv means to do normal keyword substitution and implies normal line-end conversions as well. (Internally, CVS is a bit confused about the difference between keyword substitution and line-end conversion. This confusion is reflected in the way the -k options can control both parameters.)

Unfortunately, that admin command only fixes the file in the repository - your working copy still thinks the file is binary. You can hand edit the CVS/Entries line for that file, removing the -kb, but that won't solve the problem for any other working copies out there.


Node: I need to remove a subdirectory in my project; how do I do it?, Next: , Previous: CVS is not doing line-end conversion correctly, Up: Some Real Life Problems (With Solutions)

I need to remove a subdirectory in my project; how do I do it?

Well, you can't exactly remove the subdirectory, but you can remove all of the files in it (first remove them, then cvs remove them, and then commit). Once the directory is empty, people can have it automatically pruned out of their working copies by passing the -P flag to update.


Node: Can I copy .cvspass files or portions of them?, Next: , Previous: I need to remove a subdirectory in my project; how do I do it?, Up: Some Real Life Problems (With Solutions)

Can I copy .cvspass files or portions of them?

Yes, you can. You can copy .cvspass files from machine to machine, and you can even copy individual lines from one .cvspass file to another. For high-latency servers, this may be faster than running cvs login from each working copy machine.

Remember that if you transport a .cvspass file between two machines with different line-ending conventions, it probably won't work (of course, you can probably do the line-end conversion manually without too much trouble).


Node: I just committed some files with the wrong log message, Next: , Previous: Can I copy .cvspass files or portions of them?, Up: Some Real Life Problems (With Solutions)

I just committed some files with the wrong log message

You don't need to hand-edit anything in the repository to solve this. Just run admin with the -m flag. Remember to have no space between -m and its argument, and to quote the replacement log message as you would a normal one:

     floss$ cvs admin -m1.17:'I take back what I said about the customer.' hello.c
     


Node: I need to move files around without losing revision history, Next: , Previous: I just committed some files with the wrong log message, Up: Some Real Life Problems (With Solutions)

I need to move files around without losing revision history

In the repository, copy (don't move) the RCS files to the desired new location in the project. They must remain in their old locations as well. Then, in a working copy, do:

     floss$ rm oldfile1 oldfile2 ...
     floss$ cvs remove oldfile1 oldfile2 ...
     floss$ cvs commit -m ��removed from here�� oldfile1 oldfile2 ...
     

When people do updates after that, CVS correctly removes the old files and brings the new files into the working copies just as though they had been added to the repository in the usual way (except that they'll be at unusually high revision numbers for supposedly new files).


Node: How can I get a list of all tags in a project?, Next: , Previous: I need to move files around without losing revision history, Up: Some Real Life Problems (With Solutions)

How can I get a list of all tags in a project?

Currently, there is no convenient way to do this in CVS. The lack is sorely felt by all users, and I believe work is under way to make this feature available. By the time you read this, a cvs tags command or something similar may be available.

Until then, there are workarounds. You can run cvs log -h and read the sections of the output following the header symbolic names:. Or, if you happen to be on the repository machine, you can just look at the beginnings of some of the RCS files directly in the repository. All of the tags (branches and nonbranches) are listed in the symbols field:

     floss$ head /usr/local/newrepos/hello.c,v
     head	2.0;
     access;
     symbols
     	Release_1_0:1.22
     	Exotic_Greetings-2:1.21
     	merged-Exotic_Greetings-1:1.21
     	Exotic_Greetings-1:1.21
     	merged-Exotic_Greetings:1.21
     	Exotic_Greetings-branch:1.21.0.2
     	Root-of-Exotic_Greetings:1.21
     	start:1.1.1.1
     	jrandom:1.1.1;
     locks; strict;
     comment	@ * @;
     


Node: How can I get a list of all projects in a repository?, Next: , Previous: How can I get a list of all tags in a project?, Up: Some Real Life Problems (With Solutions)

How can I get a list of all projects in a repository?

As with getting a list of tags, this is not implemented in the most current version of CVS, but it's highly likely that it will be implemented soon. I imagine the command will be called cvs list with a short form of cvs ls, and it probably will both parse the modules file and list the repository subdirectories.

In the meantime, examining the CVSROOT/modules file (either directly or by running cvs checkout -c) is probably your best bet. However, if no one has explicitly made a module for a particular project, it won't show up there.


Node: Some commands fail remotely but not locally; how should I debug?, Next: , Previous: How can I get a list of all projects in a repository?, Up: Some Real Life Problems (With Solutions)

Some commands fail remotely but not locally; how should I debug?

Sometimes there's a problem in the communication between the client and the server. If so, it's a bug in CVS, but how would you go about tracking down such a thing?

CVS gives you a way to watch the protocol between the client and server. Before you run the command on the local (working copy) machine, set the environment variable CVS_CLIENT_LOG. Here's how in Bourne shell syntax:

     floss$ CVS_CLIENT_LOG=clog; export CVS_CLIENT_LOG
     

Once that variable is set, CVS will record all communications between client and server in two files whose names are based on the variable's value:

     floss$ ls
     CVS/        README.txt    a-subdir/    b-subdir/    foo.gif     hello.c
     floss$ cvs update
     ? clog.in
     ? clog.out
     cvs server: Updating .
     cvs server: Updating a-subdir
     cvs server: Updating a-subdir/subsubdir
     cvs server: Updating b-subdir
     floss$ ls
     CVS/              a-subdir/    clog.in     foo.gif
     README.txt        b-subdir/    clog.out    hello.c
     floss$
     

The clog.in file contains everything that the client sent into the server, and clog.out contains everything the server sent back out to the client. Here are the contents of clog.out, to give you a sense of what the protocol looks like:

     Valid-requests Root Valid-responses valid-requests Repository           \
     Directory Max-dotdot Static-directory Sticky Checkin-prog Update-prog   \
     Entry Kopt Checkin-time Modified Is-modified UseUnchanged Unchanged     \
     Notify Questionable Case Argument Argumentx Global_option Gzip-stream   \
     wrapper-sendme-rcsOptions Set expand-modules ci co update diff log add  \
     remove update-patches gzip-file-contents status rdiff tag rtag import   \
     admin export history release watch-on watch-off watch-add watch-remove  \
     watchers editors init annotate noop
     ok
     M ? clog.in
     M ? clog.out
     E cvs server: Updating .
     E cvs server: Updating a-subdir
     E cvs server: Updating a-subdir/subsubdir
     E cvs server: Updating b-subdir
     ok
     

The clog.in file is even more complex, because it has to send revision numbers and other per-file information to the server.

There isn't space here to document the client/server protocol, but you can read the cvsclient Info pages that were distributed with CVS for a complete description. You may be able to figure out a good deal of it just from reading the raw protocol itself. Although you probably won't find yourself using client logging until you've eliminated all of the other possible causes of a problem, it is an invaluable tool for finding out what's really going on between the client and server.


Node: I do not see my problem covered in this chapter, Next: , Previous: Some commands fail remotely but not locally; how should I debug?, Up: Some Real Life Problems (With Solutions)

I do not see my problem covered in this chapter

Email an accurate and complete description of your problem to [email protected], the CVS discussion list. Its members are located in many different time zones, and I've usually gotten a response within an hour or two of sending a question. Please join the list by sending email to [email protected], so you can help answer questions, too.


Node: I think I have discovered a bug in CVS; what do I do?, Next: , Previous: I do not see my problem covered in this chapter, Up: Some Real Life Problems (With Solutions)

I think I have discovered a bug in CVS; what do I do?

CVS is far from perfect - if you've already tried reading the manual and posting a question on the mailing list, and you still think you're looking at a bug, then you probably are.

Send as complete a description of the bug as you can to [email protected] (you can also subscribe to that list; just use [email protected] instead). Be sure to include the version number of CVS (both client and server versions, if applicable), and a recipe for reproducing the bug.

If you have written a patch to fix the bug, include it and mention on the subject line of your message that you have a patch. The maintainers will be very grateful.

(Further details about these procedures are outlined in the node BUGS in the Cederqvist manual and the file HACKING in the source distribution.)


Node: I have implemented a new feature for CVS; to whom do I send it?, Next: , Previous: I think I have discovered a bug in CVS; what do I do?, Up: Some Real Life Problems (With Solutions)

I have implemented a new feature for CVS; to whom do I send it?

Same as with a bug: Send the patch to [email protected]. Make sure you've read over the HACKING file first, though.


Node: How can I keep up with changes to CVS?, Previous: I have implemented a new feature for CVS; to whom do I send it?, Up: Some Real Life Problems (With Solutions)

How can I keep up with changes to CVS?

The troubleshooting techniques and known bugs described in this chapter are accurate as of (approximately) CVS Version 1.10.7. Things move fast in the CVS world, however. While I was writing the last few chapters, the unofficial mantle of CVS maintainership passed from Cyclic Software to SourceGear, Inc (http://www.sourcegear.com), which purchased Cyclic. SourceGear has publicly announced its intention to take an active role in CVS maintainer-ship and has received Cyclic's approval, which is more or less enough to make it the "lead maintainer" of CVS as of right now. (The http://www.cyclic.com address will continue to work, however, so all of the URLs given previously in this book should remain valid.)

SourceGear is, at this very moment, busy organizing and cleaning up various patches that have been floating around, with the intention of incorporating many of them into CVS. Some of these patches will probably fix bugs listed previously, and others may afford new troubleshooting tools to CVS users.

The best way to stay up to date with what's going on is to read the NEWS file in your CVS distribution, watch the mailing lists, and look for changes to the Cederqvist manual and the online version of this book (http://cvsbook.red-bean.com).


Node: CVS Reference, Next: , Previous: Tips And Troubleshooting, Up: Top

CVS Reference

This chapter is a complete reference to CVS commands, repository administrative files, keyword substitution, run control files, working copy files, and environment variables - everything in CVS as of CVS version 1.10.7 (more accurately, as of August 20, 1999).


Node: Commands And Options, Next: , Up: CVS Reference

Commands And Options

This section is a reference to all CVS commands. If you are not already familiar with the syntactic conventions shared by most CVS commands, you should probably read the relevant subsections before you look up any particular command.


Node: Organization And Conventions, Next: , Up: Commands And Options

Organization And Conventions

This section is organized alphabetically to make it easy for you to look up a particular command or option. The following conventions are used:


Node: General Patterns In CVS Commands, Next: , Previous: Organization And Conventions, Up: Commands And Options

General Patterns In CVS Commands

CVS commands follow this form:

     cvs [GLOBAL_OPTIONS] COMMAND [OPTIONS] [FILES]
     

The second set of options is sometimes called command options. Because there are so many of them, though, I'll just call them "options" in most places to save space.

Many commands are meant to be run within a working copy and, therefore, may be invoked without file arguments. These commands default to all of the files in the current directory and below. So when I refer to the "file" or "files" in the text, I'm talking about the files on which CVS is acting. Depending on how you invoked CVS, these files may or may not have been explicitly mentioned on the command line.


Node: Date Formats, Next: , Previous: General Patterns In CVS Commands, Up: Commands And Options

Date Formats

Many options take a date argument. CVS accepts a wide variety of date formats - too many to list here. When in doubt, stick with the standard ISO 8601 format:

     1999-08-23
     

This means "23 August 1999" (in fact, "23 August 1999" is a perfectly valid date specifier too, as long as you remember to enclose it in double quotes). If you need a time of day as well, you can do this:

     "1999-08-23 21:20:30 CDT"
     

You can even use certain common English constructs, such as "now", "yesterday", and "12 days ago". In general, you can safely experiment with date formats; if CVS understands your format at all, it most likely will understand it in the way you intended. If it doesn't understand, it will exit with an error immediately.


Node: Global Options, Next: , Previous: Date Formats, Up: Commands And Options

Global Options

Here are all the global options to CVS.

--allow-root=REPOSITORY

The alphabetically first global option is one that is virtually never used on the command line. The -allow-root option is used with the pserver command to allow authenticated access to the named repository (which is a repository top level, such as /usr/local/newrepos, not a project subdirectory such as /usr/local/newrepos/myproj).

This global option is virtually never used on the command line. Normally, the only place you'd ever use it is in /etc/inetd.conf files (see Repository Administration), which is also about the only place the pserver command is used.

Every repository to be accessed via cvs pserver on a given host needs a corresponding -allow-root option in /etc/inetd.conf. This is a security device, meant to ensure that people can't use a CVS pserver to gain access to private repositories.

(See The Password-Authenticating Server also the node Password Authentication Server in the Cederqvist manual.)

-a

This authenticates all communications with the server. This option has no effect unless you're connecting via the GSSAPI server (gserver). GSSAPI connections are not covered in this book, because they're still somewhat rarely used (although that may change). (See the nodes Global Options and GSSAPI Authenticated in the Cederqvist manual for more information.)

-b (Obsolete)

This option formerly specified the directory where the RCS binaries could be found. CVS now implements the RCS functions internally, so this option has no effect (it is kept only for backward compatibility).

-d REPOSITORY

This specifies the repository, which might be an absolute pathname or a more complex expression involving a connection method, username and host, and path. If it is an expression specifying a connection method, the general syntax is:

     :METHOD:USER@HOSTNAME:PATH_TO_REPOSITORY
     

Here are examples using each of the connection methods:

-e EDITOR

Invokes EDITOR for your commit message, if the commit message was not specified on the command line with the -m option. Normally, if you don't give a message with -m, CVS invokes the editor based on the $CVSEDITOR, $VISUAL, or $EDITOR environment variables, which it checks in that order. Failing that, it invokes the popular Unix editor vi.

If you pass both the -e global option and the -m option to commit, the -e is ignored in favor of the commit message given on the command line (that way it's safe to use -e in a .cvsrc file).

-f

This global option suppresses reading of the .cvsrc file.

--help [COMMAND] or -H [COMMAND]

These two options are synonymous. If no COMMAND is specified, a basic usage message is printed to the standard output. If COMMAND is specified, a usage message for that command is printed.

--help-options

Prints out a list of all global options to CVS, with brief explanations.

--help-synonyms

Prints out a list of CVS commands and their short forms ("up" for "update", and so on).

-l

Suppresses logging of this command in the CVSROOT/history file in the repository. The command is still executed normally, but no record of it is made in the history file.

-n

Doesn't change any files in the working copy or in the repository. In other words, the command is executed as a "dry run" - CVS goes through most of the steps of the command but stops short of actually running it.

This is useful when you want to see what the command would have done had you actually run it. One common scenario is when you want to see what files in your working directory have been modified, but not do a full update (which would bring down changes from the repository). By running cvs -n update, you can see a summary of what's been done locally, without changing your working copy.

-q

This tells CVS to be moderately quiet by suppressing the printing of unimportant informational messages. What is considered "important" depends on the command. For example, in updates, the messages that CVS normally prints on entering each subdirectory of the working copy are suppressed, but the one-line status messages for modified or updated files are still printed.

-Q

This tells CVS to be very quiet, by suppressing all output except what is absolutely necessary to complete the command. Commands whose sole purpose is to produce some output (such as diff or annotate), of course, still give that output. However, commands that could have an effect independent of any messages that they may print (such as update or commit) print nothing.

-r

Causes new working files to be created read-only (the same effect as setting the $CVSREAD environment variable).

If you pass this option, checkouts and updates make the files in your working copy read-only (assuming your operating system permits it). Frankly, I'm not sure why one would ever want to use this option.

-s VARIABLE=VALUE

This sets an internal CVS variable named VARIABLE to VALUE.

On the repository side, the CVSROOT/*info trigger files can expand such variables to values that were assigned in the -s option. For example, if CVSROOT/loginfo contains a line like this

     myproj  /usr/local/bin/foo.pl ${=FISH}
     

and someone runs a commit from a myproj working copy like this

     floss$ cvs -s FISH=carp commit -m "fixed the bait bug"
     

the foo.pl script is invoked with carp as an argument. Note the funky syntax, though: The dollar sign, equal sign, and curly braces are all necessary - if any of them are missing, the expansion will not take place (at least not as intended). Variable names may contain alphanumerics and underscores only. Although it is not required that they consist entirely of capital letters, most people do seem to follow that convention.

You can use the -s flag as many times as you like in a single command. However, if the trigger script refers to variables that aren't set in a particular invocation of CVS, the command still succeeds, but none of the variables are expanded, and the user sees a warning. For example, if loginfo has this

     myproj  /usr/local/bin/foo.pl  ${=FISH}  ${=BIRD}
     

but the same command as before is run

     floss$ cvs -s FISH=carp commit -m "fixed the bait bug"
     

the person running the command sees a warning something like this (placed last in the output)

     loginfo:31: no such user variable ${=BIRD}
     

and the foo.pl script is invoked with no arguments. But if this command were run

     floss$ cvs -s FISH=carp -s BIRD=vulture commit -m "fixed the bait bug"
     

there would be no warning, and both ${=FISH} and ${=BIRD} in loginfo would be correctly expanded. In either case, the commit itself would still succeed.

Although these examples all use commit, variable expansion can be done with any CVS command that can be noticed in a CVSROOT/ trigger file - which is why the -s option is global.

(See the section Repository Administrative Files later in this chapter for more details about variable expansion in trigger files.)

-T DIR

Stores any temporary files in DIR instead of wherever CVS normally puts them (specifically, this overrides the value of the $TMPDIR environment variable, if any exists). DIR should be an absolute path.

This option is useful when you don't have write permission (and, therefore, CVS doesn't either) to the usual temporary locations.

-t

Traces the execution of a CVS command. This causes CVS to print messages showing the steps that it's going through to complete a command. You may find it particularly useful in conjunction with the -n global option, to preview the effects of an unfamiliar command before running it for real. It can also be handy when you're trying to discover why a command failed.

-v or --version

Causes CVS to print out its version and copyright information and then exit with no error.

-w

Causes new working files to be created read-write (overrides any setting of the $CVSREAD environment variable). Because files are created read-write by default anyway, this option is rarely used.

If both -r and -w are passed, -w dominates.

-x

Encrypts all communications with the server. This option has no effect unless you're connecting via the GSSAPI server (gserver). GSSAPI connections are not covered in this book, because they're still somewhat rarely used (although that may change). (See the nodes Global Options and GSSAPI Authenticated in the Cederqvist manual for more information.)

-z GZIPLEVEL

Sets the compression level on communications with the server. The argument GZIPLEVEL must be a number from 1 to 9. Level 1 is minimal compression (very fast, but doesn't compress much); Level 9 is highest compression (uses a lot of CPU time, but sure does squeeze the data). Level 9 is only useful on very slow network connections. Most people find levels between 3 and 5 to be most beneficial.

A space between -z and its argument is optional.


Node: add, Next: , Previous: Global Options, Up: Commands And Options

add

Synopsis: add [OPTIONS] FILES

Adds a new file or files to an existing project. Although the repository is contacted for confirmation, the file does not actually appear in it until a subsequent commit is performed. (See also remove and import.)

Options:


Node: admin, Next: , Previous: add, Up: Commands And Options

admin

Synopsis: admin [OPTIONS] [FILES]

This command is an interface to various administrative tasks - specifically, tasks applicable to individual RCS files in the repository, such as changing a file's keyword substitution mode or changing a log message after it's been committed.

Although admin behaves recursively if no files are given as arguments, you normally will want to name files explicitly. It's very rare for a single admin command to be meaningful when applied to all files in a project, or even in a directory. Accordingly, when the following explanations refer to the "file", they mean the file or (rarely) files passed as arguments to the admin command.

If there is a system group named cvsadmin on the repository machine, only members of that group can run admin (with the exception of the cvs admin -k command, which is always permitted). Thus you can disallow admin for all users by setting the group to have no users.

Options:


Node: annotate, Next: , Previous: admin, Up: Commands And Options

annotate

Synopsis: annotate [OPTIONS] [FILES]

Shows information on who last modified each line of each file and when. Each line of output corresponds to one line of the file. From left to right, the line displays the revision number of the last modification of that line, a parenthetical expression containing the user and date of the modification, a colon, and the contents of the line in the file.

For example, if a file looks like this

     this is a test file
     it only has too lines
     I mean "two"
     

the annotations for that file could look like this

     1.1          (jrandom  22-Aug-99): this is a test file
     1.1          (jrandom  22-Aug-99): it only has too lines
     1.2          (jrandom  22-Aug-99): I mean "two"
     

from which you would know that the first two lines were in the initial revision, and the last line was added or modified (also by jrandom) in Revision 1.2.

Options:


Node: checkout, Next: , Previous: annotate, Up: Commands And Options

checkout

Synopsis: checkout [OPTIONS] PROJECT(S)

Checks out a module from the repository into a working copy. The working copy is created if it doesn't exist already and updated if it does. (See also update.)

Options:


Node: commit, Next: , Previous: checkout, Up: Commands And Options

commit

Synopsis: commit [OPTIONS] [FILES]

Commits changes from a working copy to the repository.

Options:


Node: diff, Next: , Previous: commit, Up: Commands And Options

diff

Synopsis: diff [OPTIONS] [FILES]

Shows the difference between two revisions (in Unix diff format). When invoked with no options, CVS diffs the repository base revisions against the (possibly uncommitted) contents of the working copy. The base revisions are the latest revisions of this working copy retrieved from the repository; note that there could be even later revisions in the repository, if someone else committed changes but this working copy hasn't been updated yet. (See also rdiff.)

Options:

Diff Compatibility Options

In addition to the preceding options, cvs diff also shares a number of options with the GNU version of the standard command-line diff program. Following is a complete list of these options, along with an explanation of a few of the most commonly used ones. (See the GNU diff documentation for the others.)

     -0 -1 -2 -3 -4 -5 -6 -7 -8 -9
         --binary
         --brief
         --changed-group-format=ARG
         -c
           -C NLINES
           --context[=LINES]
         -e --ed
         -t --expand-tabs
         -f --forward-ed
         --horizon-lines=ARG
         --ifdef=ARG
         -w --ignore-all-space
         -B --ignore-blank-lines
         -i --ignore-case
         -I REGEXP
            --ignore-matching-lines=REGEXP
         -h
         -b --ignore-space-change
         -T --initial-tab
         -L LABEL
           --label=LABEL
         --left-column
         -d --minimal
         -N --new-file
         --new-line-format=ARG
         --old-line-format=ARG
         --paginate
         -n --rcs
         -s --report-identical-files
         -p
         --show-c-function
         -y --side-by-side
         -F REGEXP
         --show-function-line=REGEXP
         -H --speed-large-files
         --suppress-common-lines
         -a --text
         --unchanged-group-format=ARG
         -u
           -U NLINES
           --unified[=LINES]
         -V ARG
         -W COLUMNS
           --width=COLUMNS
     

Following are the GNU diff options most frequently used with cvs diff.


Node: edit, Next: , Previous: diff, Up: Commands And Options

edit

Synopsis: edit [OPTIONS] [FILES]

Signals that you are about to begin editing a watched file or files. Also adds you as a temporary watcher to the file's watch list (you'll be removed when you do cvs unedit). (See also watch, watchers, unedit, and editors.)

Options:


Node: editors, Next: , Previous: edit, Up: Commands And Options

editors

Synopsis: editors [OPTIONS] [FILES]

Shows who is currently editing a watched file. (See also watch, watchers, edit, and unedit.)

Options:


Node: export, Next: , Previous: editors, Up: Commands And Options

export

Synopsis: export [OPTIONS] PROJECT(S)

Exports files from the repository to create a project tree that is not a working copy (has no CVS/ administrative subdirectories). Useful mainly for packaging distributions.

Options:


Node: gserver, Next: , Previous: export, Up: Commands And Options

gserver

Synopsis: gserver

This is the GSSAPI (Generic Security Services API) server. This command is not normally run directly by users. Instead, it is started up on the server side when a user connects from a client with the :gserver: access method:

     cvs -d :gserver:floss.red-bean.com:/usr/local/newrepos checkout myproj
     

GSSAPI provides, among other things, Kerberos Version 5; for Kerberos Version 4, use :kserver:.

Setting up and using a GSSAPI library on your machines is beyond the scope of this book. (See the node GSSAPI Authenticated in the Cederqvist manual for some useful hints, however.)

Options: None.


Node: history, Next: , Previous: gserver, Up: Commands And Options

history

Synopsis: history [OPTIONS] [FILENAME_SUBSTRING(S)]

Shows a history of activity in the repository. Specifically, this option shows records of checkouts, commits, rtags, updates, and releases. By default, the option shows checkouts (but see the -x option). This command won't work if there's no CVSROOT/history file in the repository.

The history command differs from other CVS commands in several ways. First, it must usually be given options to do anything useful (and some of those options mean different things for history than they do elsewhere in CVS). Second, instead of taking full file names as arguments, it takes one or more substrings to match against file names (all records matching at least one of those substrings are retrieved). Third, history's output looks a lot like line noise until you learn to read it, so I'll explain the output format in a special section, after the options. (See also log.)

Options:

History Output

The output of the history command is a series of lines; each line represents one "history event" and starts with a single code letter indicating what type of event it is. For example:

     floss$ cvs history -D yesterday -x TMO
     M 08/21 20:19 +0000 jrandom 2.2              baar       myproj == <remote>
     M 08/22 04:18 +0000 jrandom 1.2              README     myproj == <remote>
     O 08/22 05:15 +0000 jrandom myproj =myproj= ~/src/*
     M 08/22 05:33 +0000 jrandom 2.18             README.txt myproj == ~/src/myproj
     O 08/22 14:25 CDT jrandom myproj =myproj= ~/src/*
     O 08/22 14:26 CDT jrandom [99.08.23.19.26.03] myproj =myproj= ~/src/*
     O 08/22 14:28 CDT jrandom [Exotic_Greetings-branch] myproj =myproj= ~/src/*
     

The code letters are the same as for the -x option just described. Following the code letter is the date of the event (expressed in UTC/GMT time, unless the -z option is used), followed by the user responsible for the event.

After the user might be a revision number, tag, or date, but only if such is appropriate for the event (date or tag will be in square brackets and formatted as shown in the preceding example). If you commit a file, it shows the new revision number; if you check out with -D or -r, the sticky date or tag is shown in square brackets. For a plain checkout, nothing extra is shown.

Next comes the name of the file in question, or module name if the event is about a module. If the former, the next two things are the module/project name and the location of the working copy in the user's home directory. If the latter, the next two things are the name of the module's checked-out working copy (between two equal signs), followed by its location in the user's home directory. (The name of the checked-out working copy may differ from the module name if the -d flag is used with checkout.)


Node: import, Next: , Previous: history, Up: Commands And Options

import

Synopsis: import [OPTIONS] REPOSITORY VENDOR_TAG RELEASE_TAG(S)

Imports new sources into the repository, either creating a new project or creating a new vendor revision on a vendor branch of an existing project. (See Advanced CVS for a basic explanation of vendor branches in import, which will help you to understand the following.)

It's normal to use import to add many files or directories at once or to create a new project. To add single files, you should use add.

Options:


Node: init, Next: , Previous: import, Up: Commands And Options

init

Synopsis: init NEW_REPOSITORY

Creates a new repository (that is, a root repository in which many different projects are stored). You will almost always want to use the global -d option with this, as in

     floss$ cvs -d /usr/local/yet_another_repository init
     

because even if you have a CVSROOT environment variable set, it's probably pointing to an existing repository, which would be useless and possibly dangerous in the context of this command. (See Repository Administration for additional steps that you should take after initializing a new repository.)

Options: None.


Node: kserver, Next: , Previous: init, Up: Commands And Options

kserver

Synopsis: kserver

This is the Kerberos server. (If you have Kerberos libraries Version 4 or below - Version 5 just uses GSSAPI, see gserver.) This command is not normally run directly by users but is instead started up on the server side when a user connects from a client with the :kserver: access method:

     cvs -d :kserver:floss.red-bean.com:/usr/local/newrepos checkout myproj
     

Setting up and using Kerberos on your machine is beyond the scope of this book. (However, see the node Kerberos Authenticated in the Cederqvist manual for some useful hints.)

Options: None.


Node: log, Next: , Previous: kserver, Up: Commands And Options

log

Synopsis: log [OPTIONS] [FILES]

Shows log messages for a project, or for files within a project. The output of log is not quite in the same style as the output of other CVS commands, because log is based on an older RCS program (rlog). Its output format gives a header, containing various pieces of non-revision-specific information about the file, followed by the log messages (arranged by revision). Each revision shows not merely the revision number and log message, but also the author and date of the change and the number of lines added or deleted. All times are printed in UTC (GMT), not local time.

Because log output is per file, a single commit involving multiple files may not immediately appear as a conceptually atomic change. However, if you read all of the log messages and dates carefully, you may be able to reconstruct what happened. (For information about a tool that can reformat multifile log output into a more readable form, see cvs2cl - Generate GNU-Style ChangeLogs in Third-Party Tools for details.) (See also history.)

Options:

As you read over the following filtering options, it may not be completely clear how they behave when combined. A precise description of log's behavior is that it takes the intersection of the revisions selected by -d, -s, and -w, intersected with the union of those selected by -b and -r.


Node: login, Next: , Previous: log, Up: Commands And Options

login

Synopsis: login

Contacts a CVS server and confirms authentication information for a particular repository. This command does not affect either the working copy or the repository; it just confirms a password (for use with the :pserver: access method) with a repository and stores the password for later use in the .cvspass file in your home directory. Future commands accessing the same repository with the same username will not require you to rerun login, because the client-side CVS will just consult the .cvspass file for the password.

If you use this command, you should specify a repository using the pserver access method, like this

     floss$ cvs -d :pserver:[email protected]:/usr/local/newrepos
     

or by setting the CVSROOT environment variable.

If the password changes on the server side, you have to rerun login.

Options: None.


Node: logout, Next: , Previous: login, Up: Commands And Options

logout

Synopsis: logout

The opposite of login - removes the password for this repository from .cvspass.

Options: None.


Node: pserver, Next: , Previous: logout, Up: Commands And Options

pserver

Synopsis: pserver

This is the password-authenticating server. This command is not normally run directly by users but is started up from /etc/inetd.conf on the server side when a user connects from a client with the :pserver: access method. (See also the login and logout commands, and the file .cvspass in the Run Control Files section in this chapter. See Repository Administration for details on setting up a password-authenticating CVS server.)

Options: None.


Node: rdiff, Next: , Previous: pserver, Up: Commands And Options

rdiff

Synopsis: rdiff [OPTIONS] PROJECTS

Like the diff command, except it operates directly in the repository and, therefore, requires no working copy. This command is meant for obtaining the differences between one release and another of your project, in a format suitable as input to the patch program (perhaps so you can distribute patch files to users who want to upgrade).

The operation of the patch program is beyond the scope of this book. However, note that if the patch file contains diffs for files in subdirectories, you may need to use the -p option to patch to get it to apply the differences correctly. (See the patch documentation for more about this.) (See also diff.)

Options:


Node: release, Next: , Previous: rdiff, Up: Commands And Options

release

Synopsis: release [OPTIONS] DIRECTORY

Cancels a checkout (indicates that a working copy is no longer in use). Unlike most CVS commands that operate on a working copy, this one is not invoked from within the working copy but from directly above it (in its parent directory). You either have to set your CVSROOT environment variable or use the -d global option, as CVS will not be able to find out the repository from the working copy.

Using release is never necessary. Because CVS doesn't normally do locking, you can just remove your working copy.

However, if you have uncommitted changes in your working copy or you want your cessation of work to be noted in the CVSROOT/history file (see the history command), you should use release. CVS first checks for any uncommitted changes; if there are any, it warns you and prompts for continuation. Once the working copy is actually released, that fact is recorded in the repository's CVSROOT/history file.

Options:

If you created any new directories inside your working copy but did not add them to the repository, they are deleted along with the rest of the working copy, if you specified the -d flag.


Node: remove, Next: , Previous: release, Up: Commands And Options

remove

Synopsis: remove [OPTIONS] [FILES]

Removes a file from a project. Normally, the file itself is removed from disk when you invoke this command (but see -f). Although this command operates recursively by default, it is common to explicitly name the files being removed. Note the odd implication of the previous sentence: Usually, you run cvs remove on files that don't exist anymore in your working copy.

Although the repository is contacted for confirmation, the file is not actually removed until a subsequent commit is performed. Even then, the RCS file is not really removed from the repository; if it is removed from the trunk, it is just moved into an Attic/ subdirectory, where it is still available to exist on branches. If it is removed from a branch, its location is not changed, but a new revision with state dead is added on the branch. (See also add.)

Options:


Node: rtag, Next: , Previous: remove, Up: Commands And Options

rtag

Synopsis: rtag [OPTIONS] TAG PROJECT(S)

Tags a module directly in the repository (requires no working copy). You probably need to have your CVSROOT environment variable set or use the -d global option for this to work. (See also tag.)

Options:


Node: server, Next: , Previous: rtag, Up: Commands And Options

server

Synopsis: server

Starts up a CVS server. This command is never invoked by users (unless they're trying to debug the client/server protocol), so forget I even mentioned it.

Options: None.


Node: status, Next: , Previous: server, Up: Commands And Options

status

Synopsis: status [OPTIONS] [FILES]

Shows the status of files in the working copy.

Options:


Node: tag, Next: , Previous: status, Up: Commands And Options

tag

Synopsis: tag [OPTIONS] TAG [FILES]

Attaches a name to a particular revision or collection of revisions for a project. Often called "taking a snapshot" of the project. This command is also used to create branches in CVS. (See the -b option - see also rtag.)

Options:


Node: unedit, Next: , Previous: tag, Up: Commands And Options

unedit

Synopsis: unedit [OPTIONS] [FILES]

Signals to watchers that you are done editing a file. (See also watch, watchers, edit, and editors.)

Options:


Node: update, Next: , Previous: unedit, Up: Commands And Options

update

Synopsis: update [OPTIONS] [FILES]

Merges changes from the repository into your working copy. As a side effect, it indicates which files in your working copy are modified (but if the -Q global option is passed, these indications won't be printed). (See also checkout.)

Options:


Node: watch, Next: , Previous: update, Up: Commands And Options

watch

Synopsis: watch on|off|add|remove [OPTIONS] [FILES]

Sets a watch on one or more files. Unlike most CVS commands, watch requires a further subcommand to do something useful. (See also watchers, edit, editors, and unedit, and users.)

Subcommands:

Options (for use with any watch subcommand). All three options have the same meanings as for edit:


Node: watchers, Previous: watch, Up: Commands And Options

watchers

Synopsis: watchers [OPTIONS] [FILES]

Shows who's watching what files.

Options - these options mean the same thing here as for edit:


Node: Keyword Substitution (RCS Keywords), Next: , Previous: Commands And Options, Up: CVS Reference

Keyword Substitution (RCS Keywords)

CVS can perform certain textual substitutions in files, allowing you to keep some kinds of information automatically up to date in your files. All of the substitutions are triggered by a certain keyword pattern, surrounded by dollar signs. For example,

     $Revision$
     

in a file expands to something like

     $Revision: 1.5 $
     

and CVS continues to keep the revision string up to date as new revisions are committed.


Node: Controlling Keyword Expansion, Next: , Up: Keyword Substitution (RCS Keywords)

Controlling Keyword Expansion

By default, CVS performs keyword expansion unless you tell it to stop. You can permanently suppress keyword expansion for a file with the -k option when you add the file to the project, or you can turn it off later by invoking admin with -k. The -k option offers several different modes of keyword control; usually you want mode o or b, for example:

     floss$ cvs add -ko chapter-9.sgml
     

This command added chapter-9.sgml to the project with keyword expansion turned off. It sets the file's default keyword expansion mode to o, which means no substitution. (Actually, the "o" stands for "old", meaning to substitute the string with its old value, which is the same as substituting it for itself, resulting in no change. I'm sure this logic made sense to somebody at the time.)

Each file's default keyword mode is stored in the repository. However, each working copy can also have its own local keyword substitution mode - accomplished with the -k options to checkout or update. You can also have a mode in effect for the duration of just one command, with the -k option to diff.

Here are all the possible modes, presented with the -k option prepended (as one would type at a command line). Any of these options can be used as either the default or local keyword substitution mode for a file:


Node: List Of Keywords, Previous: Controlling Keyword Expansion, Up: Keyword Substitution (RCS Keywords)

List Of Keywords

These are all the dollar-sign-delimited keywords that CVS recognizes. Following is a list of the keyword, a brief description, and an example of its expanded form:


Node: Repository Administrative Files, Next: , Previous: Keyword Substitution (RCS Keywords), Up: CVS Reference

Repository Administrative Files

The repository's administrative files are stored in the CVSROOT subdirectory of the repository. These files control various aspects of CVS's behavior (in that repository only, of course).

You may also want to refer to the discussion of administrative files in Repository Administration, which includes examples.


Node: Storage And Editing, Next: , Up: Repository Administrative Files

Storage And Editing

Generally, the administrative files are kept under revision control just like any other file in the repository (the exceptions are noted). However, unlike other files, checked-out copies of the administrative files are stored in the repository, right next to their corresponding RCS files in the CVSROOT subdirectory. It is these checked-out copies which actually govern CVS's behavior.

The normal way to modify the administrative files is to check out a working copy of the CVSROOT module, make your changes, and commit. CVS updates the checked-out copies in the repository automatically. (See checkoutlist.) In an emergency, however, it is also possible to edit the checked-out copies in the repository directly.


Node: Shared Syntax, Next: , Previous: Storage And Editing, Up: Repository Administrative Files

Shared Syntax

In all of the administrative files, a # at the beginning of a line signifies a comment; that line is ignored by CVS. A backslash preceding a newline quotes the newline out of existence.

Some of the files (commitinfo, loginfo, taginfo, and rcsinfo) share more syntactic conventions as well. In these files, on the left of each line is a regular expression (which is matched against a file or directory name), and the rest of the line is a program, possibly with arguments, which is invoked if something is done to a file matching the regular expression. The program is run with its working directory set to the top of the repository.

In these files, there are two special regular expressions that may be used: ALL and DEFAULT. ALL matches any file or directory, whether or not there is some other match for it, and DEFAULT matches only if nothing else matched.


Node: Shared Variables, Next: , Previous: Shared Syntax, Up: Repository Administrative Files

Shared Variables

The info files also allow certain variables to be expanded at runtime. To expand a variable, precede it with a dollar sign (and put it in curly braces just to be safe). Here are the variables CVS knows about:


Node: User Variables, Next: , Previous: Shared Variables, Up: Repository Administrative Files

User Variables

Users can also set their own variables when they run any CVS command. (See the -s global option.) These variables can be accessed in the *info files by preceding them with an equal sign, as in ${=VAR}.


Node: checkoutlist, Next: , Previous: User Variables, Up: Repository Administrative Files

checkoutlist

This contains a list of files for which checked-out copies should be kept in the repository. Each line gives the file name and an error message for CVS to print if, for some reason, the file cannot be checked out in the repository:

     FILENAME  ERROR_MESSAGE
     

Because CVS already knows to keep checked-out copies of the existing administrative files, they do not need to be listed in checkoutlist. Specifically, the following files never need entries in checkoutlist: loginfo, rcsinfo, editinfo, verifymsg, commitinfo, taginfo, ignore, checkoutlist, cvswrappers, notify, modules, readers, writers, and config.


Node: commitinfo, Next: , Previous: checkoutlist, Up: Repository Administrative Files

commitinfo

Specifies programs to run at commit time, based on what's being committed. Each line consists of a regular expression followed by a command template:

     REGULAR_EXPRESSION PROGRAM [ARGUMENTS]
     

The PROGRAM is passed additional arguments following any arguments you may have written into the template. These additional arguments are the full path to the repository, followed by the name of each file about to be committed. These files can be examined by PROGRAM; their contents are the same as those of the working copy files about to be committed. If PROGRAM exits with nonzero status, the commit fails; otherwise, it succeeds. (See also Shared Syntax earlier in this chapter.)


Node: config, Next: , Previous: commitinfo, Up: Repository Administrative Files

config

Controls various global (non-project-specific) repository parameters. The syntax of each line is

     ParameterName=yes|no
     

except for the LockDir parameter, which takes an absolute pathname as argument.

The following parameters are supported:


Node: cvsignore, Next: , Previous: config, Up: Repository Administrative Files

cvsignore

Ignores certain files when doing updates, imports, or releases. By default, CVS already ignores some kinds of files. (For a full list, see the -I option to import, earlier in this chapter.) You can add to this list by putting additional file names or wildcard patterns in the cvsignore file. Each line gives a file name or pattern, for example:

     README.msdos
     *.html
     blah?.out
     

This causes CVS to ignore any file named README.msdos, any file ending in .html, and any file beginning with blah and ending with .out. (Technically, you can name multiple files or patterns on each line, separated by whitespace, but it is more readable to keep them to one per line. The whitespace separation rule does, unfortunately, mean that there's no way to specify a space in a file name, except to use wildcards.)

A ! anywhere in the list cancels all previous entries. (See $CVSIGNORE in the section Environment Variables in this chapter for a fuller discussion of ignore processing.)


Node: cvswrappers, Next: , Previous: cvsignore, Up: Repository Administrative Files

cvswrappers

Specifies certain filtering behaviors based on file name. Each line has a file-globbing pattern (that is, a file name or file wildcards), followed by an option indicating the filter type and an argument for the option.

Options:

Here is an example cvswrappers file:

     *.blob    -m COPY
     *.blink   -k o
     

This cvswrappers file says to not attempt merges on files ending in .blob and suppress keyword substitution for files ending in .blink. (See also the file .cvswrappers in the Run Control Files section in this chapter.)


Node: editinfo, Next: , Previous: cvswrappers, Up: Repository Administrative Files

editinfo

This file is obsolete. Very.


Node: history file, Next: , Previous: editinfo, Up: Repository Administrative Files

history file

Stores an ever-accumulating history of activity in the repository, for use by the cvs history command. To disable this feature, simply remove the history file. If you don't remove the file, you should probably make it world-writeable to avoid permission problems later.

The contents of this file do not modify CVS's behavior in any way (except for the output of cvs history, of course).


Node: loginfo, Next: , Previous: history file, Up: Repository Administrative Files

loginfo

Specifies programs to run on the log message for each commit, based on what's being committed. Each line consists of a regular expression followed by a command template:

     REGULAR_EXPRESSION PROGRAM [ARGUMENTS]
     

The PROGRAM is passed the log message on its standard input.

Several special codes are available for use in the arguments: %s expands to the names of the files being committed, %V expands to the old revisions from before the commit, and %v expands to the new revisions after the commit. When there are multiple files involved, each element of the expansion is separated from the others by whitespace. For example, in a commit involving two files, %s might expand into hello.c README.txt, and %v into 1.17 1.12.

You may combine codes inside curly braces, in which case, each unit of expansion is internally separated by commas and externally separated from the other units by whitespace. Continuing the previous example, %{sv} expands into hello.c,1.17 README.txt,1.12.

If any % expansion is done at all, the expansion is prefixed by the path to the project subdirectory (relative to the top of the repository). So that last expansion would actually be:

     myproj  hello.c,1.17  README.txt,1.12
     

If PROGRAM exits with nonzero status, the commit fails; otherwise, it succeeds. (See also the Shared Syntax section in this chapter.)


Node: modules, Next: , Previous: loginfo, Up: Repository Administrative Files

modules

This maps names to repository directories. The general syntax of each line is:

     MODULE [OPTIONS] [&OTHERMODULE...] [DIR] [FILES]
     

DIR need not be a top-level project directory - it could be a subdirectory. If any FILES are specified, the module consists of only those files from the directory.

An ampersand followed by a module name means to include the expansion of that module's line in place.

Options:


Node: notify, Next: , Previous: modules, Up: Repository Administrative Files

notify

Controls how the notifications for watched files are performed. (You may want to read up on the watch and edit commands, or see the section Watches (CVS As Telephone) in Advanced CVS.) Each line is of the usual form:

REGULAR_EXPRESSION PROGRAM [ARGUMENTS]

A %s in ARGUMENTS is expanded to the name of the user to be notified, and the rest of the information regarding the notification is passed to PROGRAM on standard input (usually this information is a brief message suitable for emailing to the user). (See the section Shared Syntax earlier in this chapter.)

As shipped with CVS, the notify file has one line

     ALL mail %s -s "CVS notification"
     

which is often all you need.


Node: passwd, Next: , Previous: notify, Up: Repository Administrative Files

passwd

Provides authentication information for the pserver access method. Each line is of the form:

USER:ENCRYPTED_PASSWORD[:SYSTEM_USER]

If no SYSTEM_USER is given, USER is taken as the system username.


Node: rcsinfo, Next: , Previous: passwd, Up: Repository Administrative Files

rcsinfo

Specifies a form that should be filled out for log messages that are written with an interactive editor. Each line of rcsinfo looks like:

REGULAR_EXPRESSION FILE_CONTAINING_TEMPLATE

This template is brought to remote working copies at checkout time, so if the template file or rcsinfo file changes after checkout, the remote copies won't know about it and will continue to use the old template. (See also the section Shared Syntax in this chapter.)


Node: taginfo, Next: , Previous: rcsinfo, Up: Repository Administrative Files

taginfo

Runs a program at tag time (usually done to check that the tag name matches some pattern). Each line is of the form:

REGULAR_EXPRESSION PROGRAM

The program is handed a set group of arguments. In order, they are the tag name, the operation (see below), the repository, and then as many file name/revision-number pairs as there are files involved in the tag. The file/revision pairs are separated by whitespace, like the rest of the arguments.

The operation is one of add, mov, or del (mov means the -F option to tag was used).

If PROGRAM exits with nonzero status, the tag operation will not succeed. (See also the section Shared Syntax in this chapter.)


Node: users, Next: , Previous: taginfo, Up: Repository Administrative Files

users

Maps usernames to email addresses. Each line looks like:

USERNAME:EMAIL_ADDRESS

This sends watch notifications to EMAIL_ADDRESS instead of to USERNAME at the repository machine. (All this really does is control the expansion of %s in the notify file.) If EMAIL_ADDRESS includes whitespace, make sure to surround it with quotes.

If user aliasing is being used in the passwd file, the username that will be matched is the CVS username (the one on the left), not the system username (the one on the right, if any).


Node: val-tags, Next: , Previous: users, Up: Repository Administrative Files

val-tags

Caches valid tag names for speedier lookups. You should never need to edit this file, but you may need to change its permissions, or even ownership, if people are having trouble retrieving or creating tags.


Node: verifymsg, Previous: val-tags, Up: Repository Administrative Files

verifymsg

Used in conjunction with rcsinfo to verify the format of log messages. Each line is of the form:

REGULAR_EXPRESSION PROGRAM [ARGUMENTS]

The full path to the current log message template (see rcsinfo earlier in this chapter) is appended after the last argument written in the verifymsg file. If PROGRAM exits with nonzero status, the commit fails.


Node: Run Control Files, Next: , Previous: Repository Administrative Files, Up: CVS Reference

Run Control Files

There are a few files on the client (working copy) side that affect CVS's behavior. In some cases, they are analogs of repository administrative files; in other cases, they control behaviors that are only appropriate for the client side.

.cvsrc

Specifies options that you want to be used automatically with every CVS command. The format of each line is

COMMAND OPTIONS

where each COMMAND is an unabbreviated CVS command, such as checkout or update (but not co or up). The OPTIONS are those that you want to always be in effect when you run that command. Here is a common .cvsrc line:

     update -d -P
     

To specify global options, simple use cvs as the COMMAND.

.cvsignore

Specifies additional ignore patterns. (See cvsignore in the Repository Administrative Files section in this chapter for the syntax.)

You can have a .cvsignore file in your home directory, which will apply every time you use CVS. You can also have directory-specific ones in each project directory of a working copy (these last only apply to the directory where the .cvsignore is located, and not to its subdirectories).

(See $CVSIGNORE in the section Environment Variables in this chapter, for a fuller discussion of ignore processing.)

.cvspass

Stores passwords for each repository accessed via the pserver method. Each line is of the form:

REPOSITORY LIGHTLY_SCRAMBLED_PASSWORD

The password is essentially stored in cleartext - a very mild scrambling is done to prevent accidental compromises (such as the root user unintentionally looking inside the file). However, this scrambling will not deter any serious-minded person from gaining the password if they get access to the file.

The .cvspass file is portable. You can copy it from one machine to another and have all of your passwords at the new machine, without ever having run cvs login there. (See also the login and logout commands.)

.cvswrappers

This is a client side version of the cvswrappers file. (See the Repository Administrative Files section in this chapter.) There can be a .cvswrappers file in your home directory and in each directory of a working copy directory, just as with .cvsignore.


Node: Working Copy Files, Next: , Previous: Run Control Files, Up: CVS Reference

Working Copy Files

The CVS/ administrative subdirectories in each working copy contain some subset of the following files.

Here is what each file or directory does:

CVS/Base/  (directory)

If watches are on, cvs edit stores the original copy of the file in this directory. That way, cvs unedit can work even if it can't reach the server.

CVS/Baserev

Lists the revision for each file in Base/. Each line looks like this:

     FILE/REVISION/EXPANSION
     

EXPANSION is currently ignored to allow for, well, future expansion.

CVS/Baserev.tmp

This is the temp file for the preceding. (See CVS/Notify.tmp or CVS/Entries.Backup later on for further explanation.)

CVS/Checkin.prog

Records the name of the program specified by the -i option in the modules file. (See the Repository Administrative Files section in this chapter.)

CVS/Entries

Stores the revisions for the files in this directory. Each line is of the form:

     [CODE_LETTER]/FILE/REVISION/DATE/[KEYWORD_MODE]/[STICKY_OPTION]
     

If CODE_LETTER is present, it must be D for directory (anything else is silently ignored by CVS, to allow for future expansion), and the rest of the items on the line are absent.

This file is always present.

CVS/Entries.Backup

This is just a temp file. If you're writing some program to modify the Entries file, have it write the new contents to Entries.backup and then atomically rename it to Entries.

CVS/Entries.Log

This is basically a patch file to be applied to Entries after Entries has been read (this is an efficiency hack, to avoid having to rewrite all of Entries for every little change). The format is the same as Entries, except that there is an additional mandatory code letter at the front of every line: An A means this line is to be added to what's in Entries; R means it's to be removed from what's in Entries. Any other letters should be silently ignored, to allow for future expansion.

CVS/Entries.Static

If this file exists, it means only part of the directory was fetched from the repository, and CVS will not create additional files in that directory. This condition can usually be cleared by using update -d.

CVS/Notify

Stores notifications that have not yet been sent to the server.

CVS/Notify.tmp

Temp file for Notify. The usual procedure for modifying Notify is to write out Notify.tmp and then rename it to Notify.

CVS/Repository

The path to the project-specific subdirectory in the repository. This may be an absolute path, or it may be relative to the path given in Root.

This file is always present.

CVS/Root

This is the repository; that is, the value of the $CVSROOT environment variable or the argument to the -d global option.

This file is always present.

CVS/Tag

If there is a sticky tag or date on this directory, it is recorded in the first line of the file. The first character is a single letter indicating the type of tag: T, N, or D, for branch tag, nonbranch tag, or date respectively. The rest of the line is the tag or date itself.

CVS/Template

Contains a log message template as specified by the rcsinfo file. (See Repository Administrative Files earlier in this chapter.) It is relevant only for remote working copies; working copies on the same machine as the repository just read rcsinfo directly.

CVS/Update.prog

Records the name of the program specified by the -u option in the modules file. (See the Repository Administrative Files section in this chapter.)


Node: Environment Variables, Previous: Working Copy Files, Up: CVS Reference

Environment Variables

These are all the environment variables that affect CVS.


Node: $COMSPEC, Next: , Up: Environment Variables

$COMSPEC

This is used in OS/2 only; it specifies the name of the command interpreter. It defaults to CMD.EXE.


Node: $CVS_CLIENT_LOG, Next: , Previous: $COMSPEC, Up: Environment Variables

$CVS_CLIENT_LOG

Used for debugging the client/server protocol. Set this variable to a file name before you start using CVS; all traffic to the server will be logged in filename.in, and everything from the server will be logged in filename.out.


Node: $CVS_CLIENT_PORT, Next: , Previous: $CVS_CLIENT_LOG, Up: Environment Variables

$CVS_CLIENT_PORT

Used in Kerberos-authenticated client/server access.


Node: $CVSEDITOR, Next: , Previous: $CVS_CLIENT_PORT, Up: Environment Variables

$CVSEDITOR

Specifies the program to use to edit log messages for commits. This overrides $EDITOR and $VISUAL.


Node: $CVSIGNORE, Next: , Previous: $CVSEDITOR, Up: Environment Variables

$CVSIGNORE

A whitespace-separated list of file names and wildcard patterns that CVS should ignore. (See also the -I option to the import command.)

This variable is appended last to the ignore list during a command. The list is built up in this order: CVSROOT/cvsignore, the .cvsignore file in your home directory, the $CVSIGNORE variable, any -I command option, and finally the contents of .cvsignore files in the working copy used as CVS works in each directory. A ! as the ignore specification at any point nullifies the entire ignore list built up to that point.


Node: $CVS_IGNORE_REMOTE_ROOT, Next: , Previous: $CVSIGNORE, Up: Environment Variables

$CVS_IGNORE_REMOTE_ROOT

Recently obsolete.


Node: $CVS_PASSFILE, Next: , Previous: $CVS_IGNORE_REMOTE_ROOT, Up: Environment Variables

$CVS_PASSFILE

Tells CVS to use some file other than .cvspass in your home directory. (See the file .cvspass in the Run Control Files section in this chapter.)


Node: $CVS_RCMD_PORT, Next: , Previous: $CVS_PASSFILE, Up: Environment Variables

$CVS_RCMD_PORT

Specifies the port number to contact the rcmd daemon on the server side. (This variable is currently ignored in Unix CVS clients.)


Node: $CVSREAD, Next: , Previous: $CVS_RCMD_PORT, Up: Environment Variables

$CVSREAD

Makes working copy files read-only on checkout and update, if possible (the default is for them to be read-write). (See also the -r global option.)


Node: $CVSROOT, Next: , Previous: $CVSREAD, Up: Environment Variables

$CVSROOT

This specifies the path to the repository. This is overridden with the -d global option and by the ambient repository for a given working copy. The path to the repository may be preceded by an access method, username, and host, according to the following syntax:

     [[:METHOD:][[USER@]HOST]:]/REPOSITORY_PATH
     

See the -d global option, in the section Global Options near the beginning of this chapter, for a list of valid methods.


Node: $CVS_RSH, Next: , Previous: $CVSROOT, Up: Environment Variables

$CVS_RSH

Specifies an external program for connecting to the server when using the :ext: access method. Defaults to rsh, but ssh is a common replacement value.


Node: $CVS_SERVER, Next: , Previous: $CVS_RSH, Up: Environment Variables

$CVS_SERVER

Program to invoke for CVS on the server side. Defaults to cvs, of course.


Node: $CVS_SERVER_SLEEP, Next: , Previous: $CVS_SERVER, Up: Environment Variables

$CVS_SERVER_SLEEP

Delays the start of the server child process by the specified number of seconds. This is used only for debugging, to allow time for a debugger to connect.


Node: $CVSUMASK, Next: , Previous: $CVS_SERVER_SLEEP, Up: Environment Variables

$CVSUMASK

Permissions for files and directories in the repository. (You probably don't want to set this; it doesn't work for client/server anyway.)


Node: $CVSWRAPPERS, Next: , Previous: $CVSUMASK, Up: Environment Variables

$CVSWRAPPERS

A whitespace-separated list of file names, wildcards, and arguments that CVS should use as wrappers. (See cvswrappers in the Repository Administrative Files section in this chapter for more information.)


Node: $EDITOR, Next: , Previous: $CVSWRAPPERS, Up: Environment Variables

$EDITOR

(See $CVSEDITOR.)


Node: $HOME %HOMEDRIVE% %HOMEPATH%, Next: , Previous: $EDITOR, Up: Environment Variables

$HOME %HOMEDRIVE% %HOMEPATH%

Where the .cvsrc, .cvspass, and other such files are found (under Unix, only $HOME is used). In Windows NT, %HOMEDRIVE% and %HOMEPATH% might be set for you; in Windows 95, you may need to set them for yourself.

In Windows 95, you may also need to set %HOME%. Make sure not to give it a trailing backslash; use set HOME=C: or something similar.


Node: $PATH, Next: , Previous: $HOME %HOMEDRIVE% %HOMEPATH%, Up: Environment Variables

$PATH

Obsolete.


Node: $TEMP $TMP $TMPDIR, Next: , Previous: $PATH, Up: Environment Variables

$TEMP $TMP $TMPDIR

Where temporary files go (the server uses TMPDIR; Windows NT uses TMP). Setting this on the client side will not affect the server. Setting this on either side will not affect where CVS stores temporary lock files. (See config in the Repository Administrative Files section in this chapter for more information.)


Node: $VISUAL, Previous: $TEMP $TMP $TMPDIR, Up: Environment Variables

$VISUAL

(See $CVSEDITOR.)


Node: Third-Party Tools, Next: , Previous: CVS Reference, Up: Top

Third-Party Tools

Many people have written programs to augment CVS. I call these third-party tools because they have their own maintainers, separate from the CVS development team. Most of these programs are not distributed with CVS, although some are. This chapter covers third-party tools that I have found useful, but that are not distributed with CVS.

Although there are some very popular and widely used non-command-line or non-Unix interfaces to CVS (download sites are listed in Repository Administration), this chapter does not discuss most of them. Their popularity makes it easy to find out more about them from mailing lists and newsgroups. One exception to this is the Emacs pcl-cvs interface, which is very useful, but sometimes tricky to install.


Node: pcl-cvs -- An Emacs Interface To CVS, Next: , Up: Third-Party Tools

pcl-cvs - An Emacs Interface To CVS

Depends on: Emacs, Elib

URLs:

Authors: Per Cederqvist and Stefan Monnier (current maintainer)

pcl-cvs is one of two Emacs/CVS interfaces. The other is the native VC (Version Control) interface built into Emacs. I prefer pcl-cvs because it was written exclusively for CVS and, therefore, works smoothly with the CVS way of doing things. VC, on the other hand, was designed to work with several different back-end version control systems - RCS and SCCS, as well as CVS - and is not really "tuned" for CVS. For example, VC presents a file-based rather than a directory-based interface to revision control.

The advantages of pcl-cvs are strong enough that many people choose to download and install it rather than use VC. Unfortunately, pcl-cvs has two disadvantages: It can be a bit tricky to install (much of this section is devoted to overcoming possible installation hurdles), and its recent releases are a bit unstable.

The latter problem is likely to be temporary, but it does raise the question of which version to use. Stefan Monnier has just recently taken over the pcl-cvs maintainership; the latest release, 2.9.6 (available from the first URL in the preceding list), was a bit bumpy when I tried it. No doubt the problems will be smoothed out soon, but in the meantime, you might want to use an older version. Because I've been using Version 1.05 daily for a long time now and it's performed quite well, I'm going to document that version here. Fortunately, the installation procedures don't change much from version to version. If you decide to use pcl-cvs, I suggest that you check Monnier's download site for a version newer than 2.9.6; if there is one, try it out before regressing all the way to 1.05.

You'll notice that two URLs are given for Version 1.05. The first is Per Cederqvist's site, where he still makes available an old archive of pcl-cvs. However, since I'm not sure how much longer his archive will stay around, I'm also making the 1.05 distribution available from ftp.red-bean.com.

Although the rest of these instructions use examples from a Version 1.05 distribution, they should apply to later versions as well.


Node: Installing pcl-cvs, Next: , Up: pcl-cvs -- An Emacs Interface To CVS

Installing pcl-cvs

If you don't normally deal with Emacs installation and site-maintenance issues, the pcl-cvs installation procedure may seem a bit daunting. A little background on how Emacs works may help.

Most higher-level Emacs features are written in a language called "Emacs Lisp" (Emacs itself is essentially an interpreter for this language). People add new features to Emacs by distributing files of Emacs Lisp code. pcl-cvs is written in this language, and it depends on a library of useful, generic Emacs Lisp functions called Elib (also written in part by Per Cederqvist, but distributed separately from pcl-cvs).

Elib is not included in the regular Emacs distribution (at least not FSF Emacs; I don't know about XEmacs), so you may have to download and install it yourself before you can use pcl-cvs. You can get it from ftp://ftp.lysator.liu.se/pub/emacs/elib-1.0.tar.gz. Installation instructions are contained within the package.

Once Elib is installed, you're ready to build and install pcl-cvs. These instructions applies both to Version 1.05 and the 2.x series (although you should check the NEWS and INSTALL files in newer distributions to see what's changed).

First, unpack pcl-cvs (I'm using Version 1.05, but it could just as easily have been 2.9.6)

     floss$ zcat pcl-cvs-1.05.tar.gz | tar xvf -
     pcl-cvs-1.05/
     pcl-cvs-1.05/README
     pcl-cvs-1.05/NEWS
     pcl-cvs-1.05/INSTALL
     pcl-cvs-1.05/ChangeLog
     pcl-cvs-1.05/pcl-cvs.el
     pcl-cvs-1.05/pcl-cvs.texinfo
     pcl-cvs-1.05/compile-all.el
     pcl-cvs-1.05/pcl-cvs-lucid.el
     pcl-cvs-1.05/pcl-cvs-startup.el
     pcl-cvs-1.05/pcl-cvs.info
     pcl-cvs-1.05/Makefile
     pcl-cvs-1.05/texinfo.tex
     

and go into the source tree's top level:

     floss$ cd pcl-cvs-1.05/
     

A Makefile is supplied there. According to the instructions in the INSTALL file, you're supposed to edit a few paths at the top of the Makefile and then run:

     floss$ make install
     

If that works, great. However, this sometimes results in an error (the pcl-cvs code itself is very portable, but its installation procedures sometimes are not). Do this if you get an error:

     floss$ make clean
     floss$ make
     

If all goes well, these commands accomplish a significant part of the installation by byte-compiling all of the Emacs Lisp files. (Byte-compiling converts a file of human-readable Emacs Lisp code - an .el file - into a more compact and efficient representation - an .elc file. Emacs can load and run an .elc file with better performance than it can a plain .el file.)

I'll proceed as though the byte-compilation stage has succeeded. If the byte compilation does not appear to succeed, don't worry: The .elc files are a luxury, not a necessity. They improve performance slightly, but you can run pcl-cvs from the raw .el files with no problem.

If the make install failed, the next step is to get the Emacs Lisp (whether .el or .elc) into a directory where Emacs can load it automatically. Emacs has a designated directory on the system for locally installed Lisp. To find this directory - it will have a file named default.el in it - check the following locations, in this order:

  1. /usr/share/emacs/site-lisp/
  2. /usr/local/share/emacs/site-lisp/
  3. /usr/lib/emacs/site-lisp/
  4. /usr/local/lib/emacs/site-lisp/

Once you've found your site-lisp directory, copy all of the Lisp files to it (you may have to be root to do this):

     floss# cp -f *.el *.elc /usr/share/emacs/site-lisp/
     

The last step is to tell Emacs about the entry points to pcl-cvs (the main one being the function cvs-update), so it will know to load the pcl-cvs code on demand. Because Emacs always reads the default.el file when it starts up, that's where you need to list the pcl-cvs entry points.

Fortunately, pcl-cvs provides the necessary content for default.el. Simply put the contents of pcl-cvs-startup.el into default.el (or perhaps into your .emacs, if you're just installing this for yourself) and restart your Emacs.

You may also want to copy the .info files into your info tree and add pcl-cvs to the table of contents in the dir file.


Node: Using pcl-cvs, Next: , Previous: Installing pcl-cvs, Up: pcl-cvs -- An Emacs Interface To CVS

Using pcl-cvs

Once installed, pcl-cvs is very easy to use. You just run the function cvs-update, and pcl-cvs brings up a buffer showing what files in your working copy have been modified or updated. From there, you can commit, do diffs, and so on.

Because cvs-update is the main entry point, I suggest that you bind it to a convenient key sequence before going any further. I have it bound to Ctrl+c v in my .emacs:

     (global-set-key "\C-cv" 'cvs-update)
     

Otherwise, you can run it by typing M-x cvs-update (also known as Esc-x cvs-update).

When invoked, cvs-update runs cvs update as if in the directory of the file in the current buffer - just as if you typed cvs update on the command line in that directory. Here's an example of what you might see inside Emacs:

     PCL-CVS release 1.05 from CVS release $Name:  $.
     Copyright (C) 1992, 1993 Per Cederqvist
     Pcl-cvs comes with absolutely no warranty; for details consult the manual.
     This is free software, and you are welcome to redistribute it under certain
     conditions; again, consult the TeXinfo manual for details.
      Modified ci README.txt
      Modified ci fish.c
     ---------- End ----
     

Two files have been locally modified (some versions of pcl-cvs show the subdirectories where the files are located). The next logical action is to commit one or both of the files, which is what the ci on each line means. To commit one of them, go to its line and type c. You are brought to a log message buffer, where you can type a log message as long as you want (real log message editing is the major advantage of pcl-cvs over the command line). Type Ctrl+c Ctrl+c when done to complete the commit.

If you want to commit multiple files at once, sharing a log message, first use m to mark the files that you intend to commit. An asterisk appears next to each file as you mark it:

     PCL-CVS release 1.05 from CVS release $Name:  $.
     Copyright (C) 1992, 1993 Per Cederqvist
     Pcl-cvs comes with absolutely no warranty; for details consult the manual.
     This is free software, and you are welcome to redistribute it under certain
     conditions; again, consult the TeXinfo manual for details.
     * Modified ci README.txt
     * Modified ci fish.c
     ---------- End ----
     

Now when you type c anywhere, it applies to all (and only) the marked files. Write the log message and commit them with Ctrl+c Ctrl+c as before.

You can also type d to run cvs diff on a file (or on marked files) and f to bring a file into Emacs for editing. Other commands are available; type Ctrl+h m in the update buffer to see what else you can do.


Node: Error Handling In pcl-cvs, Next: , Previous: Using pcl-cvs, Up: pcl-cvs -- An Emacs Interface To CVS

Error Handling In pcl-cvs

The pcl-cvs program has historically had an odd way of dealing with error and informational messages from CVS (although this may be corrected in the latest versions). When it encounters a message from CVS that it doesn't know about, it gets hysterical and throws you into a mail buffer, ready to send a pregenerated bug report to the author of pcl-cvs. Unfortunately, among the CVS messages that pcl-cvs may not know about are the ones associated with conflicting merges, which, although not common, certainly do occur from time to time.

If pcl-cvs suddenly dumps you into a mail buffer, don't panic. Read over the contents of the buffer carefully - the offending CVS output should be in there somewhere. If it looks like a merge, you can just get rid of the mail buffer and rerun cvs-update. It should now succeed, because CVS won't output any merge messages (because the merge has already taken place).

(Update: this problem appears to have been fixed in more recent versions of pcl-cvs, so very probably you can ignore this entire warning.)


Node: The Future Of pcl-cvs, Previous: Error Handling In pcl-cvs, Up: pcl-cvs -- An Emacs Interface To CVS

The Future Of pcl-cvs

Although I may be giving you the impression that pcl-cvs is barely maintained and a risky investment, the instability appears to be temporary. Stefan Monnier is a responsive maintainer (I contacted him several times during the writing of this chapter, and he always answered right away; he is already making headway on some of the bugs in Version 2.9.6). Very likely by the time this is published, you will be able to download Version 2.9.7 or later with confidence.

In fact, I just now got an encouraging email on this topic from Greg Woods, a former maintainer of pcl-cvs, reprinted here with his permission:

     From: [email protected] (Greg A. Woods)
     Subject: Re: pcl-cvs maintenance status, stability of recent "release"s?
     To: [email protected]
     Date: Sun, 29 Aug 1999 18:59:19 -0400 (EDT)
     
     [...]
     I've been using Stefan's releases for some time now, and indeed I have
     abandoned my own branch of it.
     
     He's done a lot of really good work on PCL-CVS and except for a few odd
     quirks in the 2.9.6 version I'm using daily now it is quite usable (and
     is approximately infinitely more usable with modern CVS than the one
     that was in the CVS distribution! ;-).
     
     I've added a pcl-cvs.README file to my FTP site to point out that the
     files there are indeed quite old (at least in Internet time! ;-) and to
     give a pointer to Stefan's FTP site too.
     
     [...]
     

In a later email, Greg said that the FSF is considering including pcl-cvs in their next release of Emacs (20.5), which would render most of the preceding installation advice obsolete. Sigh. It's hard to keep up with free software, sometimes.


Node: cvsutils -- General Utilities For Use With CVS, Next: , Previous: pcl-cvs -- An Emacs Interface To CVS, Up: Third-Party Tools

cvsutils - General Utilities For Use With CVS

Depends on: Perl

URLs:

Authors: Tom Tromey (original author) and Pavel Roskin (current maintainer)

The suite of small programs called cvsutils generally (although not always) performs offline operations in the CVS working copy. Offline operations are those that can be done without contacting the repository, while still leaving the working copy in a consistent state for the next time the repository is contacted. Offline behavior can be extremely handy when your network connection to the repository is slow or unreliable.

The cvsutils programs are listed below in approximate order of usefulness (according to my opinion), with the more useful ones coming first. Coincidentally, this also arranges them by safety. Safety is an issue because some of these utilities can, in their normal course of operation, cause you to lose local modifications or files from your working copy. Therefore, read the descriptions carefully before using these utilities.

This documentation is accurate as of Version 0.1.4. Be sure to read the README file in any later versions for more up-to-date information.


Node: cvsu, Next: , Up: cvsutils -- General Utilities For Use With CVS

cvsu

Danger level: None

Contacts repository: No

This does an offline cvs update by comparing the timestamps of files on disk with their timestamps recorded in CVS/Entries. You can thus tell which files have been locally modified and which files are not known to be under CVS control. Unlike cvs update, cvsu does not bring down changes from the repository.

Although it can take various options, cvsu is most commonly invoked without any options:

     floss$ cvsu
     ? ./bar
     ? ./chapter-10.html
     M ./chapter-10.sgml
     D ./out
     ? ./safe.sh
     D ./tools
     

The left-side codes are like the output of cvs update, except that D means directory. This example shows that chapter-10.sgml has been modified locally. What the example doesn't show is that cvsu ran instantly, whereas a normal cvs update would have required half a minute or so over my slow modem line. Run

     floss$ cvsu --help
     

to see a list of options.


Node: cvsdo, Next: , Previous: cvsu, Up: cvsutils -- General Utilities For Use With CVS

cvsdo

Danger level: Low to none

Contacts repository: No

This can simulate the working copy effects of cvs add and cvs remove, but without contacting the repository. Of course, you'd still have to commit the changes to make them take effect in the repository, but at least the add and remove commands themselves can be sped up this way. Here's how to use it

     floss$ cvsdo add FILENAME
     

or

     floss$ cvsdo remove FILENAME
     

To see a list of further options, run:

     floss$ cvsdo --help
     


Node: cvschroot, Next: , Previous: cvsdo, Up: cvsutils -- General Utilities For Use With CVS

cvschroot

Danger level: Low

Contacts repository: No

This deals with a repository move by tweaking the working copy to point to the new repository. This is useful when a repository is copied en masse to a new location. When that happens, none of the revisions are affected, but the CVS/Root (and possibly the CVS/Repository) file of every working copy must be updated to point to the new location. Using cvschroot is a lot faster than checking out a new copy. Another advantage is that it doesn't lose your local changes.

Usage:

     floss$ cvschroot NEW_REPOS
     

For example:

     floss$ cvschroot :pserver:[email protected]:/home/cvs/myproj
     


Node: cvsrmadm, Next: , Previous: cvschroot, Up: cvsutils -- General Utilities For Use With CVS

cvsrmadm

Danger level: Low to medium

Contacts repository: No

This removes all of the CVS/ administrative subdirectories in your working copy, leaving behind a tree similar to that created by cvs export.

Although you won't lose any local changes by using cvsrmadm, your working copy will no longer be a working copy.

Use with caution.


Node: cvspurge, Next: , Previous: cvsrmadm, Up: cvsutils -- General Utilities For Use With CVS

cvspurge

Danger level: Medium

Contacts repository: No

This removes all non-CVS-controlled files in your working copy. It does not undo any local changes to CVS-controlled files.

Use with caution.


Node: cvsdiscard, Next: , Previous: cvspurge, Up: cvsutils -- General Utilities For Use With CVS

cvsdiscard

Danger level: Medium to high

Contacts repository: Maybe

This is the complement of cvspurge. Instead of removing unknown files but keeping your local changes, cvsdiscard undoes any local changes (replacing those files with fresh copies from the repository), but keeps unknown files.

Use with extreme caution.


Node: cvsco, Next: , Previous: cvsdiscard, Up: cvsutils -- General Utilities For Use With CVS

cvsco

Danger level: High

Contacts repository: Maybe

This is the union of cvspurge and cvsdiscard. It undoes any local changes and removes unknown files from the working copy.

Use with truly paranoid caution.


Node: cvsdate, Previous: cvsco, Up: cvsutils -- General Utilities For Use With CVS

cvsdate

This script is apparently incomplete and possibly may never be finished. (See the README file for details.)


Node: cvs2cl -- Generate GNU-Style ChangeLogs, Next: , Previous: cvsutils -- General Utilities For Use With CVS, Up: Third-Party Tools

cvs2cl - Generate GNU-Style ChangeLogs

Depends on: Perl

URL: http://www.red-bean.com/~kfogel/cvs2cl.shtml

cvs2cl.pl condenses and reformats the output of cvs log to create a GNU-style ChangeLog file for your project. ChangeLogs are chronologically organized documents showing the change history of a project, with a format designed specifically for human-readability (see the following examples).

The problem with the cvs log command is that it presents its output on a per-file basis, with no acknowledgement that the same log message, appearing at roughly the same time in different files, implies that those revisions were all part of a single commit. Thus, reading over log output to get an overview of project development is a hopeless task - you can really only see the history of one file at a time.

In the ChangeLog produced by cvs2cl.pl, identical log messages are unified, so that a single commit involving a group of files shows up as one entry. For example:

     floss$ cvs2cl.pl -r
     cvs log: Logging .
     cvs log: Logging a-subdir
     cvs log: Logging a-subdir/subsubdir
     cvs log: Logging b-subdir
     floss$ cat ChangeLog
     ...
     1999-08-29 05:44  jrandom
     
        * README (1.6), hello.c (2.1), a-subdir/whatever.c (2.1),
        a-subdir/subsubdir/fish.c (2.1): Committing from pcl-cvs 2.9, just
        for kicks.
     
     1999-08-23 22:48  jrandom
     
        * README (1.5): [no log message]
     
     1999-08-22 19:34  jrandom
     
        * README (1.4): trivial change
     ...
     floss$
     

The first entry shows that four files were committed at once, with the log message, "Committing from pcl-cvs 2.9, just for kicks.". (The -r option was used to show the revision number of each file associated with that log message.)

Like CVS itself, cvs2cl.pl takes the current directory as an implied argument but acts on individual files if given file name arguments. Following are a few of the most commonly used options.


Node: cvsq -- Queue CVS Commands For Later Connection, Next: , Previous: cvslock -- Lock Repositories For Atomicity, Up: Third-Party Tools

cvsq - Queue CVS Commands For Later Connection

Depends on: Bash

URL: http://www.volny.cz/v.slavik/lt/cvsq.html

Vaclav Slavik <[email protected]>, the author of cvsq, has this to say about it:

cvsq stands for "cvs queued" and it is a small bash script that wraps around CVS. It makes working with CVS repository a bit easier for people connected via dial-up, because it can queue CVS commands and pass them to "real cvs" later.

For example, you can commit files immediately after editing them, when being offline, so you don't forget about them:

         cvsq commit -m "change 1" file1.c
         cvsq commit -m "change 2" file2.c
         cvsq commit -m "change 3" file3.c
     

And then, when you go online, you simply type

         cvsq upload
     

and all changes will be commited into the repository. If uploading of a particular file fails, it won't be lost - instead, you'll see error message and the file will stay in cvsq queue.

You can use cvsq even for commands that make no sense when offline - in that case, the command is immediately passed to cvs and not queued. For example, you can call cvsq update and it won't be put into the queue but executed immediately. In fact, you can start using cvsq as a replacement for cvs.

cvsq is in public domain.


Node: cvslock -- Lock Repositories For Atomicity, Next: , Previous: cvs2cl -- Generate GNU-Style ChangeLogs, Up: Third-Party Tools

cvslock - Lock Repositories For Atomicity

Depends on: C compiler for installation; nothing for runtime

URL: ftp://riemann.iam.uni-bonn.de/pub/users/roessler/cvslock/

This program locks a CVS repository (either for reading or writing) in the same way that CVS does, so that CVS will honor the locks. This can be useful when, for example, you need to make a copy of the whole repository and want to avoid catching parts of commits or other people's lockfiles.

The cvslock distribution is packaged extremely well and can be installed according to the usual GNU procedures. Here's a transcript of an install session:

     floss$ zcat cvslock-0.1.tar.gz | tar xvf -
     cvslock-0.1/
     cvslock-0.1/Makefile.in
     cvslock-0.1/README
     cvslock-0.1/COPYING
     cvslock-0.1/Makefile.am
     cvslock-0.1/acconfig.h
     cvslock-0.1/aclocal.m4
     cvslock-0.1/config.h.in
     cvslock-0.1/configure
     cvslock-0.1/configure.in
     cvslock-0.1/install-sh
     cvslock-0.1/missing
     cvslock-0.1/mkinstalldirs
     cvslock-0.1/stamp-h.in
     cvslock-0.1/cvslock.c
     cvslock-0.1/cvslock.1
     cvslock-0.1/snprintf.c
     cvslock-0.1/cvslssh
     cvslock-0.1/VERSION
     floss$ cd cvslock-0.1
     floss$ ./configure
      ...
     floss$ make
     gcc -DHAVE_CONFIG_H -I. -I. -I.   -g -O2 -c cvslock.c
     gcc -g -O2  -o cvslock  cvslock.o
     floss$ make install
      ...
     floss$
     

(Note that you may have to do the make install step as root).

Now, cvslock is installed as /usr/local/bin/cvslock. When you invoke it, you can specify the repository with -d or via the $CVSROOT environment variable, just as with CVS itself (the following examples use -d). Its only required argument is the name of the directory to lock, relative to the top of the repository. That directory and all of its subdirectories will be locked. In this example, there are no subdirectories, so only one lockfile is created:

     floss$ ls /usr/local/newrepos/myproj/b-subdir/
     random.c,v
     floss$ cvslock -d /usr/local/newrepos  myproj/b-subdir
     floss$ ls /usr/local/newrepos/myproj/b-subdir/
     #cvs.rfl.cvslock.floss.27378  random.c,v
     floss$ cvslock -u -p 27378 -d /usr/local/newrepos  myproj/b-subdir
     floss$ ls /usr/local/newrepos/myproj/b-subdir/
     random.c,v
     floss$
     

Notice that when I cleared the lock (-u for unlock), I had to specify -p 27378. That's because cvslock uses Unix process IDs when creating lockfile names to ensure that its locks are unique. When you unlock, you have to tell cvslock which lock instance to remove, even if there's only one instance present. Thus, the -p flag tells cvslock which previous instance of itself it's cleaning up after (you can use -p with or without -u, though).

If you're going to be working in the repository for a while, doing various operations directly in the file system, you can use the -s option to have cvslock start up a new shell for you. It then consults the $SHELL environment variable in your current shell to determine which shell to use:

     floss$ cvslock -s -d /usr/local/newrepos myproj
     

The locks remain present until you exit the shell, at which time they are automatically removed. You can also use the -c option to execute a command while the repository is locked. Just as with -s, the locks are put in place before the command starts and removed when it's finished. In the following example, we lock the repository just long enough to display a listing of all of the lockfiles:

     floss$ cvslock -c 'find . -name "*cvslock*" ' -d /usr/local/newrepos myproj
     cvslock: '/usr/local/newrepos/myproj' locked successfully.
     cvslock: Starting 'find . -name "*cvslock*" -print'...
     ./a-subdir/subsubdir/#cvs.rfl.cvslock.floss.27452
     ./a-subdir/#cvs.rfl.cvslock.floss.27452
     ./b-subdir/#cvs.rfl.cvslock.floss.27452
     ./#cvs.rfl.cvslock.floss.27452
     floss$ find /usr/local/newrepos/myproj -name "*cvslock*" -print
     floss$
     

The command (the argument to the -c option) is run with the specified repository directory as its working directory.

By default, cvslock creates read-locks. You can tell it to use write-locks instead by passing the -W option. (You can pass -R to specify read-locks, but that's the default anyway.) Always remove any locks when you're finished, so that other users' CVS processes don't wait needlessly.

Note that cvslock must be run on the machine where the repository resides - you cannot specify a remote repository. (For more information, run man cvslock, which is a manual page installed when you ran make install.)


Node: Other Packages, Next: , Previous: cvsq -- Queue CVS Commands For Later Connection, Up: Third-Party Tools

Other Packages

Many other third-party packages are available for CVS. Following are pointers to some of these.

CVSUp (Part Of The FreeBSD Project)

CVSUp is an efficient generic mirroring tool with special built-in support for mirroring CVS repositories. The FreeBSD operating system uses it to distribute changes from their master repository, so users can keep up to date conveniently.

For more information on CVSUp in general, check out http://www.polstra.com/projects/freeware/CVSup/.

For its use in FreeBSD in particular, see http://www.freebsd.org/handbook/synching.html#CVSUP.

CVSWeb: A Web Interface To CVS Repositories

CVSWeb provides a Web interface to browsing CVS repositories. A more accurate name might be "RCSWeb", because what it actually does is allow you to browse revisions directly in a repository, viewing log messages and diffs. Although I've never found it to be a particularly compelling interface myself, I have to admit that it is intuitive enough and a lot of sites use it.

Although the software was originally written by Bill Fenner, the version most actively under development right now seems to be Henner Zeller's, at http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi/.

You may also want to visit Fenner's original site at http://www.freebsd.org/~fenner/cvsweb/ and possibly this summary of the CVSWeb scene at http://www.cvshome.org/dev/addoncvsweb.html.

Finally, if you'd like to see CVSWeb in action, a good example can be browsed at http://sourceware.cygnus.com/cgi-bin/cvsweb.cgi/.

The CVS contrib/ Directory

As mentioned in Repository Administration, a number of third-party tools are shipped with CVS and are collected in the contrib/ directory. Although I'm not aware of any formal rule for determining which tools are distributed with CVS, an effort may be in process to gather most of the widely used third-party tools and put them in contrib/ so people know where to find them. Until that happens, the best way to find such tools is to look in contrib/, look at various CVS Web sites, and ask on the mailing list.


Node: Writing Your Own Tools, Previous: Other Packages, Up: Third-Party Tools

Writing Your Own Tools

CVS can at times seem like a bewildering collection of improvised standards. There's RCS format, various output formats (history, annotate, log, update, and so on), several repository administrative file formats, working copy administrative file formats, the client/server protocol, the lockfile protocol.... (Are you numb yet? I could keep going, you know.)

Fortunately, these standards remain fairly consistent from release to release - so if you're trying to write a tool to work with CVS, you at least don't have to worry about hitting a moving target. For every internal standard, there are usually a few people on the [email protected] mailing list who know it extremely well (several of them helped me out during the writing of this book). There is also the documentation that comes with the CVS distribution (especially doc/cvs.texinfo, doc/cvsclient.texi, and doc/RCSFILES). Finally, there is the CVS source code itself, the last word on any question of implementation or behavior.

With all of this at your disposal, there's no reason to hesitate. If you can think of some utility that would make your life with CVS easier, go ahead and write it - chances are other people have been wanting it, too. Unlike a change to CVS itself, a small, standalone external utility can get wide distribution very quickly, resulting in quicker feedback for its author and faster bug fixes for all of the users.


Node: Index, Next: , Previous: Third-Party Tools, Up: Top

Index

Sorry, the index is still in progress.

Since the online format is searchable anyway, I decided the incompleteness of the index need not delay the release of the chapters. I hope to have the index finished reasonably soon. Volunteer indexers are certainly welcome, too - please email [email protected] if you're interested.


Node: GNU General Public License, Next: , Previous: Index, Up: Top

GNU General Public License

     GNU General Public License
     
     Version 2, June 1991
     
     Copyright (C) 1989, 1991 Free Software Foundation, Inc.
     59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
     
     Everyone is permitted to copy and distribute verbatim copies
     of this license document, but changing it is not allowed.
     
     Preamble
     
     The licenses for most software are designed to take away your freedom to
     share and change it. By contrast, the GNU General Public License is intended
     to guarantee your freedom to share and change free software--to make sure
     the software is free for all its users. This General Public License applies
     to most of the Free Software Foundation's software and to any other program
     whose authors commit to using it. (Some other Free Software Foundation
     software is covered by the GNU Library General Public License instead.) You
     can apply it to your programs, too.
     
     When we speak of free software, we are referring to freedom, not price. Our
     General Public Licenses are designed to make sure that you have the freedom
     to distribute copies of free software (and charge for this service if you
     wish), that you receive source code or can get it if you want it, that you
     can change the software or use pieces of it in new free programs; and that
     you know you can do these things.
     
     To protect your rights, we need to make restrictions that forbid anyone to
     deny you these rights or to ask you to surrender the rights. These
     restrictions translate to certain responsibilities for you if you distribute
     copies of the software, or if you modify it.
     
     For example, if you distribute copies of such a program, whether gratis or
     for a fee, you must give the recipients all the rights that you have. You
     must make sure that they, too, receive or can get the source code. And you
     must show them these terms so they know their rights.
     
     We protect your rights with two steps: (1) copyright the software, and (2)
     offer you this license which gives you legal permission to copy, distribute
     and/or modify the software.
     
     Also, for each author's protection and ours, we want to make certain that
     everyone understands that there is no warranty for this free software. If
     the software is modified by someone else and passed on, we want its
     recipients to know that what they have is not the original, so that any
     problems introduced by others will not reflect on the original authors'
     reputations.
     
     Finally, any free program is threatened constantly by software patents. We
     wish to avoid the danger that redistributors of a free program will
     individually obtain patent licenses, in effect making the program
     proprietary. To prevent this, we have made it clear that any patent must be
     licensed for everyone's free use or not licensed at all.
     
     The precise terms and conditions for copying, distribution and modification
     follow.
     
     TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
     
     0. This License applies to any program or other work which contains a notice
     placed by the copyright holder saying it may be distributed under the terms
     of this General Public License. The "Program", below, refers to any such
     program or work, and a "work based on the Program" means either the Program
     or any derivative work under copyright law: that is to say, a work
     containing the Program or a portion of it, either verbatim or with
     modifications and/or translated into another language. (Hereinafter,
     translation is included without limitation in the term "modification".) Each
     licensee is addressed as "you".
     
     Activities other than copying, distribution and modification are not covered
     by this License; they are outside its scope. The act of running the Program
     is not restricted, and the output from the Program is covered only if its
     contents constitute a work based on the Program (independent of having been
     made by running the Program). Whether that is true depends on what the
     Program does.
     
     1. You may copy and distribute verbatim copies of the Program's source code
     as you receive it, in any medium, provided that you conspicuously and
     appropriately publish on each copy an appropriate copyright notice and
     disclaimer of warranty; keep intact all the notices that refer to this
     License and to the absence of any warranty; and give any other recipients of
     the Program a copy of this License along with the Program.
     
     You may charge a fee for the physical act of transferring a copy, and you
     may at your option offer warranty protection in exchange for a fee.
     
     2. You may modify your copy or copies of the Program or any portion of it,
     thus forming a work based on the Program, and copy and distribute such
     modifications or work under the terms of Section 1 above, provided that you
     also meet all of these conditions:
     
        * a) You must cause the modified files to carry prominent notices stating
          that you changed the files and the date of any change.
     
        * b) You must cause any work that you distribute or publish, that in
          whole or in part contains or is derived from the Program or any part
          thereof, to be licensed as a whole at no charge to all third parties
          under the terms of this License.
     
        * c) If the modified program normally reads commands interactively when
          run, you must cause it, when started running for such interactive use
          in the most ordinary way, to print or display an announcement including
          an appropriate copyright notice and a notice that there is no warranty
          (or else, saying that you provide a warranty) and that users may
          redistribute the program under these conditions, and telling the user
          how to view a copy of this License. (Exception: if the Program itself
          is interactive but does not normally print such an announcement, your
          work based on the Program is not required to print an announcement.)
     
     These requirements apply to the modified work as a whole. If identifiable
     sections of that work are not derived from the Program, and can be
     reasonably considered independent and separate works in themselves, then
     this License, and its terms, do not apply to those sections when you
     distribute them as separate works. But when you distribute the same sections
     as part of a whole which is a work based on the Program, the distribution of
     the whole must be on the terms of this License, whose permissions for other
     licensees extend to the entire whole, and thus to each and every part
     regardless of who wrote it.
     
     Thus, it is not the intent of this section to claim rights or contest your
     rights to work written entirely by you; rather, the intent is to exercise
     the right to control the distribution of derivative or collective works
     based on the Program.
     
     In addition, mere aggregation of another work not based on the Program with
     the Program (or with a work based on the Program) on a volume of a storage
     or distribution medium does not bring the other work under the scope of this
     License.
     
     3. You may copy and distribute the Program (or a work based on it, under
     Section 2) in object code or executable form under the terms of Sections 1
     and 2 above provided that you also do one of the following:
     
        * a) Accompany it with the complete corresponding machine-readable source
          code, which must be distributed under the terms of Sections 1 and 2
          above on a medium customarily used for software interchange; or,
     
        * b) Accompany it with a written offer, valid for at least three years,
          to give any third party, for a charge no more than your cost of
          physically performing source distribution, a complete machine-readable
          copy of the corresponding source code, to be distributed under the
          terms of Sections 1 and 2 above on a medium customarily used for
          software interchange; or,
     
        * c) Accompany it with the information you received as to the offer to
          distribute corresponding source code. (This alternative is allowed only
          for noncommercial distribution and only if you received the program in
          object code or executable form with such an offer, in accord with
          Subsection b above.)
     
     The source code for a work means the preferred form of the work for making
     modifications to it. For an executable work, complete source code means all
     the source code for all modules it contains, plus any associated interface
     definition files, plus the scripts used to control compilation and
     installation of the executable. However, as a special exception, the source
     code distributed need not include anything that is normally distributed (in
     either source or binary form) with the major components (compiler, kernel,
     and so on) of the operating system on which the executable runs, unless that
     component itself accompanies the executable.
     
     If distribution of executable or object code is made by offering access to
     copy from a designated place, then offering equivalent access to copy the
     source code from the same place counts as distribution of the source code,
     even though third parties are not compelled to copy the source along with
     the object code.
     
     4. You may not copy, modify, sublicense, or distribute the Program except as
     expressly provided under this License. Any attempt otherwise to copy,
     modify, sublicense or distribute the Program is void, and will automatically
     terminate your rights under this License. However, parties who have received
     copies, or rights, from you under this License will not have their licenses
     terminated so long as such parties remain in full compliance.
     
     5. You are not required to accept this License, since you have not signed
     it. However, nothing else grants you permission to modify or distribute the
     Program or its derivative works. These actions are prohibited by law if you
     do not accept this License. Therefore, by modifying or distributing the
     Program (or any work based on the Program), you indicate your acceptance of
     this License to do so, and all its terms and conditions for copying,
     distributing or modifying the Program or works based on it.
     
     6. Each time you redistribute the Program (or any work based on the
     Program), the recipient automatically receives a license from the original
     licensor to copy, distribute or modify the Program subject to these terms
     and conditions. You may not impose any further restrictions on the
     recipients' exercise of the rights granted herein. You are not responsible
     for enforcing compliance by third parties to this License.
     
     7. If, as a consequence of a court judgment or allegation of patent
     infringement or for any other reason (not limited to patent issues),
     conditions are imposed on you (whether by court order, agreement or
     otherwise) that contradict the conditions of this License, they do not
     excuse you from the conditions of this License. If you cannot distribute so
     as to satisfy simultaneously your obligations under this License and any
     other pertinent obligations, then as a consequence you may not distribute
     the Program at all. For example, if a patent license would not permit
     royalty-free redistribution of the Program by all those who receive copies
     directly or indirectly through you, then the only way you could satisfy both
     it and this License would be to refrain entirely from distribution of the
     Program.
     
     If any portion of this section is held invalid or unenforceable under any
     particular circumstance, the balance of the section is intended to apply and
     the section as a whole is intended to apply in other circumstances.
     
     It is not the purpose of this section to induce you to infringe any patents
     or other property right claims or to contest validity of any such claims;
     this section has the sole purpose of protecting the integrity of the free
     software distribution system, which is implemented by public license
     practices. Many people have made generous contributions to the wide range of
     software distributed through that system in reliance on consistent
     application of that system; it is up to the author/donor to decide if he or
     she is willing to distribute software through any other system and a
     licensee cannot impose that choice.
     
     This section is intended to make thoroughly clear what is believed to be a
     consequence of the rest of this License.
     
     8. If the distribution and/or use of the Program is restricted in certain
     countries either by patents or by copyrighted interfaces, the original
     copyright holder who places the Program under this License may add an
     explicit geographical distribution limitation excluding those countries, so
     that distribution is permitted only in or among countries not thus excluded.
     In such case, this License incorporates the limitation as if written in the
     body of this License.
     
     9. The Free Software Foundation may publish revised and/or new versions of
     the General Public License from time to time. Such new versions will be
     similar in spirit to the present version, but may differ in detail to
     address new problems or concerns.
     
     Each version is given a distinguishing version number. If the Program
     specifies a version number of this License which applies to it and "any
     later version", you have the option of following the terms and conditions
     either of that version or of any later version published by the Free
     Software Foundation. If the Program does not specify a version number of
     this License, you may choose any version ever published by the Free Software
     Foundation.
     
     10. If you wish to incorporate parts of the Program into other free programs
     whose distribution conditions are different, write to the author to ask for
     permission. For software which is copyrighted by the Free Software
     Foundation, write to the Free Software Foundation; we sometimes make
     exceptions for this. Our decision will be guided by the two goals of
     preserving the free status of all derivatives of our free software and of
     promoting the sharing and reuse of software generally.
     
     NO WARRANTY
     
     11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
     THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
     OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
     PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
     OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO
     THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM
     PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
     CORRECTION.
     
     12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
     WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
     REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
     INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
     OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO
     LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR
     THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
     PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
     POSSIBILITY OF SUCH DAMAGES.
     
     END OF TERMS AND CONDITIONS
     
     How to Apply These Terms to Your New Programs
     
     If you develop a new program, and you want it to be of the greatest possible
     use to the public, the best way to achieve this is to make it free software
     which everyone can redistribute and change under these terms.
     
     To do so, attach the following notices to the program. It is safest to
     attach them to the start of each source file to most effectively convey the
     exclusion of warranty; and each file should have at least the "copyright"
     line and a pointer to where the full notice is found.
     
     one line to give the program's name and an idea of what it does.
     Copyright (C) yyyy  name of author
     
     This program is free software; you can redistribute it and/or
     modify it under the terms of the GNU General Public License
     as published by the Free Software Foundation; either version 2
     of the License, or (at your option) any later version.
     
     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
     
     You should have received a copy of the GNU General Public License
     along with this program; if not, write to the Free Software
     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     
     Also add information on how to contact you by electronic and paper mail.
     
     If the program is interactive, make it output a short notice like this when
     it starts in an interactive mode:
     
     Gnomovision version 69, Copyright (C) yyyy name of author
     Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
     type `show w'.  This is free software, and you are welcome
     to redistribute it under certain conditions; type `show c'
     for details.
     
     The hypothetical commands `show w' and `show c' should show the appropriate
     parts of the General Public License. Of course, the commands you use may be
     called something other than `show w' and `show c'; they could even be
     mouse-clicks or menu items--whatever suits your program.
     
     You should also get your employer (if you work as a programmer) or your
     school, if any, to sign a "copyright disclaimer" for the program, if
     necessary. Here is a sample; alter the names:
     
     Yoyodyne, Inc., hereby disclaims all copyright
     interest in the program `Gnomovision'
     (which makes passes at compilers) written
     by James Hacker.
     
     signature of Ty Coon, 1 April 1989
     Ty Coon, President of Vice
     
     This General Public License does not permit incorporating your program into
     proprietary programs. If your program is a subroutine library, you may
     consider it more useful to permit linking proprietary applications with the
     library. If this is what you want to do, use the GNU Library General Public
     License instead of this License.
     
     



Node: GNU Free Documentation License, Previous: GNU General Public License, Up: Top

GNU Free Documentation License

     GNU Free Documentation License
     
     Version 1.1, March 2000
     
     Copyright (C) 2000  Free Software Foundation, Inc.
     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
     Everyone is permitted to copy and distribute verbatim copies
     of this license document, but changing it is not allowed.
     
     0. PREAMBLE
     
     The purpose of this License is to make a manual, textbook, or other written
     document "free" in the sense of freedom: to assure everyone the effective
     freedom to copy and redistribute it, with or without modifying it, either
     commercially or noncommercially. Secondarily, this License preserves for the
     author and publisher a way to get credit for their work, while not being
     considered responsible for modifications made by others.
     
     This License is a kind of "copyleft", which means that derivative works of
     the document must themselves be free in the same sense. It complements the
     GNU General Public License, which is a copyleft license designed for free
     software.
     
     We have designed this License in order to use it for manuals for free
     software, because free software needs free documentation: a free program
     should come with manuals providing the same freedoms that the software does.
     But this License is not limited to software manuals; it can be used for any
     textual work, regardless of subject matter or whether it is published as a
     printed book. We recommend this License principally for works whose purpose
     is instruction or reference.
     
     1. APPLICABILITY AND DEFINITIONS
     
     This License applies to any manual or other work that contains a notice
     placed by the copyright holder saying it can be distributed under the terms
     of this License. The "Document", below, refers to any such manual or work.
     Any member of the public is a licensee, and is addressed as "you".
     
     A "Modified Version" of the Document means any work containing the Document
     or a portion of it, either copied verbatim, or with modifications and/or
     translated into another language.
     
     A "Secondary Section" is a named appendix or a front-matter section of the
     Document that deals exclusively with the relationship of the publishers or
     authors of the Document to the Document's overall subject (or to related
     matters) and contains nothing that could fall directly within that overall
     subject. (For example, if the Document is in part a textbook of mathematics,
     a Secondary Section may not explain any mathematics.) The relationship could
     be a matter of historical connection with the subject or with related
     matters, or of legal, commercial, philosophical, ethical or political
     position regarding them.
     
     The "Invariant Sections" are certain Secondary Sections whose titles are
     designated, as being those of Invariant Sections, in the notice that says
     that the Document is released under this License.
     
     The "Cover Texts" are certain short passages of text that are listed, as
     Front-Cover Texts or Back-Cover Texts, in the notice that says that the
     Document is released under this License.
     
     A "Transparent" copy of the Document means a machine-readable copy,
     represented in a format whose specification is available to the general
     public, whose contents can be viewed and edited directly and
     straightforwardly with generic text editors or (for images composed of
     pixels) generic paint programs or (for drawings) some widely available
     drawing editor, and that is suitable for input to text formatters or for
     automatic translation to a variety of formats suitable for input to text
     formatters. A copy made in an otherwise Transparent file format whose markup
     has been designed to thwart or discourage subsequent modification by readers
     is not Transparent. A copy that is not "Transparent" is called "Opaque".
     
     Examples of suitable formats for Transparent copies include plain ASCII
     without markup, Texinfo input format, LaTeX input format, SGML or XML using
     a publicly available DTD, and standard-conforming simple HTML designed for
     human modification. Opaque formats include PostScript, PDF, proprietary
     formats that can be read and edited only by proprietary word processors,
     SGML or XML for which the DTD and/or processing tools are not generally
     available, and the machine-generated HTML produced by some word processors
     for output purposes only.
     
     The "Title Page" means, for a printed book, the title page itself, plus such
     following pages as are needed to hold, legibly, the material this License
     requires to appear in the title page. For works in formats which do not have
     any title page as such, "Title Page" means the text near the most prominent
     appearance of the work's title, preceding the beginning of the body of the
     text.
     
     2. VERBATIM COPYING
     
     You may copy and distribute the Document in any medium, either commercially
     or noncommercially, provided that this License, the copyright notices, and
     the license notice saying this License applies to the Document are
     reproduced in all copies, and that you add no other conditions whatsoever to
     those of this License. You may not use technical measures to obstruct or
     control the reading or further copying of the copies you make or distribute.
     However, you may accept compensation in exchange for copies. If you
     distribute a large enough number of copies you must also follow the
     conditions in section 3.
     
     You may also lend copies, under the same conditions stated above, and you
     may publicly display copies.
     
     3. COPYING IN QUANTITY
     
     If you publish printed copies of the Document numbering more than 100, and
     the Document's license notice requires Cover Texts, you must enclose the
     copies in covers that carry, clearly and legibly, all these Cover Texts:
     Front-Cover Texts on the front cover, and Back-Cover Texts on the back
     cover. Both covers must also clearly and legibly identify you as the
     publisher of these copies. The front cover must present the full title with
     all words of the title equally prominent and visible. You may add other
     material on the covers in addition. Copying with changes limited to the
     covers, as long as they preserve the title of the Document and satisfy these
     conditions, can be treated as verbatim copying in other respects.
     
     If the required texts for either cover are too voluminous to fit legibly,
     you should put the first ones listed (as many as fit reasonably) on the
     actual cover, and continue the rest onto adjacent pages.
     
     If you publish or distribute Opaque copies of the Document numbering more
     than 100, you must either include a machine-readable Transparent copy along
     with each Opaque copy, or state in or with each Opaque copy a
     publicly-accessible computer-network location containing a complete
     Transparent copy of the Document, free of added material, which the general
     network-using public has access to download anonymously at no charge using
     public-standard network protocols. If you use the latter option, you must
     take reasonably prudent steps, when you begin distribution of Opaque copies
     in quantity, to ensure that this Transparent copy will remain thus
     accessible at the stated location until at least one year after the last
     time you distribute an Opaque copy (directly or through your agents or
     retailers) of that edition to the public.
     
     It is requested, but not required, that you contact the authors of the
     Document well before redistributing any large number of copies, to give them
     a chance to provide you with an updated version of the Document.
     
     4. MODIFICATIONS
     
     You may copy and distribute a Modified Version of the Document under the
     conditions of sections 2 and 3 above, provided that you release the Modified
     Version under precisely this License, with the Modified Version filling the
     role of the Document, thus licensing distribution and modification of the
     Modified Version to whoever possesses a copy of it. In addition, you must do
     these things in the Modified Version:
     
        * A. Use in the Title Page (and on the covers, if any) a title distinct
          from that of the Document, and from those of previous versions (which
          should, if there were any, be listed in the History section of the
          Document). You may use the same title as a previous version if the
          original publisher of that version gives permission.
        * B. List on the Title Page, as authors, one or more persons or entities
          responsible for authorship of the modifications in the Modified
          Version, together with at least five of the principal authors of the
          Document (all of its principal authors, if it has less than five).
        * C. State on the Title page the name of the publisher of the Modified
          Version, as the publisher.
        * D. Preserve all the copyright notices of the Document.
        * E. Add an appropriate copyright notice for your modifications adjacent
          to the other copyright notices.
        * F. Include, immediately after the copyright notices, a license notice
          giving the public permission to use the Modified Version under the
          terms of this License, in the form shown in the Addendum below.
        * G. Preserve in that license notice the full lists of Invariant Sections
          and required Cover Texts given in the Document's license notice.
        * H. Include an unaltered copy of this License.
        * I. Preserve the section entitled "History", and its title, and add to
          it an item stating at least the title, year, new authors, and publisher
          of the Modified Version as given on the Title Page. If there is no
          section entitled "History" in the Document, create one stating the
          title, year, authors, and publisher of the Document as given on its
          Title Page, then add an item describing the Modified Version as stated
          in the previous sentence.
        * J. Preserve the network location, if any, given in the Document for
          public access to a Transparent copy of the Document, and likewise the
          network locations given in the Document for previous versions it was
          based on. These may be placed in the "History" section. You may omit a
          network location for a work that was published at least four years
          before the Document itself, or if the original publisher of the version
          it refers to gives permission.
        * K. In any section entitled "Acknowledgements" or "Dedications",
          preserve the section's title, and preserve in the section all the
          substance and tone of each of the contributor acknowledgements and/or
          dedications given therein.
        * L. Preserve all the Invariant Sections of the Document, unaltered in
          their text and in their titles. Section numbers or the equivalent are
          not considered part of the section titles.
        * M. Delete any section entitled "Endorsements". Such a section may not
          be included in the Modified Version.
        * N. Do not retitle any existing section as "Endorsements" or to conflict
          in title with any Invariant Section.
     
     If the Modified Version includes new front-matter sections or appendices
     that qualify as Secondary Sections and contain no material copied from the
     Document, you may at your option designate some or all of these sections as
     invariant. To do this, add their titles to the list of Invariant Sections in
     the Modified Version's license notice. These titles must be distinct from
     any other section titles.
     
     You may add a section entitled "Endorsements", provided it contains nothing
     but endorsements of your Modified Version by various parties--for example,
     statements of peer review or that the text has been approved by an
     organization as the authoritative definition of a standard.
     
     You may add a passage of up to five words as a Front-Cover Text, and a
     passage of up to 25 words as a Back-Cover Text, to the end of the list of
     Cover Texts in the Modified Version. Only one passage of Front-Cover Text
     and one of Back-Cover Text may be added by (or through arrangements made by)
     any one entity. If the Document already includes a cover text for the same
     cover, previously added by you or by arrangement made by the same entity you
     are acting on behalf of, you may not add another; but you may replace the
     old one, on explicit permission from the previous publisher that added the
     old one.
     
     The author(s) and publisher(s) of the Document do not by this License give
     permission to use their names for publicity for or to assert or imply
     endorsement of any Modified Version.
     
     5. COMBINING DOCUMENTS
     
     You may combine the Document with other documents released under this
     License, under the terms defined in section 4 above for modified versions,
     provided that you include in the combination all of the Invariant Sections
     of all of the original documents, unmodified, and list them all as Invariant
     Sections of your combined work in its license notice.
     
     The combined work need only contain one copy of this License, and multiple
     identical Invariant Sections may be replaced with a single copy. If there
     are multiple Invariant Sections with the same name but different contents,
     make the title of each such section unique by adding at the end of it, in
     parentheses, the name of the original author or publisher of that section if
     known, or else a unique number. Make the same adjustment to the section
     titles in the list of Invariant Sections in the license notice of the
     combined work.
     
     In the combination, you must combine any sections entitled "History" in the
     various original documents, forming one section entitled "History"; likewise
     combine any sections entitled "Acknowledgements", and any sections entitled
     "Dedications". You must delete all sections entitled "Endorsements."
     
     6. COLLECTIONS OF DOCUMENTS
     
     You may make a collection consisting of the Document and other documents
     released under this License, and replace the individual copies of this
     License in the various documents with a single copy that is included in the
     collection, provided that you follow the rules of this License for verbatim
     copying of each of the documents in all other respects.
     
     You may extract a single document from such a collection, and distribute it
     individually under this License, provided you insert a copy of this License
     into the extracted document, and follow this License in all other respects
     regarding verbatim copying of that document.
     
     7. AGGREGATION WITH INDEPENDENT WORKS
     
     A compilation of the Document or its derivatives with other separate and
     independent documents or works, in or on a volume of a storage or
     distribution medium, does not as a whole count as a Modified Version of the
     Document, provided no compilation copyright is claimed for the compilation.
     Such a compilation is called an "aggregate", and this License does not apply
     to the other self-contained works thus compiled with the Document, on
     account of their being thus compiled, if they are not themselves derivative
     works of the Document. If the Cover Text requirement of section 3 is
     applicable to these copies of the Document, then if the Document is less
     than one quarter of the entire aggregate, the Document's Cover Texts may be
     placed on covers that surround only the Document within the aggregate.
     Otherwise they must appear on covers around the whole aggregate.
     
     8. TRANSLATION
     
     Translation is considered a kind of modification, so you may distribute
     translations of the Document under the terms of section 4. Replacing
     Invariant Sections with translations requires special permission from their
     copyright holders, but you may include translations of some or all Invariant
     Sections in addition to the original versions of these Invariant Sections.
     You may include a translation of this License provided that you also include
     the original English version of this License. In case of a disagreement
     between the translation and the original English version of this License,
     the original English version will prevail.
     
     9. TERMINATION
     
     You may not copy, modify, sublicense, or distribute the Document except as
     expressly provided for under this License. Any other attempt to copy,
     modify, sublicense or distribute the Document is void, and will
     automatically terminate your rights under this License. However, parties who
     have received copies, or rights, from you under this License will not have
     their licenses terminated so long as such parties remain in full compliance.
     
     10. FUTURE REVISIONS OF THIS LICENSE
     
     The Free Software Foundation may publish new, revised versions of the GNU
     Free Documentation License from time to time. Such new versions will be
     similar in spirit to the present version, but may differ in detail to
     address new problems or concerns. See http://www.gnu.org/copyleft/.
     
     Each version of the License is given a distinguishing version number. If the
     Document specifies that a particular numbered version of this License "or
     any later version" applies to it, you have the option of following the terms
     and conditions either of that specified version or of any later version that
     has been published (not as a draft) by the Free Software Foundation. If the
     Document does not specify a version number of this License, you may choose
     any version ever published (not as a draft) by the Free Software Foundation.
     
     How to use this License for your documents
     
     To use this License in a document you have written, include a copy of the
     License in the document and put the following copyright and license notices
     just after the title page:
     
           Copyright (c)  YEAR  YOUR NAME.
           Permission is granted to copy, distribute and/or modify this document
           under the terms of the GNU Free Documentation License, Version 1.1
           or any later version published by the Free Software Foundation;
           with the Invariant Sections being LIST THEIR TITLES, with the
           Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
           A copy of the license is included in the section entitled "GNU
           Free Documentation License".
     
     If you have no Invariant Sections, write "with no Invariant Sections"
     instead of saying which ones are invariant. If you have no Front-Cover
     Texts, write "no Front-Cover Texts" instead of "Front-Cover Texts being
     LIST"; likewise for Back-Cover Texts.
     
     If your document contains nontrivial examples of program code, we recommend
     releasing these examples in parallel under your choice of free software
     license, such as the GNU General Public License, to permit their use in free
     software.