From 8672a04720e7421e7f41bbf49364bcc1df910bb2 Mon Sep 17 00:00:00 2001
From: llornkcor Copyright © 1999, 2000 Karl Fogel <kfogel@red-bean.com>
+
+ 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.
+
+
+ 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 bug-cvsbook@red-bean.com. For news and updates, visit
+http://cvsbook.red-bean.com/.
+
+ -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.
+
+ 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:
+
+Table of Contents
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+.cvsrc
+.cvsignore
+.cvspass
+.cvswrappers
+
+Node:Top,
+Next:Introduction,
+Up:(dir)
+
+
+
+
+Top
+
+
+
+
+
+Node:Introduction,
+Next:An Overview of CVS,
+Previous:Top,
+Up:Top
+
+
+Introduction
+
+
+Node:An Overview of CVS,
+Next:Repository Administration,
+Previous:Introduction,
+Up:Top
+
+
+An Overview of CVS
+
+
+I can't imagine programming without it... that would be like
+parachuting without a parachute!
+
+
+
+
+
+
+
+Node:Basic Concepts,
+Next:A Day With CVS,
+Up:An Overview of CVS
+
+
+Basic Concepts
+
+
+
+
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: + +
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: + +
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. + +
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. + +
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.) + +
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:jrandom@cvs.foobar.com:/usr/local/cvs login +(Logging in to jrandom@cvs.foobar.com) +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:jrandom@fish.foobar.org:/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:jrandom@cvs.foobar.com:/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:jrandom@cvs.foobar.com:/usr/local/cvs login +(Logging in to jrandom@cvs.foobar.com) +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:jrandom@cvs.foobar.com:/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:jrandom@cvs.foobar.com:/usr/local/cvs +floss$ export CVSROOT +floss$ echo $CVSROOT +:pserver:jrandom@cvs.foobar.com:/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. + +
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. + +
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:jrandom@cvs.foobar.com:/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. + +
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. + +
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. + +
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. + +
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. + +
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. + +
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:qsmith@cvs.foobar.com:/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. + +
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$ ++ +
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. + +
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. + +
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$ ++ +
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. + +
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. + +
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. + +
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$ ++ +
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. + +
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$ ++ +
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.
+
+
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. + +
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. + +
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. + +
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. + +
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$ ++ +
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. + +
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. + +
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. + +
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
).
+
+
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. + +
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.)
+
+
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. + +
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. + +
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. + +
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. + +
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. + +
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.) + +
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. + +
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. + +
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: + +
NEWS
- This file lists the changes from one release to the next,
+in reverse chronological order (that is, most recent first). If you've
+already been using CVS for a while and have just upgraded to a new
+version, you should look at the NEWS file to see what new features are
+available. Also, although most changes to CVS preserve backward
+compatibility, noncompatible changes do occur from time to time. It's
+better to read about them here than be surprised when CVS doesn't behave
+the way you expect it to.
+
+BUGS
- This file contains exactly what you think it does: a list
+of known bugs in CVS. They usually aren't show-stoppers, but you should
+read over them whenever you install a new release.
+
+DEVEL-CVS
- This file is the CVS "constitution". It describes
+the process by which changes are accepted into the main CVS distribution
+and the procedures through which a person becomes a CVS developer. You
+don't really need to read it if you just want to use CVS; however, it's
+highly interesting if you want to understand how the mostly
+uncoordinated efforts of people scattered across the globe coalesce into
+a working, usable piece of software. And of course, it's required
+reading if you plan to submit a patch (be it a bug fix or new feature)
+to CVS.
+
+HACKING
- Despite its name, the HACKING file doesn't say much
+about the design or implementation of CVS. It's mainly a guide to
+coding standards and other technical administrivia for people thinking
+of writing a patch to CVS. It can be thought of as an addendum to the
+DEVEL-CVS file. After you understand the basic philosophy of CVS
+development, you must read the HACKING file to translate that into
+concrete coding practices.
+
+FAQ
- This is the CVS "Frequently Asked Questions" document.
+Unfortunately it has a rather spotty maintenance history. David Grubbs
+took care of it until 1995, then he (presumably) got too busy and it
+languished for a while. Eventually, in 1997, Pascal Molli took over
+maintenance. Molli also didn't have time to maintain it by hand, but at
+least he found time to put it into his automated FAQ-O-Matic system,
+which allows the public to maintain the FAQ in a decentralized manner
+(basically, anyone can edit or add entries via a Web form). This was
+probably a good thing, in that at least the FAQ was once again being
+maintained; however, its overall organization and quality control are
+not on the same level as if a person were maintaining it.
+
+The master version of the FAQ is always available from Molli's Web site +(http://www.loria.fr/~molli/cvs-index.html, under the link +"Documentation"). The FAQ file shipped with CVS distributions is +generated automatically from that FAQ-O-Matic database, so by the time +it reaches the public it's already a little bit out of date. +Nevertheless, it can be quite helpful when you're looking for hints and +examples about how to do something specific (say, merging a large branch +back into the trunk or resurrecting a removed file). The best way to +use it is as a reference document; you can bring it up in your favorite +editor and do text searches on terms that interest you. Trying to use +it as a tutorial would be a mistake - it's missing too many important +facts about CVS to serve as a complete guide. + +
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. + +
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. + +
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 +info-cvs-request@gnu.org (the list itself is +info-cvs@gnu.org). 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 +bug-cvs-request@gnu.org. + +
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. + +
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: + +
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 ++ +
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. + +
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:jrandom@floss.red-bean.com:/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.
+
+
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:anonymous@cvs.foobar.com:/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. + +
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.
+
+
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 jrandom@red-bean.com 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.) + +
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 jrandom@red-bean.com 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 jrandom@red-bean.com 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.
+
+
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. + +
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. + +
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. + +
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. + +
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 myproj-devel@foobar.com %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).
+
+
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. + +
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. + +
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. + +
This file is obsolete, even though it's still included in distributions. +Just ignore it. + +
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.
+
+
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.
+
+
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 listerizer@red-bean.com +RoadMail CVSROOT/log.pl %s -f CVSROOT/commitlog -m roadmail@red-bean.com +bk/*score CVSROOT/log.pl %s -f CVSROOT/commitlog -m \ + bkscore-devel@red-bean.com ++ +
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.
+
+
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. + +
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. + +
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. + +
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. + +
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:quentinsmith@farawayplace.com ++ +
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 <quentinsmith@farawayplace.com>" ++ +
or + +
qsmith:'Quentin Q. Smith <quentinsmith@farawayplace.com>' ++ +
However, this will not work: + +
qsmith:"Quentin Q. Smith" <quentinsmith@farawayplace.com> ++ +
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:quentinsmith@farawayplace.com ++ +
Now that the repository is set up for watches, let's look at what +developers need to do in their working copies. + +
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.) + +
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!
+
+
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. + +
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. + +
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. + +
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. + +
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. + +
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 <peter.ross@miscrit.be>; 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.
+
+
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.
+
+
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 ++ +
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$ ++ +
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. + +
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$ ++ +
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$ + +$Id$ ==> filename, revision, date, and author; expands to ==> +$Id$ + +$Revision$ ==> exactly what you think it is, expands to ==> +$Revision$ + +$Source$ ==> path to corresponding repository file, expands to ==> +$Source$ + +$Log$ +Revision 1.1 2002/06/23 14:32:50 llornkcor +some dev docs + ==> accumulating log messages for the file, expands to ==> +$Log$ +Revision 1.1 2002/06/23 14:32:50 llornkcor +some dev docs + +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$ +
The Revision 1.1 2002/06/23 14:32:50 llornkcor +
The some dev docs +
The 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$ +// Revision 1.1 2002/06/23 14:32:50 llornkcor +// some dev docs +// ++ +it will expand to something like this on the first commit: + +
// $Log$ +// Revision 1.1 2002/06/23 14:32:50 llornkcor +// some dev docs +// +// Revision 1.14 1999/07/26 07:03:20 jrandom +// this is the first log message... +// ++ +this on the second: + +
// $Log$ +// Revision 1.1 2002/06/23 14:32:50 llornkcor +// some dev docs +// +// 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$ +// Revision 1.1 2002/06/23 14:32:50 llornkcor +// some dev docs +// +// 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$"; ++ +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:Tracking Third-Party Sources (Vendor Branches), +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. + +
To work successfully with branches, your development group should adhere +to these principles: + +
This does not mean minimizing the absolute number of branches in the +project, just the number being worked on at any given time. + +
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. + +
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. + +
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. + +
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. + +
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$ ++ +
and this on the branch + +
$Revision$ ++ +
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$ +======= +key $Revision$ +>>>>>>> 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. + +
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!) + +
------------------------------------------------------------- +
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.
+
+
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 info-cvs@gnu.org +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. + +
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. + +
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. + +
+ +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: + +
floss$ cvs checkout -D 1999-05-15 myproj ++ +
or + +
floss$ cvs update -D 1999-05-15 myproj ++ +
is run. + +
(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
.
+
+
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). + +
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). + +
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: + +
As noted in Repository Administration, you can get around this +writeability requirement by setting the LockDir parameter in +CVSROOT/config, like this: + +
LockDir=/usr/local/cvslocks ++ +
Of course, then you would need to make sure the directory +/usr/local/cvslocks is writeable by all CVS users. Either way, most CVS +operations, including read-only ones, are going to require a writeable +directory somewhere. By default, that directory is the project's +repository; if you're very security conscious, you can change it to be +somewhere else. + +
Unfortunately (and inexplicably), the history file is not born +world-writeable when you create a new repository with cvs init. At +least with the current version of CVS, you should explicitly change its +permissions after you create a new repository (or just remove it, if you +want to disable history logging entirely). + +
(This problem may go away - I just now submitted a patch to the CVS +maintainers that makes the history file world-writeable when you +initialize a new repository. So perhaps if you get a more recent +version of CVS than the one available now (September 1999), it won't be +a problem for you.) + +
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... + +
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. + +
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. + +
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.
+
+
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. + +
Okay, if the problem is not a missing --allow-root
, here are a
+few other possibilities:
+
+
cvspserver 2401/tcp ++ +
so inetd is not even listening on that port to pass connections off to +CVS. + +
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): + +
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. + +
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.
+
+
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. + +
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. + +
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. + +
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.) + +
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. + +
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. + +
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. + +
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. + +
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. + +
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. + +
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). + +
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 ++ +
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). + +
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 @ * @; ++ +
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. + +
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.
+
+
Email an accurate and complete description of your problem to +info-cvs@gnu.org, 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 info-cvs-request@gnu.org, so you can help +answer questions, too. + +
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 +bug-cvs@gnu.org (you can also subscribe to that list; just use +bug-cvs-request@gnu.org 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.) + +
Same as with a bug: Send the patch to bug-cvs@gnu.org. Make +sure you've read over the HACKING file first, though. + +
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). + +
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). + +
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. + +
add
command.
+admin
command.
+annotate
command.
+checkout
command.
+commit
command.
+diff
command.
+edit
command.
+editors
command.
+export
command.
+gserver
command.
+history
command.
+import
command.
+init
command.
+kserver
command.
+log
command.
+login
command.
+logout
command.
+pserver
command.
+rdiff
command.
+release
command.
+remove
command.
+rtag
command.
+server
command.
+status
command.
+tag
command.
+unedit
command.
+update
command.
+watch
command.
+watchers
command.
+This section is organized alphabetically to make it easy for you to look +up a particular command or option. The following conventions are used: + +
[ ]
. (This works
+out okay because square brackets turn out not used in CVS syntaces.)
+
+x|y|z
. (And therefore forward slashes (/
)
+should be interpreted literally - they do not divide choices in a set.)
+
+When a plural is parenthesized, as in FILE(S), it means that although +technically there can be two or more files, usually there is only one. + +
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. + +
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. + +
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
REPOSITORYThis 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: + +
:ext:jrandom@floss.red-bean.com:/usr/local/newrepos
- Connects
+using rsh
, ssh
, or some other external connection program.
+If the $CVS_RSH
environment variable is unset, this defaults to
+rsh
; otherwise, it uses the value of that variable.
+
+:server:jrandom@floss.red-bean.com:/usr/local/newrepos
- Like
+:ext:
, but uses CVS's internal implementation of rsh. (This may
+not be available on all platforms.)
+
+:pserver:jrandom@floss.red-bean.com:/usr/local/newrepos
-
+Connects using the password authenticating server (see The Password-Authenticating Server in Repository Administration; see
+also the login command.)
+
+:kserver:jrandom@floss.red-bean.com:/usr/local/newrepos
-
+Connects using Kerberos authentication.
+
+:gserver:jrandom@floss.red-bean.com:/usr/local/newrepos
-
+Connects using GSSAPI authentication.
+
+:fork:jrandom@floss.red-bean.com:/usr/local/newrepos
- Connects
+to a local repository, but using the client/server network protocol
+instead of directly accessing the repository files. This is useful for
+testing or debugging remote CVS behaviors from your local machine.
+
+:local:jrandom@floss.red-bean.com:/usr/local/newrepos
-
+Accesses a local repository directly, as though only the absolute path
+to the repository had been given.
+
+-e
EDITORInvokes 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=
VALUEThis 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
DIRStores 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
GZIPLEVELSets 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. + +
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: + +
As of version 1.10.7, there is a bug in CVS whereby the description is +lost if you add a file via client/server CVS. The rest of the add +process seems to work fine, however, if that's any comfort. + +
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: + +
strict
. (See -l below.)
+
+The intent of this option is to give you a way to do reserved
+checkouts, where only one user can be editing the file at a time. I'm
+not sure how useful this really is, but if you want to try it, you
+should probably do so in conjunction with the rcslock.pl script in the
+CVS source distribution's contrib/ directory. See comments in that file
+for further information. Among other things, those comments indicate
+that the locking must be set to strict
. (See -L.) There is no
+space between -l and its argument.
+
+
.
), NAME
+is attached to the highest revision on that branch. If REV is just $,
+NAME is attached to revision numbers found in keyword strings in the
+working files.
+
+In all cases where a NAME is assigned, CVS exits with an error if there +is already a tag named NAME in the file (but see -N). There are no +spaces between -n and its arguments. + +
None of the revisions being deleted may have branches or locks. If any +of the revisions have symbolic names attached, you have to delete them +first with tag -d or admin -n. (Actually, right now CVS only protects +against deleting symbolically named revisions if you're using one of the +:: syntaxes, but the single-colon syntaxes may soon change to this +behavior as well.) + +
Instead of using this option to undo a bad commit, you should commit a +new revision that undoes the bad change. There are no spaces between -o +and its arguments. + +
Any string of letters or numbers is acceptable for STATE; some commonly +used states are Exp for experimental, Stab for stable, and Rel for +released. (In fact, CVS sets the state to Exp when a file is created.) +Note that CVS uses the state dead for its own purposes, so don't specify +that one. + +
States are displayed in cvs log output, and in the $Log and $State RCS +keywords in files. There is no space between -s and its arguments. + +
This useful option, unfortunately, does not currently work in
+client/server CVS. In addition, if you try it in client/server and omit
+DESCFILE, any existing description for the file is wiped out and
+replaced with the empty string. If you need to rewrite a file's
+description, either do so using only local CVS on the same machine as
+the repository or -t-STRING (see below). There is no space between -t
+and its argument. DESCFILE may not begin with a hyphen (-
).
+
+
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: + +
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: + +
Synopsis: commit [OPTIONS] [FILES] + +
Commits changes from a working copy to the repository. + +
Options: + +
commit
does not recurse with this option (it
+implies -l). You can force it to recurse with -R.
+
+This meaning of -f is at odds with its usual meaning ("force to head +revision") in CVS commands. + +
The -r REV option implies -f as well. A new revision is committed even +if there are no changes to commit. + +
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. + +
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: + +
Synopsis: editors [OPTIONS] [FILES] + +
Shows who is currently editing a watched file. (See also +watch, watchers, edit, and unedit.) + +
Options: + +
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: + +
CVSROOT/modules
. (See Repository Administrative Files
+later in this chapter for more about this.)
+
+.cvsrc
file.
+
+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. + +
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: + +
This option appears to be at least partially broken as of summer 1999. + +
TOEFWUCGMAR
; any
+number of letters can be combined. Here is what they mean:
+
+The default, if no -x option is given, is to show checkouts (like
+-x O
).
+
+
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.) + +
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: + +
floss$ cvs import -m "importing from vendor 1" theirproj THEM1 THEM1-0 ++ +
To import to a vendor branch other than the default, you must specify a +different branch number explicitly: + +
floss$ cvs import -b 1.1.3 -m "from vendor 2" theirproj THEM2 THEM2-0 ++ +
The 1.1.3 branch can absorb future imports and be merged like any other
+vendor branch. However, you must make sure any future imports that
+specify -b 1.1.3
also use the same vendor tag (THEM2
).
+CVS does not check to make sure that the vendor branch matches the
+vendor tag. However, if they mismatch, odd and unpredictable things
+will happen.
+
+
Vendor branches are odd-numbered, the opposite of regular branches. + +
*.foo
means ignore everything ending in .foo
.
+(See cvsignore in Repository Administrative Files for
+details about wildcards.)
+
+The following file and directory names are ignored by default: + +
. + .. + .#* + #* + ,* + _$* + *~ + *$ + *.a + *.bak + *.BAK + *.elc + *.exe + *.ln + *.o + *.obj + *.olb + *.old + *.orig + *.rej + *.so + *.Z + .del-* + .make.state + .nse_depinfo + core + CVS + CVS.adm + cvslog.* + RCS + RCSLOG + SCCS + tags + TAGS ++ +
You can suppress the ignoring of those file name patterns, as well as
+any specified in .cvsignore
, CVSROOT/cvsignore
, and the
+$CVSIGNORE
environment variable, by using -I !
. That
+is,
+
+
floss$ cvs import -I ! -m "importing the universe" proj VENDOR VENDOR_0 ++ +
imports all files in the current directory tree, even those that would +otherwise be ignored. + +
Using a -I !
clears whatever ignore list has been created to
+that point, so any -I options that came before it would be nullified,
+but any that come after will still count. Thus,
+
+
floss$ cvs import -I ! -I README.txt -m "some msg" theirproj THEM THEM_0 ++ +
is not the same as + +
floss$ cvs import -I README.txt -I ! -m "some msg" theirproj THEM THEM_0 ++ +
The former ignores (fails to import) README.txt, whereas the latter +imports it. + +
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. + +
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. + +
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. + +
>
instead; otherwise, no log messages
+are retrieved.
+
+You may use <=
and >=
instead of <
and >
to
+indicate an inclusive range (otherwise, ranges are exclusive). Multiple
+ranges should be separated with semicolons, for example
+
+
floss$ cvs log -d"1999-06-01<1999-07-01;1999-08-01<1999-09-01" ++ +
selects log messages for revisions committed in June or August of 1999 +(skipping July). There can be no space between -d and its arguments. + +
This is different from the usual meaning of -R: "recursive". There's no +way to override a -l for this command, so don't put log -l in your +.cvsrc. + +
Finally, a lone -r, with no argument, means select the latest revision +on the default branch (normally the trunk). There can be no space +between -r and its argument. + +
If the argument to -r is a list, it is comma-separated, not +semicolon-separated like -d. + +
If the argument to -s is a list, it is comma-separated, not semicolon-separated like -d. + +
Remember that when user aliasing is in effect (see the section The Password-Authenticating Server in Repository Administration), CVS +records the CVS username, not the system username, with each commit. +There can be no space between -w and its argument. + +
If the argument to -w is a list, it is comma-separated, not +semicolon-separated like -d. + +
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:jrandom@floss.red-bean.com:/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. + +
Synopsis: logout + +
The opposite of login - removes the password for this repository from +.cvspass. + +
Options: None. + +
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. + +
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: + +
floss$ cvs -Q rdiff -s -D 1999-08-20 myproj +File myproj/Random.txt is new; current revision 1.4 +File myproj/README.txt changed from revision 2.1 to 2.20 +File myproj/baar is new; current revision 2.3 ++ +
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. + +
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: + +
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: + +
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. + +
Synopsis: status [OPTIONS] [FILES] + +
Shows the status of files in the working copy. + +
Options: + +
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: + +
Synopsis: unedit [OPTIONS] [FILES] + +
Signals to watchers that you are done editing a file. (See also +watch, watchers, edit, and editors.) + +
Options: + +
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: + +
.#file.rev
.
+
+Note: this option was implemented in January 2000; if your CVS was +acquired before then, you'd have to upgrade. + +
The special tags HEAD and BASE may be used as arguments to -j; they mean +the most recent revision in the repository, and the revision on which +the current working copy file is based, respectively. + +
As for the optional DATE arguments, if REV is a branch, it is normally +taken to mean the latest revision on that branch, but you can restrict +it to the latest revision no later than DATE. The date should be +separated from the revision by a colon, with no spaces, for instance: + +
floss$ cvs update -j ABranch:1999-07-01 -j ABranch:1999-08-01 ++ +
In this example, different dates on the same branch are used, so the +effect is to take the changes on that branch from July to August and +merge them into the working copy. However, note that there is no +requirement that the branch be the same in both -j options. + +
floss$ cvs update -p -r 1.3 README.txt > README.txt ++ +
Now README.txt in the working copy has the contents of its past Revision +1.3, just as if you had hand-edited it into that state. + +
This option is sticky. If the files are switched to a nonbranch tag or +sticky revision, they cannot be committed until the stickiness is +removed. (See -A.) If REV was a branch tag, however, commits are +possible. They'll simply commit new revisions on that branch. + +
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: + +
watch add
and watch remove
for that.)
+
+Options (for use with any watch subcommand). All three options have the +same meanings as for edit: + +
Synopsis: watchers [OPTIONS] [FILES] + +
Shows who's watching what files. + +
Options - these options mean the same thing here as for edit: + +
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$ ++ +
and CVS continues to keep the revision string up to date as new +revisions are committed. + +
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: + +
$Revision$ ++ +
and + +
$Revision$ ++ +
would both "expand" (okay, contract) to: + +
$Revision$ ++ +
$Revision$ ++ +
might become: + +
1.5 ++ +
Of course, after that's happened once, future substitutions will not +take place, so this option should be used with care. + +
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: + +
$Author$ ++ +
$Date$ ++ +
$Header: /usr/local/newrepos/myproj/hello.c,v 1.1 1999/06/01 \ +03:21:13 jrandom Exp qsmith $ ++ +
$Id$ ++ +
$Log$ +Revision 1.1 2002/06/23 14:32:50 llornkcor +some dev docs +Revision 1.12 1999/07/19 06:12:43 jrandom + say hello in Aramaic ++ +Any text preceding the $Log$ +
Any text preceding the Revision 1.1 2002/06/23 14:32:50 llornkcor +
Any text preceding the some dev docs +
Any text preceding the keyword on the same line will be prepended to the downward expansions too; this is so that if you use it in a comment in a program source file, all of the expansion is commented, too. + +
$Locker$ ++ +
$Name$ ++ +
$RCSfile$ ++ +
$Revision$ ++ +
$Source$ ++ +
$State$ ++ +
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. + +
checkoutlist
file.
+commitinfo
file.
+config
file.
+cvsignore
file.
+cvswrappers
file.
+editinfo
file.
+history
file.
+loginfo
file.
+modules
file.
+notify
file.
+passwd
file.
+rcsinfo
file.
+taginfo
file.
+users
file.
+val-tags
file.
+verifymsg
file.
+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. + +
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. + +
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: + +
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}.
+
+
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. + +
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.) + +
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: + +
=no
) - (Obsolete) This option is silently
+accepted for backwards compatibility, but no longer has any effect.
+
+=no
) - If yes
, CVS pserver
+authentication tries the system user database - usually
+/etc/passwd
- if a username is not found in
+CVSROOT/passwd
. If no
, the user must exist in
+CVSROOT/passwd
to gain access via the :pserver:
method.
+
+=no
) - If yes
, CVS tries to
+preserve permissions and other special file system information (such as
+device numbers and symbolic link targets) for files. You probably don't
+want to do this, as it does not necessarily behave as expected. (See the
+node Special Files in the Cederqvist manual for details.)
+
+=no
) - If yes
, checkouts create a
+CVS/
subdirectory next to each working copy tree (in the parent
+directory of the working copy). This can be useful if you will be
+checking out many working copies from the same repository; on the other
+hand, setting it here affects everyone who uses this repository.
+
+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.)
+
+
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
+Working Copy Files section in this chapter.)
+
+
This file is obsolete. Very. + +
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). + +
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.) + +
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: + +
If you use the -a option, you may exclude certain directories from other +modules by putting them after an exclamation point (!). For example + +
top_proj -a !myproj/a-subdir !myproj/b-subdir myproj ++ +
means that checking out top_proj
will get all of myproj
+except a-subdir
and b-subdir
.
+
+
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. + +
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. + +
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.) + +
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.) + +
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). + +
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. + +
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. + +
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
.
+
+
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.) + +
These are all the environment variables that affect CVS. + +
This is used in OS/2 only; it specifies the name of the command
+interpreter. It defaults to CMD.EXE
.
+
+
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. + +
Used in Kerberos-authenticated client/server access. + +
Specifies the program to use to edit log messages for commits. This
+overrides $EDITOR
and $VISUAL
.
+
+
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.
+
+
Recently obsolete. + +
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.)
+
+
Specifies the port number to contact the rcmd daemon on the server side. +(This variable is currently ignored in Unix CVS clients.) + +
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.) + +
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. + +
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.
+
+
Program to invoke for CVS on the server side. Defaults to cvs
,
+of course.
+
+
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. + +
Permissions for files and directories in the repository. (You probably +don't want to set this; it doesn't work for client/server anyway.) + +
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.) + +
(See $CVSEDITOR.) + +
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.
+
+
Obsolete. + +
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.) + +
(See $CVSEDITOR.) + +
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. + +
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. + +
+ +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:
+
+
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. + +
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. + +
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.) + +
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: woods@most.weird.com (Greg A. Woods) +Subject: Re: pcl-cvs maintenance status, stability of recent "release"s? +To: kfogel@red-bean.com +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. + +
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. + +
+ +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. + +
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 ++ +
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:newuser@newhost.wherever.com:/home/cvs/myproj ++ +
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. + +
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. + +
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. + +
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. + +
This script is apparently incomplete and possibly may never be finished. +(See the README file for details.) + +
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. + +
h
, --help
+
+Show usage (including a complete list of options). + +
-r
, --revisions
+
+Show revision numbers in output. If used in conjunction with -b, +branches are shown as BRANCHNAME.N, where N is the revision on the +branch. + +
-t
, --tags
+
+Show tags (symbolic names) for revisions that have them. + +
-b
, --branches
+
+Show the branch name for revisions on that branch. (See also -r.) + +
-g OPTS
, --global-opts OPTS
+
+Pass OPTS as global arguments to cvs. Internally, cvs2cl.pl invokes cvs +to get the raw log data; thus, OPTS are passed right after the cvs in +that invocation. For example, to achieve quiet behavior and +compression, you can do this: + +
floss$ cvs2cl.pl -g "-Q -z3" ++ +
-l OPTS
, --log-opts OPTS
+
+Similar to -g, except that OPTS are passed as command options instead of +global options. To generate a ChangeLog showing only commits that +happened between July 26 and August 15, you can do this: + +
floss$ cvs2cl.pl -l "'-d1999-07-26<1999-08-15'" ++ +
Notice the double-layered quoting - this is necessary in Unix because
+the shell that invokes cvs log (inside cvs2cl.pl) interprets the
+<
as a shell redirection symbol. Therefore, the quotes have to
+be passed as part of the argument, making it necessary to surround the
+whole thing with an additional set of quotes.
+
+
-d
, --distributed
+
+Put an individual ChangeLog in each subdirectory, covering only commits +in that subdirectory (as opposed to building one ChangeLog that covers +the directory where cvs2cl.pl was invoked and all subdirectories +underneath it). + +
Depends on: Bash + +
URL: http://www.volny.cz/v.slavik/lt/cvsq.html + +
Vaclav Slavik <v.slavik@volny.cz>, 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 Cyclic's 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. + +
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
.)
+
+
Many other third-party packages are available for CVS. Following are +pointers to some of these. + +
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 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 Cyclic +Software's summary of the CVSWeb scene at +http://www.cyclic.com/cyclic-pages/web-cvsweb.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/. + +
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. + +
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 +info-cvs@gnu.org 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. + +
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 +bug-cvsbook@red-bean.com if you're interested. + +
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. + ++ +
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. + ++ + + -- cgit v0.9.0.2