How-To: CVS crash-course
Back to the How-To Index ]

This is intended for people with some basic CVS experience already. As a supplement to this crash-course, you should check out the info page for CVS---it contains Everything You Could Ever Hope To Know About CVS Without Having To Read The Source Code. Or, in a word, EYCEHTKACWHTRTSC. It will do far more to increase your knowledge of CVS than I can in these pages. Its jokes are funnier than mine, too.

Generally speaking, the -d option to the cvs command is equivalent to setting the CVSROOT environment variable. However, if you are currently in a CVS working directory, and you are attempting to access a different repository, you may experience some strange behavior. The -d option overrides the CVS working directory you are currently in (if any), and the CVS working directory you are in (if any) overrides the CVSROOT environment variable. When in doubt, pass the -d option to cvs.

A note on options. Please be careful in passing command-line options. There is a difference between options to cvs and options to the requested CVS command. The CVS command lines you issue will generally take this form:

% cvs [cvs-options] command [command-options] command-arguments...

Contents:


cvsrc

Your ~/.cvsrc file contains command line options that are to be passed by default. For example, ~/.cvsrc might contain:

cvs -z4 -q diff -u3 -pN rdiff -u3 -pN update -P checkout -P tag -c release -d

This means that options -z4 -q are always passed to cvs, options -u3 -pN are always passed when using the CVS diff command, etc. When the command line arguments you actually supply contradict those in your ~/.cvsrc file, the ones explicitly specified on the command line take precedence.


Environment variables relevant to CVS

CVSROOT specifies the default path to the repository. The general form is:

:method:[[user][:password]@]hostname[:[port]]/path/to/repository
...but there are a variety of shortcuts---the most commonly used is that the "local" access method is the default. So a CVSROOT of "/home/limbo/myrepo" specifies a local repository.

CVS_RSH specifies the rsh binary to use when connecting via the "ext" access method. Luckily, ssh provides an interface similar enough to rsh for it to work, and it provides a useful data security and integrity layer to accessing your CVS repositories remotely. (rsh is unsafe for a variety of reasons.) So, we generally will want to set CVS_RSH to ssh---in fact, you will probably want to do so in ~/.login-mine or somewhere. If you want to pass specific options to ssh, you will need to create a wrapper script to perform that operation and then specify that script in the CVS_RSH variable.

CVSEDITOR specifies the editor to use to edit the commit log if you don't specify the -m option to commands that require it (commit and import). If you want to use emacs but put a lot of stuff in your ~/.emacs file, you may want to consider using "emacsclient" or "emacs -q" here. Other popular options include pico and vi. If you leave CVSEDITOR unset, CVS will use the setting in your EDITOR environment variable.

CVS_SERVER is something you'll hopefully never have to set unless you're doing something indefensibly hackish. It is the path to the cvs binary on the server side.

Please note that the above is an incomplete list of environment variables used by CVS. See the info page for more detailed coverage.


Creating your own CVS repository

Creating your own CVS repository is easy. To create a "myrepo" repository in your home directory:

% setenv CVSROOT $HOME/myrepo % cvs init

You will notice that a directory has been created with that name and that a directory exists inside it called "CVSROOT." Information on the structure of CVS repositories can be found below, as can information on the structure and features of the CVSROOT directory.


Review: import, checkout, commit, update

Importing files into a CVS repository (as given by CVSROOT):

% cd my-sources-to-import % cvs import -m "initial import of sources" repo/path/to/import $USER start

Checking out files into a working directory from the repository (as given by CVSROOT):

% cvs co repo/path/to/checkout

Committing ("checking in") changes you've made in a working directory back into the repository:

% cvs ci -m 'This is my **detailed** commit log! :-P'

Updating your working directory from the repository (in case changes have been committed by other people since your last checkout or update):

% cvs up

Updating as above and also acquiring directories not already checked out:

% cvs up -d

To get CVS to do nothing, pass the -n option to CVS (not to the individual CVS command!---See A note on options, above). This can be incredibly useful if you want to see quickly, for example, what files need updating in your working directory without actually performing any updating or merging. When passed the -n option, CVS is directed not to "execute anything that will change the disk."


Ewwww, gross!! Stickiness

When you check out (or update) a specific symbolic (or numeric) revision of a file or directory, various aspects of the checkout (or update) can become sticky. This means that on subsequent updates, the file or directory will not be updated from the head.

This is particularly useful if you use CVS branching (see Symbolic Tagging and Branching, below), because any updates and commits you perform in the working directory will be automatically associated to that specific branch.

However, you need to be aware of this stickiness because it doesn't always give you what you intuitively think you're getting. If you do an update, you don't always get the newest version that somebody just checked into the head of the repository. If you really want to update from the head (no side branch, and avoiding stickiness), do this:

% cvs up -A

The -A option explicitly discards all sticky tags. This includes the branch associated to your working directory, so be sure that this is really what you want if you've been editing a branch.

Or, to be even more "complete" in your update, you can do this:

% cvs up -dA

Using the -d option grabs new directories that are in the repository but not in your working directory. (If you also use -P though, you won't get empty directories.)


Exporting your CVS project

Perhaps you haven't seen this before: you can also export a CVS directory. This is exactly like checking out a directory, except that when you export, you don't get a working directory so you can't in the future check in any changes you make. Technically this means you don't get CVS directories in the exported copy (see CVS working directory file structure, below).

Exporting is particularly useful if you need to grab a copy of your sources for outside distribution, for example. To export, do something like this:

% cvs export -D 2003-05-20 my-cvs-project

Note that a date (with -D) or a revision number or tag (with -r, seeSymbolic Tagging and Branching) is required with the cvs export command.


"cvsignore" files

Files that you never want committed into the repository or listed as "?" in update listings can be named in a ".cvsignore" file in the appropriate directory. The ".cvsignore" file format is quite liberal---any sequence of spaces, tabs, and/or newlines can be used to separate file names, and wildcards may be used to exclude, for example, files with a particular extension (as in "*.class").

Files can also be ignored using the -I option to CVS commands like import and update. Some files are automatically ignored by CVS, including files named "core", files with a standard backup extension (for example, those ending with a ~), and object files ending in ".o".


File listing legend

When you issue a cvs update command, it gives you a single-character status on every file that it isn't ignoring (see "cvsignore" files above). These status characters are:

U - file has been updated (no local modifications, so no merge necessary)
P - file has been "patched" (a bandwidth-efficient version of "U")
A - file has been locally added but not yet committed
R - file has been locally removed but not yet committed
M - file has been locally modified but also updated elsewhere---changes were merged (or were previously merged) with no conflicts---see additional output for merge details
C - conflicts detected in file during merge (or previous merge, and the conflicts haven't been fully resolved yet)---see additional output for details---also see instructions on resolving conflicts below
? - file is in working directory, but the repository knows nothing about it---good candidate for adding (via cvs add) or ignoring (via "cvsignore" files)


Conflict resolution

So you have a conflict. Now what?! A conflict means that changes were made concurrently to the same part (usually the same line) of the same file. Luckily, CVS handles these conflicts relatively gracefully. It backs up your current working-directory version and inserts context diff symbols into the file.

To resolve a conflict, open the offending file and search for "<<<<<<<"---this will take you to the first conflict region. Merge the file changes manually as necessary. Note that there may be several conflicts in the file.

The emacs vc ("version control") package has a conflict resolution mode---see documentation on the vc-resolve-conflicts function (C-h f vc-resolve-conflicts).


Review: CVS minor mode for emacs & key bindings for common CVS operations

When you open a file under CVS control, emacs installations generally are set up to notice it and plop you into "version-control minor mode." This gives you a CVS revision number on the status line.

The most heavily-used commands offered by version-control minor mode are:

C-x v v - perform the "next action" (checkin/update)
C-x v = - show local modifications (diff)
C-x v i - register new file under version control (add)
C-x v g - annotate (see below)


status and log and annotate...oh my!

Querying status. For human-readable status information regarding a file in your working directory, do this:

% cvs status file

You can also get a verbose listing which includes tag information (see below for information on CVS symbolic tagging) by passing the -v option to the command.

Getting the log. Remember those commit comments you're supposed to leave whenever you commit a file? Those comments where you always say "fixed stuff" or "foo" when you should leave more detailed comments? Well, they are all stored away in the repository (see CVS repository structure, below) and you can retrieve them using the CVS log command. Generally, you do this:

% cvs log file

However, there are two parts to this output---the header and the revision listing. If you want just the header, do this:

% cvs log -h file

The header includes important information about the file, like the current head revision and the symbolic tags associated with this file in the repository (and the revision numbers to which they correspond)---this is sort of like doing a cvs status -v on a file.

The revision listing gives a slip of information for each commit of the file, including date of the commit, revision number, author, and number of lines changed.

You can also get the log of multiple files by specifying them on the command line, or even on the whole current directory (recursively) by omitting the file name.

There are a variety of options available on the log command. I suggest trying cvs log --help to familiarize yourself with them.

Annotating a file. Annotating a file shows the file line by line with the revision, date, and identity of the committer for the last change of each. It's a great tool to find out who added a particular method to a class or who made that stupid change that broke the method you wrote a month ago. :-)

You usually use it like this:

% cvs annotate file

However, you can also specify a particular date or revision of interest, or annotate an entire directory. It's also handy occasionally to annotate a file within emacs---see emacs commands, above.

Getting repository history. Think of this as cvs log on steroids. You can (if enabled in the CVSROOT directory of the repository) find out what everyone has ever done with the repository. With the -e option, every update, export, release, merge, conflict, modification, addition---everything is held open to view:

% cvs history -e

See cvs -H history or the CVS info pages for more information on this command.


CVS locking -- how does it work and what if it doesn't?

The word "locking" refers to a few distinct things in CVS.

CVS internal locking. CVS locks its internal structures in accordance with The Principle of Least Surprise. Or, at least, it would be nice if it did. If Bob were to check out the current version of a repository module and get half of the files from Joe's commit and half of those from Mary's commit, Bob would be thoroughly confused. Hence, CVS should provide some kind of guard against this. It does not. Beware.

However, some egregious things are actually stopped by CVS internal locking. Usually, this locking is completely transparent, so you don't even need to know it's there. You will, however, occasionally see a message like the following one:

[12:19:01] waiting for bob's lock in /project/repo

When this happens, CVS will pause for thirty seconds, then retry. If you get this message for a very long time, find the person whose lock is offensive to you and interrogate them. If they don't know what's going on any better than you do, then perhaps something weird happened and a stale lock is around somewhere. Find someone to manually remove it from the repository.

Please refer to the info page for CVS for more information.

Explicit revision locking. CVS also offers explicit file locking. This can be used to enforce reserved checkouts in which only one person can edit a given file at once. (This is different from the normal CVS model, where anyone who wants to commits changes to files, and the changes are merged as appropriate.) See the info pages for details.


Symbolic Tagging and Branching

CVS tagging is a powerful feature that is incredibly important for a variety of reasons. Let's dig right in.

The simplest use of CVS tagging is to give a symbolic name to the current revision of a file:

% cvs tag TAG files...

Or, if you don't have a working directory handy, you can do an "rtag" ("remote" tag):

% cvs rtag TAG path/to/module/files...

But even better is to leave off the file names and symbolically tag the whole directory. Think of it this way: you are packaging the current version of your CVS project in a way that you can easily refer to later. For example, if you're working on your Master's thesis, and you submit your manuscript for format verification, you might tag your thesis source directories with a tag of "format_approval", then later "final_revision". Or, if you're submitting a journal paper, you might tag your paper's sources with the tag "submitted", then maybe later with "revised" and/or "published". Your software projects might be tagged with "1_2_release" or "beta_2_3". This way, you can easily get back to that same version later, even after further commits.

Please note that tag names cannot contain periods! They can only contain uppercase and lowercase letters, digits, underscores (_), and hyphens (-), and must start with an uppercase or lowercase letter. The tags BASE and HEAD are reserved for CVS's use, so you cannot use them with the cvs tag or cvs rtag command.

This is more powerful than it may at first seem, because the symbolic tag is an alias for different revision numbers in different files. In effect, it can be thought of as a cross-section of the repository at a particular time. I liberally tag my CVS projects at important moments in their lifecycle.

But what if you incorrectly tag something and you want to move the tag later? You can do so, but I recommend that you be incredible careful as one slip could move the tag for every file.

To move a tag, use the -F option:

% cvs tag -F TAG files...

To delete a tag (for goodness' sake, BE CAREFUL!), use the -d option:

% cvs tag -d TAG files...

Passing the -c option to cvs"tag directs it to verify that all files being tagged are the most recent ones. It flags an error and tells you to update if this is not so.

Branching. Symbolic tagging is nice, but there's an even more powerful facility available through cvs tag.

INCOMPLETE


Getting diffs, watching, etc...

Diffing. There are, generally, two ways to get "diffs" out of CVS:

% cvs diff file.c % cvs rdiff -r 1.5 myproject/src/file.c

The first (with the command diff) is dependent on the working directory; the second (with rdiff---"remote" diff) does not require a working directory, and because it doesn't, it needs at least one revision number (or date) as a reference point.

If you don't supply an -r or -D option to cvs diff, CVS compares your working copy and the revision it was based on. If you supply an -r or -D (specifying a revision number/tag or date, respectively), CVS compares that revision with your current working copy of the file. You can also supply two revision numbers (or symbolic tags, or dates)---this tells CVS that you want the differences between the two specified revisions (your current working copy is irrelevant).

The order of the -r and -D options are significant---you can use this to obtain "backward" patches.

You can also pass standard arguments to cvs diff that are understood by your diff installation. This allows you to get nice unified diffs with a customizable number of lines of context, function names, etc.

Using cvs diff, you can generate appropriate patches between different revisions of files for use with the patch utility.

Watching. What if you want to know when a file has been modified, and you don't want to check manually by continually polling the repository (or writing a script to do so, since we're all computer geeks we just love to spend hours perfecting programs that will save us a few seconds, don't we? ;-))?

Well, lucky you. CVS has watchers. That is, if you issue the following commands, in order:

% cvs watch on file.c % cvs watch add -a commit file.c

Then you will be notified whenever someone commits the file file.c. At least, theoretically it does. The appropriate action must be specified in the notify administrative file in the repository CVSROOT directory (see below).

To stop watching a file, issue these commands:

% cvs watch remove -a commit file.c % cvs watch off file.c

What is the significance of each of the two commands? I'm glad you asked. The cvs watch command registers a general interest to watch the file; technically, its effect is to check out the file read-only in everyone's working directory. To make the file read/write once you've checked it out, you issue the command cvs edit file.c. After finishing your edits, you run cvs commit as normal, or, only if you decide to abandon your changes, you issue the command cvs unedit file.c. Each of these steps generates a potential watch notification. See the CVS info pages for more information.


Merging branches

INCOMPLETE


CVS string substitution in files

CVS does perform some automatic string substitution in your files. This is called "keyword substitution" or "keyword expansion." It takes a little getting used to, but it can be quite useful. Keyword substitution takes place between dollar signs ($). The most commonly-used string substitution is $Id$, which expands to something like the following:

$Id: index.html,v 1.8 2004/04/05 21:29:23 mdeters Exp $

This gives the repository RCS file name for the file (see CVS repository structure, below), its revision, its date and time of last modification and the author, and, in this case, "Exp" (the "state" of the file---"Exp" means "Experimental," as opposed to "Stable," "Release," etc.).

This is cool because if you export your sources (see above), you still know the revision of the files with such $Id$ tags in them. Further, if you do something like this (in Java):

public static final String id = "$Id$";

Then the resulting binary (class) file will have the identifier stored in it so that you can track the source revision that created it.

Other keywords can be substituted, too, like $Revision$, $Author$, $Date$, etc. For a full list, see, as always, the info page for CVS.

You can avoid keyword substitution, too, if you decide it will cause problems in your file (for example, if you are checking in a binary file likely to contain a keyword). To check out a file with a particular keyword substitution mode, supply an appropriate -k option to checkout or update. To change the substitution mode in the repository, supply an appropriate -k option to the add or admin commands. To get a brief list of the keyword options available, do cvs -H admin or...you guessed it!...see the CVS info pages.


CVS working directory file structure

Ever noticed a directory called "CVS" lurking about in your working directories (and all their subdirectories)? Good! You're observant!

That's where CVS stores its working directory information. If you have a sticky tag associated with one or more files (see above for information on tagging), or the whole directory, it is stored here. All files checked out (with their revision numbers at the time of checkout) are listed. The repository address is listed, and the module path. This way, even if you hose your CVSROOT environment variable and can't remember the module name, you can still update or commit your working directory. This is very nice. The working directory itself stores the pointer back to the appropriate repository and context.

Anyway, the Root file stores the appropriate CVSROOT, the Repository file stores the appropriate module path, and Entries contains information about the files and directories checked out. You will notice from time to time other files popping up in this directory, for example when you add a file or checkout (or update) a directory with a sticky symbolic tag.


CVS repository structure

NOTE: For informational purposes only! Do not access the DOC group CVS repository directly, or you will be strung up by your toenails! (And I will too, for having told you about all this.)

There are two main parts to the repository structure---the CVSROOT directory (described separately, below) and everything else.

"Everything else" consists of all of the files you've put under CVS control---everything you've ever imported or committed is here. The structure is pretty simple---directories exist as you would expect them, hierarchically. Each file under CVS control, however, looks a little different than when you check it out.

That's because each file is actually stored as an RCS file---all of the information about all the file's revisions, all its commit scripts, all its associated attributes, etc.;

Files are never actually removed from the repository. Never. Instead, they are placed into the "Attic"---a special directory in each directory in the repository that contains deleted files. This way, you can access old files that were once part of your software project (or whatever), even though they no longer exist in the current version of the project.

Likewise, directories are never removed from the repository. But they are handled differently than files---instead of being placed in the "Attic," they are simply left in the repository empty (or, perhaps, with a constituent "Attic" directory). If you pass (as a matter of course, or in your ~/.cvsrc file) the -P ("prune") option to checkout and update, you never see these empty repository directories in your working directories.


CVSROOT directory stuff---commit scripts, etc.

You can heavily customize a repository in a variety of ways through the CVSROOT directory in the repository. But first, you need a copy, and why not just check it out like anything else?

% cvs co CVSROOT

(This checks out some configuration files from CVSROOT, but not everything.) Now, anything you modify and commit will take effect (the CVSROOT directory embedded in the repository is automatically updated).

Let's look at some of the files in CVSROOT.

checkoutlist: additional files to be treated as part of CVSROOT for checkout purposes (for example, scripts)

commitinfo: pre-commit authorization (for example, coding style enforcement)

config: various configuration directives (you can usually ignore this)

cvswrappers: automated detection of whether or not keyword substitution is appropriate (based on file name)

history: stores the history used by the cvs history command. If the file doesn't exist, no history is kept.

loginfo: post-commit/import processing (for example, keeping a checked out copy)

modules: aliases and options for modules; provides post-commit, -checkout, -export, -rtag, and -update processing, and other options

notify: customizes how watch notifications occur

rcsinfo: log message template specification

taginfo: pre-tagging script (can abort tagging operations)

verifymsg: evaluates commit/import log messages (for example, require a detailed log message including bug ID or whatever)


Morgan Deters / Last updated: 15 August 2005