Mark's CVS FAQ

Answers colored red indicate stubs that I plan to flesh-out when time permits and feedback requires

Questions I need to add & answer:

Contents


1.0 Introduction

Why should I care about CM or CVS?

You should care about CM if you fit any of these descriptions: If any of the preceeding applied to you, then you need to learn about CM, a fascinating and diverse niche of softare tools which are specifically designed to meet those and other needs.

Moreover, if you haven't yet picked a CM tool, then you should probably take a look at CVS if:

That's why.


2.0 Concepts and History

What is CM?

CM stands for Configuration Management. In abstract, Configuration Management refers to a set of procedures used to track document changes. More specifically, it typically describes a software program or programs used to maintain a library of computer files, especially source code.

The goals of CM, briefly, are these:

Some common examples of commercial CM packages include Microsoft
SourceSafe, PVCS, Razor, Rational ClearCase, etc. In the Unix freeware arena, CM historically meant a choice between RCS and SCCS. In addition, countless teams of developers have built proprietary in-house CM systems using the 'make' utility, but they tend to be overly complex and difficult to maintain.

However, the recent explosion of Linux and "open-source" development has made the RCS-derivative CVS undisputedly the most widely-used, though perhaps not best-loved, Software CM (SCM) tool in the world.

Some good general-purpose CM links:

What is RCS?

...and why should I care? Well, if you're reading this document, then you should have a minimal awareness of RCS because CVS is nothing more than a glorified wrapper around RCS. It is occasionally significant that CVS still stores repository files in basically the same format as pioneered by RCS, and to know that RCS-based tools like "ident" work perfectly well on CVS-managed applications. Also, occasional oddities in the CVS implementation, which may seem strange or unintuitive in isolation, may make more sense in consideration of its RCS origins and legacy compatibility requirements.

RCS, or Revision Control System, is the suite of CM tools based on /bin/ci (Check In), /bin/co (Check Out), rlog, ident, etc. Basically, RCS allows developers to save a history of changes made to a file. This allows users to resurrect previous versions of a file, or to generate a list of exactly what changes were applied between specific versions, or even to merge together two sets of changes made to different "branches" of development.

RCS is easy to use, but (like 'make') it gets a little complicated when used to manage source code spanning more than one directory, and it completely falls apart if the code is being developed/tested on more than one computer. It is the lack of network support which really undermined RCS's post-internet utility. Collaboration is now king, and that was the fundamental innovation CVS added to RCS.

Therefore, for historical interest, here are some good RCS links:

What is CVS?

CVS is a cross-platform, network-centric Configuration Management (CM) tool, based on RCS, which is used to automate the management of large source code trees being simultaneously modified by multiple developers.

Basically, CVS is a front-end to the ancient RCS toolset which ships with virtually every Unix distribution in the world. To RCS, CVS adds the ability for multiple developers to collaborate over a network. Each user is able to checkout their own "working" copy of the code tree, and modify, compile, and test the programs at-will. Each user may checkout their tree onto their own computer, or simply into their own directories on a single server. The point is that each user has a full working copy of the complete source code, so their short-term changes don't overwrite or obstruct one another.

Then, when the developer feels they have finished whatever changes they were making, they can "check in" their version of the file, which applies their changes back to the master code tree. Anytime a developer checks out a new copy thereafter, or "updates" their current working tree, the new version will be automatically (but safely) distributed to everyone who should receive it.

All this is made possible through the use of the "repository." The "pure, original" version of the code, along with a complete history of every change ever made to every file (along with metadata such as who made the change, when they made it, and ideally why the change was applied) is stored in a single repository on a commonly accessible networked computer.

This repository may be thought of as a "black box", a database that "simply is", but for the record it's nothing more than a nested set of directories and files which exactly matches the source code tree, except that each file is stored as an RCS-formatted series of 'diff's. It's noteworthy that the repository is "just a bunch of files" because that greatly simplifies the task of backing up the repository (just tar it up), verifying access privileges, etc.

The remainder of this document describes how to make use of these features, and how to handle certain predictable points of confusion.

Find additional CVS documentation, tutorials, and references at the end of this document.


3.0 Basic Usage

How do I setup my computer to access an existing CVS repository?

  1. Find out where your CVS repository is (ie, on what server, in what directory). In the case of PRISM, it's on cvs-oa.orl.lmco.com (ie orlsr007464), in /usr/local/cvsroot.

  2. Find out what your username is on that server (ie, mzieg, josh, etc).

  3. Test that you can reach the server, like so:

    C:\> ping cvs-oa
    If you can't ping the CVS server, you're probably not on the unclassified Lockheed network, and shouldn't be attempting this procedure.
  4. [REPLACE WITH SSH INSTRUCTIONS]
    Configure rhosts-based authentication between your client and the CVS repository server:

    1. find out your username and hostname on your client machine. Under Cygwin, your username should be the same as your NT login name. (You can confirm that by opening a Cygwin window and typing "echo $USER". If you get "Administrator", then you probably need to use the Cygwin "mkpasswd" utility to properly configure /etc/passwd, as described here.) Your hostname can be derived from the asset (inventory) sticker on your computer, ie "orlsr007296", etc.

    2. login to the CVS server (ie, "cvs-oa", either at the console or via telnet), and create an .rhosts file in your home directory1. The file should contain a line of the form "hostname username", ie:
      orlsr0072962     ziegm3
      (Make sure the file is not writeable by anyone other than yourself (ie, chmod 644 ~/.rhosts), or rlogind will refuse to trust it. --Josh)

      1 remember, this .rhosts file should be in your home directory on the server (ie, cvs-oa)

      2 remember, this should be the domain name of your client (ie, desktop) computer

      3 remember, this should be your username on your client (ie, desktop) computer

  5. [REPLACE WITH SSH INSTRUCTIONS]
    Verify that you can rsh from your desktop to the server, like so:
    $ uname -a
    $ rsh mzieg@cvs-oa uname -a
    If the rsh command above fails, then confirm that you have followed the .rhosts instructions correctly. (Also, if you're trying to rsh from Cygwin, then make sure that rsh/rlogin/etc are installed via the "inetutils" package.)
  6. [REPLACE WITH SSH INSTRUCTIONS]
    From any Unix-like OS on the unclass network (Linux, SGI, Solaris, or Cygwin), export the environment variable CVSROOT as follows:

    This would be a good thing to do in your .bash_profile, .login, etc. Again, username in this case would be your username on the host which contains the repository (ie, your login on cvs-oa).

  7. Attempt to checkout the current code base as follows:
    cvs checkout project_name

How do I check code out of the repository?

The way CVS works, you generally don't check out just "portions" of a project; you check out the whole schebang.

What you or I might call a "project" or an "application" -- ie, a complete collection of code and files that are interrelated and typically distributed together -- CVS calls a "module". For instance, "PRISM" is one module in my current CVS repository.

To checkout a module, then, you use the checkout command, as so:

cvs checkout PRISM
This will create a PRISM folder under your current directory and extract the entire nested PRISM tree into it (starting with BasePrograms, BaseData, and Docs at the top, then all of their contents).

(See here for detailed options)

(See here for the Fogel docs)

How do I see if and where my local copy of the code base differs from the current repository version (but without changing anything)?

see cdir.

cvs -n update will recurse down through the current directory, comparing each file against what is in the repository. If CVS detects that you have made your own modifications to a file, it will print that filename preceeded by an M (modified) code.

It's kind of like find ., except that a code will preceed each file to let you know how it compares to the version in the repository. Click here to see the full list of codes which may explain what is learned about each file.

To build on that, a fast and simple way to find out which files you've modified, but not-yet commited back to the repository, is to cd to the root of your working tree ('cd $MAKE_HOME' or whatever), and type:

cvs -n update 2>&1 | grep ^M
The key argument here is the -n, which tells CVS to do "nothing" (or "no action", or whatever). You'll note the location of the option, between the program name ("cvs") and the command verb ("update"). This is significant and initially confusing, so if you haven't already read it, take a look at some potential gotcha's in the CVS command-line syntax.

Generally, it's quite safe to cvs update your code tree without the "-n" option; by itself, CVS shouldn't do anything that would overwrite or destroy any changes you've made in your local copy. (See here for the Fogel docs)

How do I update my local working copy to the latest version of all files?

As above, but just use cvs update.

(See here for detailed options)

(See here for the Fogel docs)

How do I update code which is already in the repository?

Assuming that you have checked out your own working copy ("sandbox") of a project, then you can commit your changes back to the repository using:
cvs commit <filename>
(See
here for detailed options)

If you get an error message, skip down to the next section.

If there are no errors
Assuming there are no errors, a vi session will pop up (or whatever program is specified in your EDITOR environment variable), looking basically like this:


        CVS: ----------------------------------------------------------------------
        CVS: Enter Log.  Lines beginning with `CVS:' are removed automatically
        CVS:
        CVS: Added Files:
        CVS:    flyer.cpp
        CVS: ----------------------------------------------------------------------
    
This is where you specify a log comment for your commit. You can type whatever you want into this window. As the message says, any lines starting with "CVS:" will be ignored, so the window is essentially blank.

Sample log messages are usually things like:

Note that you can commit many files at once. There are two main ways to do that.

One is by just typing "cvs commit" by itself. This will recursively commit every file in the current directory, and all subdirectories, which have been modified since the last checkout/update. If you're in a top-level directory, this can commit a LOT of files, so use with care (although, as with most CVS operations, it is very difficult to accidentally do any real damage, since undesired commits can always be subsequently backed out or undone.) The other is by specifying multiple filenames on the command line, such as:

For either of these methods, whatever log message you provide will be applied to all files.

A command-line trick to skip the vi session is to specify the log message in the commit message, as with:

cvs commit -m "added --verbose cmd-line option" tracker.c loader.c
If there is an error
This is largely a shorter form of the discussion on CVS's copy-edit-merge paradigm found in the section on Locking vs Merging.
Here's what will happen:

CVS will check to make sure that the version of the file you had when you started editing (ie, when you last did a "cvs checkout" or "cvs update") is still the most recent revision in the repository.

That is, if tracker.c was at revision 1.3 when you checked out your sandbox, and you've been making changes to revision 1.3, then CVS will check to see that nobody else has subsequently committed revision 1.4 (or 1.5 or 1.6) of tracker.c.

If the repository version of tracker.c is indeed still at 1.3, then CVS will commit your changes to the repository. tracker.c will be updated to revision 1.4.

However, if someone has slipped in between your last checkout/update and updated tracker.c "behind your back", then CVS will report an error. You're not allowed to commit changes made to tracker.c 1.3, if someone else has already updated tracker.c to 1.4, because there can't be "two version 1.4's". Instead, CVS will report an error, and tell you that you need to update your copy of tracker.c.

You do that by typing "cvs update tracker.c". This will not lose the changes you have just laboriously (and brilliantly) applied to your version of tracker.c! This will merge your changes to tracker.c 1.3 with "Bob's" changes to tracker.c 1.3. The result will not be "your" version of tracker.c, nor Bob's version, but the combination of both.

Basically, the result will be the same as if you had not made your changes to tracker.c 1.3, but to tracker.c 1.4. Literally, CVS will use "diff" to determine what changes you made to version 1.3; then it will grab its own copy of 1.4; then it will apply those same changes (your changes) to version 1.4.

(Can this magical merge process fail? Of course it can. See the section on conflicts if you get an error during a "cvs update".)

So now you do the "cvs commit" again...and this time it succeeds. It succeeds because this time, when CVS looks at it's metadata for your working copy of tracker.c, it sees that your changes were applied to revision 1.4, which is indeed the current repository version. Thus the merged version (with both your and Bob's changes) is commited back to the repository as revision 1.5.

How do I add new code into the repository?

Assuming that you are within an existing project ("module", "sandbox") which has been checked out of a CVS repository, you can add new files and directories via:
cvs add <filename>
(See
here for detailed options)

If you have added a new directory, beneath which are additional files and/or directories, then you must add them in hierarchical order (ie, the same order in which nested subdirectories must be created with "mkdir tree", "mkdir tree/bough", "mkdir tree/bough/twig", etc):

  1. cvs add tree
  2. cvs add tree/*
  3. cvs add tree/*/*
Note that adding a file to the repository does not automatically commit that file (although intuitively it probably should). Therefore, you still have to commit the "1.1" version of your newly added file, in the normal way.

Please note that not all files need to be in the repository. There is typically no point in adding files with any of these extensions, because it is almost guaranteed that they will be of no use to anyone:

(See here for the Fogel docs)

I did a 'cvs update', but I'm still missing some new code! I know it was added and commited to the repository! Where is it???

By itself, "cvs update" only updates directories you've already checked out. If the new files also happen to be inside new directories, then "cvs update" by itself will not (by default) automatically "checkout" those new directories.

To "update my current tree, including checking out any new additions" (what you probably mean), you need to use:

cvs update -d
(note that this might be a handy option to add to your
~/.cvsrc)

How do I compare my version of a file with earlier releases?

The basic command is:
cvs diff <filename>
(See
here for detailed options)

Note that this command can be a little tricky, and some people find it's default behavior counterintuitive, so note the next section if you get confused.

By default, cvs diff filename will show how filename differs from it's "sticky version" (the version last extracted from the repository via "cvs checkout", "cvs update", or "cvs commit").

That is, in a nutshell, it will show you what changes you have made to that file, since you last synced it from the repository. In most cases, that's probably what you want.

However, CVS can provide diffs between any arbitrary versions of any file under version control.

Scenario One
A user (or another developer) has complained that, since they upgraded to the latest distribution, they are starting to get frequent crashes under SGI. They say the program worked fine off their old CD.

You ask the user what the dates were on their previous, working CD and their new, crashing one. He says the previous distribution was labeled "2002-06-22" and the latest was made "2002-08-15". You check the history of tracker.c using "cvs log tracker.c":

    cvs-oa [~/PRISM/BasePrograms] mzieg $ cvs log tracker.c

    RCS file: /usr/local/cvsroot/PRISM/BasePrograms/tracker.c,v
    Working file: tracker.c
    head: 1.7
    branch:
    locks: strict
    access list:
    symbolic names:
    keyword substitution: kv
    total revisions: 7;    selected revisions: 7
    description:
    ----------------------------
    revision 1.7
    date: 2002/09/04 14:47:40;  author: josh;  state: Exp;  lines: +1 -1
    fixed GUI display under SGI
    ----------------------------
    revision 1.6
    date: 2002/08/29 18:13:55;  author: josh;  state: Exp;  lines: +18 -3
    added debug option
    ----------------------------
    revision 1.5
    date: 2002/06/25 19:02:46;  author: mzieg;  state: Exp;  lines: +7 -4
    *** empty log message ***
    ----------------------------
    revision 1.4
    date: 2002/06/25 19:00:34;  author: mzieg;  state: Exp;  lines: +11 -4
    compiles under Cygwin!!!
    ----------------------------
    revision 1.3
    date: 2002/06/20 19:42:07;  author: mzieg;  state: Exp;  lines: +22 -11
    more fixes
    ----------------------------
    revision 1.2
    date: 2002/06/19 16:54:16;  author: mzieg;  state: Exp;  lines: +9 -6
    testing pointer logic
    ----------------------------
    revision 1.1
    date: 2002/06/19 16:53:20;  author: mzieg;  state: Exp;
    Initial CVS release
    =====================================================================
    
What does this tell us?
  1. the user's previous distribution was presumably cut with revision 1.3 of tracker.c
  2. the user's broken distribution was probably using tracker.c 1.5
  3. tracker.c is now at 1.7
  4. presumably something bad happened between 1.3 and 1.5 to cause the crashes
So we wonder, just what was done between those versions? The log messages weren't exactly descriptive, so we check the source:
cvs diff -r1.3 -r1.5 tracker.c
(sorry, I'm not going to make up sample output :-)
Okay, that showed us exactly what changes were made to the source between those versions, and sure enough there was a change made in a glutCreateWindow call, referencing an uncasted NULL, that looks like it could provoke a crash.

We open up a current version of tracker.c in our text editor, go to that line, and...it's different! The NULL is gone, and instead the address is being taken of a global variable. That was the same change you were going to make!

You wonder when that happened, so you check with CVS:

cvs diff -r1.5 -r1.6 tracker.c
Nope, that was in another part of the program...
cvs diff -r1.6 -r1.7 tracker.c
Ahah! There it is. Somebody had already made that fix.

So then you check the log output again, and now you actually read the log message for revision 1.7, which already mentions fixing that bug. It was a known problem that has already been fixed, and all you need to do is send a fresh distribution to the user. Issue solved!

'cvs update' says my file is out of date, but 'cvs diff' won't show me what's been changed! What gives?

Use this:
cvs diff -rHEAD [filename...]
Explanation: by default, 'cvs diff' will show you how your current tree (or just particular file(s)) differ from their base version.

Let's say that you checked out a current code tree last week, which happened to include rev 1.13 of myfile.c. Over the next few days, Bob and Jane made various tweaks to myfile.c, so the repository version is now at rev 1.15.

You do a 'cvs -n update' and see that "U myfile.c", so you know it's been updated. However, when you do "cvs diff myfile.c", it returns... nothing. Hence the confusion.

This is because CVS remembers, via it's "sticky tags" stored in ./CVS/Entries, that the version of myfile.c which you checked out -- either explicitly or implicitly -- was version 1.13. When you do a 'cvs diff', it compares your working copy against version 1.13, and shows that, sure enough, your copy of revision 1.13 exactly matches the repository's copy of revision 1.13.

What you probably actually want -- and what might make for a more reasonable default behavior of 'cvs diff' -- is to diff your version against the most recently committed revision of myfile.c.

The "-r HEAD" argument to 'cvs diff' will do that for you, because it explicitly says "diff my working copy against the head of my current branch." "HEAD" is an "implicit tag", automatically maintained by CVS, which points to the latest version of each file in each branch. No matter which branch you specified when you checked out your working tree (including the "trunk" if you didn't specify a branch), "HEAD" is available as an automatic tag pointing to the latest revisions of every file.

Note, therefore, that even "-r HEAD" may not diff your file against the chronologically most recent version of your file, because someone else might have committed a newer update to "myfile.c" in a different branch.

If you really understand what you are doing, then you might choose to add a line like "diff -rHEAD" to your .cvsrc file, so that this will become the default behavior. Beware, though, that that might have unanticipated consequences. For instance, if create such a default, and then forget, you might inadvertently type "cvs diff -rHEAD myfile.c" at the cmd-line.

What will this do? Well, since 'cvs diff' (like many cvs commands) will accept two -r arguments (to show the difference between two arbitrary revisions, ignoring your working copy), then what you will actually request is "cvs diff -rHEAD -rHEAD myfile.c" (ie, the difference between a thing and itself), which is always guaranteed to be...nothing. Therefore, this probably isn't a good choice for automating via .cshrc.

How do I checkout, or "rollback" to, an older release of a file?

If you're trying to simply throw away any changes you made to your local copy, which have not yet been committed back to the repository, and make your working copy exactly equal to the latest revision in the repository, then you can use either of these methods:
cvs update -C filename
or
rm filename && cvs update
Otherwise, perhaps you want to rollback to a previous revision. For instance, perhaps foo.c is currently at 1.12, but you want to go back to revision 1.10.

More than that, you want to get rid of revisions 1.11 and 1.12 altogether; they represent a demented design that you want to just erase from history.

Well, you can't. Erase them, that is. The entire point of a revision-control system is to preserve every revision of a file, even the ones that are later proved to be stupid and useless.

However, what you can do is bring the older version "forward" and place a fresh copy of it on the head of the file's revision tree. That is, you can make a new revision of foo.c, say 1.13, and make that exactly equal to the older 1.10. Thenceforth, all edits and updates will be made to 1.13 (to 1.14, 1.15, etc), which is effectively what you want.

You can do this with the following procedure:

  1. establish what version of the file you actually have:
    cvs status filename
    Example:
    $ cvs status acq.c
    ===================================================================
    File: acq.c Status: Up-to-date

    Working revision: 1.13 Wed May 7 15:42:39 2003
    Repository revision: 1.13 /usr/local/cvsroot/PRISM/src/Acquire/acq.c,v
    Sticky Tag: (none)
    Sticky Date: (none)
    Sticky Options: (none)

    In this case, we see that acq.c is at revision 1.13, and that our working copy is up-to-date with the repository.

  2. establish to which version you wish to rollback:
    cvs log filename
    Example:
    $ cvs log acq.c
    RCS file: /usr/local/cvsroot/PRISM/src/Acquire/acq.c,v
    Working file: acq.c
    head: 1.13
    branch:
    locks: strict
    access list:
    keyword substitution: kv
    total revisions: 13; selected revisions: 13
    description:
    ----------------------------
    revision 1.13
    date: 2003/05/07 15:42:39; author: mzieg; state: Exp; lines: +10 -21
    rolled back zieg's malloc checks, added Tom's assert checks
    ----------------------------
    revision 1.12
    date: 2003/05/07 15:28:05; author: mzieg; state: Exp; lines: +22 -14
    zieg's malloc checks
    ----------------------------
    revision 1.11
    date: 2003/04/30 16:06:05; author: mzieg; state: Exp; lines: +2 -0
    added rcs_id

    [snip]

    In this example, let's say we want to "rollback" to revision 1.11.

  3. update your working copy with the difference between the latest revision and the old revision:
    cvs update -j current_rev -j rollback_rev filename
    Example:
    $ cvs update -j 1.13 -j 1.11 acq.c

  4. commit your updated copy back to the repository:
    cvs commit filename
    Example:
    $ cvs commit -m "threw out bad changes; rolled back to working rev (1.11)" acq.c

    Checking in acq.c;
    /usr/local/cvsroot/PRISM/src/Acquire/acq.c,v <-- acq.c
    new revision: 1.14; previous revision: 1.13
    done

Okay, what happened here?

The key step is the "cvs update -j now -j old file" command. Basically, this is a "cvs update" command, and like any other "cvs update" command, it generates a "diff" from the repository and applies that diff to your working copy. In this case, it's generating a diff between two repository revisions of the same file. The important thing is the order of the revisions specified in the "-j" options.

Normally you think of a diff as moving forward in time; to see recent modifications of a file, you generate "the diff between the old copy and my new copy". In this case, you want to roll the file backwards in time, so you ask for the diff "between the new copy and the old copy"; in other words, "generate a patchfile, which, if applied to a RECENT revision of the file, will delete a bunch of additions and leave an exact image of the OLD revision."

So that's what the "cvs update -j recent -j old file" command does. It generates a patchfile which will DELETE all of the changes made between the "old" and "recent" revisions, and then applies those patches to your working copy. This results in a modification to your working copy, so if you follow-up with a "cvs commit", your modified version gets commited back to the repository as the latest revision of that file's history.

You have just "rolled back" the file's contents to those of a historical revision's, without actually destroying the validity of the repository's history log.

Reference:

How do I move or rename files or directories that have already been added and committed to the repository?

Short-form: http://www.cvshome.org/docs/manual/cvs_7.html#SEC70

I only want to checkout a partial subset of the tree, or maybe just a single file. How do I do that?

To check out an entire module, you use this:
cvs checkout MODULE
In the same way, to checkout part of a module, you would use this:
cvs checkout MODULE/just/this/part
Note that while this will only checkout the file(s) under just/this/part, it will place them into a fully-nested directory tree. Ie, if you are in /home/mzieg/work when you execute the command, your files will end up in /home/mzieg/work/MODULE/just/this/part.

To reduce the number of spurious directories created, see the next tip.

I only want to checkout a partial subset of the tree, or maybe just a single file...and I want it checked out here. I don't want the whole MODULE/long/path/to/one/file mess!

Three methods are presented. The first two give you a genuine "working sub-tree", to which you can update, commit, diff, etc in normal CVS fashion.

The third gives you only the desired file (no companion ./CVS directory to store repository metadata), and (AFAIK) only works for a single file.

  1. cvs checkout -d /where/I/want/it MODULE/what/I/want
    example:
    1. mkdir /usr/share/doc/prism
    2. cvs checkout -d /usr/share/doc/prism PRISM/Docs
  2. cvs checkout -d . MODULE/what/I/want
    example:
    1. mkdir /usr/share/doc/prism
    2. cd /usr/share/doc/prism
    3. cvs checkout -d . PRISM/Docs
  3. cvs checkout -p MODULE/what/I/want.ext > /where/I/want/it.ext
    example:
    1. mkdir /usr/share/doc/prism
    2. cd /usr/share/doc/prism
    3. cvs checkout -p PRISM/Docs/cm/cvs.html > PrismCvs.html

How do I remove a file from the repository that was added by mistake?

First of all, there is no way to "remove" any files from the CVS repository short of having an administrator physically pluck them out of the repository filesystem. This is for a reason.

The purpose of CVS is to maintain a historical trail of how the code looked over time. If there was a file in the code tree at 8:34am on Tuesday, and on Friday somebody says "quick, I need an exact copy of our code tree from 8:34am last Tuesday!", then by-God CVS is going to try to present the exact state of the repository at that point in time.

It couldn't very well do that if people went and retroactively "removed" files from the repository history, could it?

Therefore, the notion of "removing" files is a very sensitive one to any revision-control system. The CVS approach is to not actually "remove" them, but "move them to the Attic." The Attic is a "graveyard" within the repository where old and useless files can be put out to pasture.

If you do a "checkout" or an "update", you will not see these files. They will be "gone", "removed" as far as users can tell. But if, for some reason, you decide in five years time that "Oh, if only I still had that old snippet of obsolete SGI code we wrote for that one customer before we transitioned to OpenGl"...you'll be able to extract the old version. (Exactly how is a subject for a FAQ entry which will be created when someone has a need for that :-)

Having said that, note furthermore that there is no way to remove the file from the repository without also removing (deleting) it from your working copy. I don't know what the logic was behind that design decision, but there you are. Therefore, both of the following procedures will end up deleting your local copy of the file as well. (If it was important, you can of course get it back, simply by checking it out of the Attic. Or you may simply decide to back it up somewhere before deleting it. Whatever.) The simplest way is:

cvs remove -f <filename>
cvs commit <filename>
If you don't include the -f ("delete the file before removing it"), then you'll need to make it a 3-step process:
rm <filename>
cvs remove <filename>
cvs commit <filename>

How do I remove a directory from the repository when it no longer pertains to the project?

Well, now, that's a tricky one. The thing is...you can't.

Seriously. It's in the manual.

There are reasons for this, which AFAIK have to do with the fact that old files, which once existed in that directory, are now stored in the "Attic"...and the current CVS implementation stores a mini-Attic inside each directory of the repository. Meaning that you can't "remove" the directory without also removing the Attic version of that directory's old contents...which CVS will not allow you to do.

"What? I'm going to have these stupidly-named ghost directories floating around for the life of my project?"

Well, not necessarily. You have at least three options:

  1. [recommended] instruct your developers to add the "-P" (prune) option to the "update" and "checkout" lines of their "~/.cvsrc" files. This will prevent CVS from generating any new directories which are clearly empty. Ie,
    # ~/.cvsrc

    # I like context diffs
    diff -c

    # checkout any new (but non-empty) directories when doing updates
    update -d -P

    # don't checkout empty directories
    checkout -P

  2. Physically remove the old directory from the repository. Note that this will break your historical revision trail.

  3. Stash the old stuff in a single location, thus creating only one "ghost" directory:
    1. Create a new directory in your project.
    2. Call it "DEPRECATED" or whatever.
    3. Copy your old stuff into DEPRECATED
    4. add your old stuff back to the repository, in the new location
    5. remove the old stuff from the previous location
    6. physically remove the old location from the repository
    Note that this will break your ability to recreate historical snapshots, but at least you'll still have all the files that were ever in the code tree.

Some of my scripts are executable when I check them out, others aren't! How do I control that?

See the Fogel explanation.

Basically, you just need to have an admin physically chmod the files +x or -x in the repository itself. Otherwise, it will use whatever the perms were when the file was first added (commited?).


4.0 Tags and Branches

What is a tag?

A tag is an English-like, human-readable label that refers to a what snapshot of the project looked like at a historical point in time. (Razor calls these "Threads.")

Every time you modify (and commit) a particular file, the revision number is incremented. These per-file revision numbers have absolutely nothing to do with the official "version number" of the overall software package.

That is, what we think of as "release 2.1" might actually be made up of these file revisions:

gui.c1.54
sensor.c1.24
sim.c1.11
socket.c1.5
tracker.c1.79

In order to make it easier for humans (users) to go back years from now and examine exactly what went into "release 2.1", they create a label, called a tag, which represents the set of those particular revisions of those files. In this case, we might make a tag called "Release-2_1" to indicate the collection of specific file revisions which were included in that release.

Tags are just text strings, but they have some funny restrictions on what characters are allowed (basically A-Za-z0-9_ and hyphen; the decimal point is a noticable ommision). Therefore, tags usually look like "PRISM-2_1", "SNAPSHOT-Owego-2001_09_11", etc. The usual practice is to use an underscore where you would normally want a period, and a hyphen where you would otherwise use an underscore :-)

While tags are one of the most useful features of CVS, they are only useful if you use them! If you cut a release of your code, or send a distribution to somebody, you really should make yourself tag the release so that you can later re-generate exactly what you sent them should a question arise.

Note that there are two "implicit" tags, which are maintained by the system and are always available: BASE and HEAD. Those refer to the very first (oldest) and most-recent (newest) revisions of each file in the current branch, respectively. (Think of them as analogs to list->tail and list->head pointers in a queue structure.)

For more info on tags, check Fogel's entry (which has an excellent ASCII drawing of a wavefront :-)

How do I see what tags are defined?

See cvsFileTags.sh.

How do I create a tag?

This is the basic command:
cvs tag new_tag_name
Example:
cvs tag release-2003_01_21-Name_of_Receiving_User
Note that you probably want to be in the top-level directory when you do that (ie, ./PRISM, not ./PRISM/BasePrograms or somesuch).

For reference, see cvsbook.html#tag.

How do I checkout a tagged release?

This is the basic command:
cvs checkout -rtag_name
Example:
cvs checkout -r release-2002_09_17-Chad_McCrae
For reference, see
cvsbook.html#checkout.

What is a branch?

I can't possibly improve on Ken Fogel's excellent narrative tutorial. Read it..

How do I create a branch?

It's actually implemented as a special sort of tag (a "branch tag"). This is wonderful, because it gives us a nice English-language label to use when referencing the branch.
cvs tag -b branch_label
Example:
cvs tag -b Branch-2002_11_16-EOTDS_Testing
That's assuming, of course, that you're already in the top-level directory of the code that you want to use as the branch "base". A more complete example might be:
    # STEP 1: grab a snapshot of the code circa Nov 16, 2002 

    $ cd ~            
    ~ $ mkdir test
    ~ $ cd test
    ~/test $ cvs checkout -D 2002/11/16 PRISM

    # STEP 2: create a branch (with associated English tag) starting with this snapshot

    ~/test $ cd PRISM
    ~/test/PRISM $ cvs tag -b Branch-2002_11_16-EOTDS_Testing

    # STEP 3: make sure it worked, by deleting the tree and checking out that branch

    ~/test/PRISM $ cd ~/test
    ~/test $ rm -rf PRISM
    ~/test $ cvs checkout -r Branch-2002_11_16-EOTDS_Testing PRISM

    STEP 4: build PRISM for kicks

    ~/test $ cd PRISM
    ~/test $ cd BasePrograms
    ~/test $ export MAKE_HOME=~/test/PRISM/BasePrograms
    ~/test $ export DATA_HOME=~/test/PRISM/BaseData
    ~/test $ ./configure
       [...snip]
    ~/test $ make
       [...snip]
Now, the end-result of all of this is that code changes (commits) made to this "test" sandbox WILL be recorded in CVS, but will NOT affect the main development trunk.

How do I merge a branch back into the main trunk?

Now, this can be tricky.

It's easy if you merge all of the branch changes into the trunk, all at once, one time. For that trivial case, all you have to do is this:

    # make sure you're in the top-level directory of an updated
    # "trunk" sandbox

    $ cd ~/PRISM

    # update (merge) the branch changes to the trunk

    $ cvs update -j Branch-2002_11_16-EOTDS_Testing

    # fix any conflicts (there may be some)

    # commit the merged version into the trunk

    $ cvs commit -m "merged changes from Branch-2002_11_16-EOTDS_Testing"

    # all done!
Where it gets tricky is if:
  1. you decide to only merge in "part" of the branch changes (I have not yet established a safe procedure to do this!), or
  2. you make further changes to the branch after the initial merge, and then want to re-merge them.
What will seriously help in those cases is if you follow a procedure like this:
  1. create the branch
    (cvs tag -b Branch-2002_11_16-EOTDS_Testing)
  2. commit some changes to the branch
  3. make a tag, on the branch, of the snapshot you're ready to merge
    (cvs tag Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_21)
  4. merge those changes over to the trunk (cd to a "trunk" sandbox, then
    cvs update -j Branch-2002_11_16-EOTDS_Testing -j Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_21
    ...yes, those were two -j options, saying "take the changes between 'this' tag and 'that' tag, and merge them into my current working directory.
  5. commit your merged trunk
    (cvs commit -m "merged changes from Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_21")
  6. make a tag, on the trunk, of the merged version
    (cvs tag Merged-Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_21)
Then, the next time you want to merge in the "latest" changes from that particular branch, all you've got to do is repeat the process (skipping the first two steps). Let's say it's two days later:
  1. make a tag, on the branch, of the snapshot you're ready to merge
    (cvs tag Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_23)
  2. merge those changes over to the trunk (cd to a "trunk" sandbox, then
    cvs update -j Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_21 -j Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_23
  3. commit your merged trunk
    (cvs commit -m "merged changes from Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_23")
  4. make a tag, on the trunk, of the merged version
    (cvs tag Merged-Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_23)
Sound like a pain in the ass? Yes, I'd agree with you -- but that seems to be the process.

For reference, definitely read the Fogel chapter. You also might like this newsgroup post.


5.0 Locking vs. Merging

What is the "Locking Problem"?

The Locking Problem refers to the situation of what to do when two developers are trying to modify the same code at the same time.

The scope of the problem is proportional to the granularity with which code can be addressed (directories, files, or even sub-files; IBM's VisualAge can actually lock individual methods (functions) within a single file). However, regardless of granularity (CVS and most other environments are file-based), you eventually come up with a point where two developers might try to modify the same unit of code.

Many tools avoid the problem entirely by using exclusive file-level locking. That is, if user Jane has already checked out file "tracker.c" for editing, then user Bob is unable to do the same. Bob may be able to checkout a read-only (non-editable) copy, but will be unable to modify his version (or at least forbidden from commiting his changes back to the repository). This is a reasonable approach, and is the preferred solution for many developers.

However, it is not how CVS has elected to operate, which is the subject of a long-lasting and heated debate on the internet. If you are willing to brave the vitriol and invective that frequently infect these discussions, you can quickly find them on groups.google.com.

Some more-rational descriptions of the pros and cons between locking and non-locking behaviour -- AKA "reserved vs unreserved", AKA "pessimistic vs optimistic" checkouts, as the debate is alternately described -- can be found here:

How does CVS address the Locking Problem?

From the CVS FAQ-o-matic primer:
Conflict:
When two people make changes to the same file at the same time, their work may conflict. There are two ways to address conflicts: locks and merging.
Locks:
One strategy for managing conflicts in version control is to lock files so that only one person can check out a read-write copy at a time. Other users can only get read-only copies. This prevents two people from ever working on the same file at the same time. It is safe, but not very efficient in practice. Also, if one person locks a file and then leaves for the day or the week, no one else can work with that file, so they are needlessly blocked.
Merging:
Another strategy for managing conflict is to let multiple people work at the same time (with no locks) and then try to merge their results into one combined version. This is closer to the way that intelligent people tend to work together in parallel if they had no version control tools. Merging works well when two sets of changes can be combined, and poorly when it is impossible to combine changes. CVS uses merging.
In other words, when CVS calls itself a "Concurrent Versioning System", it means concurrent. More than one person can edit the same file at the same time.

You will frequently find the CVS strategy described as "copy-edit-merge" or "copy-modify-merge" in various documentation (in contrast, "locking" strategies are likewise known as "lock-edit-unlock").

Here's a sample scenario to demonstrate how it works:

  1. Backstory:
    • tracker.c is currently at revision 1.6.
    • Bob and Jane both already have working copies of tracker.c under their home directories.
    • Because Bob hasn't worked with this code in a while, his personal copy is out-of-date; he still has revision 1.3.
  2. Jane and Bob both diligently obey the company's CM procedures, which dictate that "before starting work on a block of shared/common code, make sure you're working on the most current version". Therefore, they each run "cvs update" in their working directories. When Bob does this, CVS notices that he has an unmodified copy of an old version of tracker.c, and politely updates it to revision 1.6. (It informs him of this as it happens.)
  3. Jane opens her copy of tracker.c in vi and edits the existing function getNextTrack(). Perhaps she fixes a long-standing bug, or optimizes a loop for efficiency. Whatever.
  4. Around the same time, Bob edits his copy of that same file using emacs. He wants to add a new diagnostic routine, displayAllTracks(), to help debug some strange behavior reported by a customer.
  5. Jane commits her changes first. CVS prompts her to enter a short comment explaining her change, so she types "fixed getNextTrack() bug which skipped every 16th track". Her version is committed back to the repository, and a message is printed stating that tracker.c is now at revision 1.7.
  6. Now Bob tries to commit his changes. Like Jane, he types cvs commit tracker.c, but unlike her he gets an error message:
    cvs commit: Up-to-date check failed for `tracker.c'
    cvs [commit aborted]: correct above errors first!
    Whoops! So, to correct the error, he types cvs update tracker.c. This is what he gets back:
    RCS file: /usr/local/cvsroot/testproj/src/tracker.c,v
    retrieving revision 1.6
    retrieving revision 1.7
    Merging differences between 1.6 and 1.7 into tracker.c
    M tracker.c
  7. Okay, what just happened here?
    1. Operating automatically, CVS determines that Bob was starting with revision 1.6 of tracker.c when he started editing. (It determined this by checking the ./CVS metadata directory, which is why you shouldn't screw around with those files.)
    2. Knowing both the current repository revision number and the version Bob had, CVS compares those versions. It does this (offstage, somewhere in /tmp or something) by checking out temporary copies of both versions and "diff"ing them together.
    3. Then, still automatically, CVS attempted to apply that "diff" as a patch to Bob's working copy of tracker.c. This replaced the old getNextTrack() function in Bob's copy of tracker.c with Jane's newer version, without disturbing the displayAllTracks() function he had just added.
    4. The upshot of this is that CVS "revises history", making it as though Bob had added his new displayAllTracks() function to Jane's latest version (1.7), rather than to the now-dated 1.6 version. It merges Bob's changes and Jane's changes together, generally quite safely.
    5. Therefore, if Bob now opens his copy of tracker.c with a text editor, he'll see his new displayAllTracks() function just as he had written it. However, if he pokes around in getNextTrack(), he may notice that it runs better due to Jane's new code.
  8. Bob is justifiably curious as to what was just updated in tracker.c, so he asks CVS to tell him what has changed. The first thing he tries is cvs log tracker.c, which displays a list of the log entries that developers provide when they commit changes. Sure enough, there at the top of the report is Jane's log entry:

    ----------------------------
    revision 1.7
    date: 2002/08/05 19:02:44; author: jane; state: Exp; lines: +13 -5
    fixed getNextTrack() bug which skipped every 16th track
    ----------------------------

  9. That may be all Bob needed to know to satisfy his curiousity. Perhaps, however, he'd like to know more, so he asks for a "diff" report to see the actual lines of code. He can't simply do a cvs diff tracker.c, because that will tell him how his own copy of tracker.c differs from the repository. Since he already update'd his tracker.c to the latest repository version (1.7, including Jane's fixes), cvs diff would only show him his own new function, displayAllTracks() -- not what he wants to see. Rather, he wants to see what Jane added or changed, ie what differed between versions 1.6 and 1.7; therefore, he types "cvs diff -r 1.6 -r 1.7 tracker.c". Now he can see exactly what Jane was up to (other than simply hollering over the cubicle wall to ask her :-)
  10. Satisfied that Jane's changes really didn't affect anything he was doing, he now commits (cvs commit tracker.c) his own changes back to the repository, taking care to provide an appropriate log message (-m "added displayAllTracks() diag func"). CVS reports that tracker.c is now at version 1.8 in the repository.

Wait, that can't possibly resolve all conflicts!

No, it doesn't. In particular, it abysmally fails when encountering any variation of this scenario:
  1. tracker.c is at revision 1.8
  2. line 100 of tracker.c reads "int ratio = 3;"
  3. Jane and Bob each checkout the project
  4. Jane changes line 100 to read "int ratio = 4;"
  5. Bob changes line 100 to read "int ratio = 2;"
  6. Jane commits her changes first, so tracker.c is now at revision 1.9, and ratio is set to 4
  7. Bob tries to commit his changes, and of course immediately gets a warning:
    $ cvs commit tracker.c
    cvs commit: Up-to-date check failed for `tracker.c'
    cvs [commit aborted]: correct above errors first!
  8. Bob realizes that someone else has apparently modified the file, so he tries to merge their changes:
    $ cvs update tracker.cc
    RCS file: /usr/local/cvsroot/testproj/src/tracker.c,v
    retrieving revision 1.8
    retrieving revision 1.9
    Merging differences between 1.8 and 1.9 into tracker.c
    rcsmerge: warning: conflicts during merge
    cvs update: conflicts found in tracker.c
    C tracker.c
  9. Okay, what does this mean?
    • As in the previous merging example, CVS automatically grabs its own private copies of revisions 1.8 (the version Bob had been editing) and 1.9 (the most current revision from the repository), and diff's them. At this point, CVS knows that, in taking tracker.c from 1.8 to 1.9, Jane modified line 100 and changed the value of ratio from 3 to 4.
    • Moreover (this wasn't described in the previous example), CVS also takes a diff between Bob's working version of tracker.c and the base version he started from (1.8). At this point, CVS knows that, while modifying revision 1.8 of tracker.c, Bob modified line 100 and changed the value of ratio from 3 to 2.
    • CVS now compares those two sets of changes with the intent of merging them...and instantly realizes that it cannot. They are mutally exclusive.
    • CVS reports to the user (Bob) that it has found a conflict which must be manually resolved.
    • Bob's working copy of tracker.c -- the one that he has modified, which now sets ratio to 2 -- is renamed to ".#tracker.c.1.8". This file is Bob's modified, but uncommitted, version of revision 1.8 of tracker.c. It is temporarily backed up in this manner so that Bob's code isn't inadvertently lost.
    • A new copy of tracker.c is placed in Bob's current directory, which contains the "merged" result of Jane's 1.9 revision and Bob's 1.8 changes. Conflicts are called out inside the source code, and must be manually hand-resolved.
    • If Bob then pulls up his copy of tracker.c in vi, he will find a block like this in the source code:
      int computeSomething( int *a, int *b ) {
      <<<<<<< tracker.c
          int ratio = 2;
      =======
          int ratio = 4;
      >>>>>>> 1.9
          *a = *b * ratio;
          return *b / *a;
      }
      (He won't actually see all those pretty colors, of course, unless he's using an editor includes syntax rules for CVS.) (like vim :-)
      red lines
      visual call-outs put there by CVS to draw your attention to the block. Even if you should miss them, your compiler probably will not :-)
      blue lines
      show what your (Bob's) code was
      green lines
      shows what your peer (ie Jane, who also edited v1.8 of tracker.c, and got there before you) committed to the repository
Okay, there's a conflict; I see what it is, and how it came about.

What are Bob's options at this point?

Bob has a number of options here, any of which will work, but all of which will require the manual exercise of some plain 'ole human judgement. (SuperCVS, which will totally obviate the need for human judgement and is hoped to replace programmers altogether, won't be available until next spring.)

  • Bob can call up Jane, and see if they can agree on a common value for ratio that will work for both of them.
  • Bob can block out his value of ratio with some #ifdef BOB_TEST...#endif macros (lame) or tie ratio to a command-line option or environment variable (better)
  • If this is a significant difference for tracker.c, and genuinely represents a divergent code path for the software (perhaps all of Customer A's tracks will require Jane's ratio, whereas all of Customer B's tracks will require Bob's), then it might actually be appropriate to create a branch in the code tree.
In short, Bob and Jane will simply have to work this out exactly as they normally would in the absence of a CM tool such as CVS.

The important point to remember is not that CVS caused the conflict described above -- it did not -- but that the proper execution of SCM procedures, assisted by the CVS tool, helped the developers discover the discrepancy in their algorithm assumptions, ideally well-before inadvertantly shipping code with the wrong ratio to the wrong customer, possibly skewing tracks, causing missiles to slew wildly out of kilter, destroy the wrong targets, lead to international conflagration, and ultimately bring about the end of civilization as we know it.

All that, and CVS is still free! Vat a bargain! (See here for the Fogel docs)


6.0 Administration

How do you install CVS?

Under RedHat Linux 7.2, find it on "CD-ROM #2":

$ sudo rpm -i /mnt/cdrom/RedHat/RPMS/cvs-1.11.1p1-3.i386.rpm

(actually, I used rpmfind.net to grab a slightly-newer RPM for 1.11.2)

How do you create a repository?

With the init command: http://www.cvshome.org/docs/manual/cvs_2.html#SEC23

How do you create a module?

With the import command: http://www.cvshome.org/docs/manual/cvs_3.html#SEC38.

How do you backup a repository?

cvs-oa $ cd /usr/local
cvs-oa $ tar zcvf ~/cvsroot.2002-08-01.tgz cvsroot
cvs-oa $ rcp ~/cvsroot.2002-08-01.tgz me@some.other.box:backups/

(Note that there is nothing funky or CVS-specific about these commands; they're just plain-jane Unix 'tar' commands to backup a directory. In this case, the CVS repository is stored in /usr/local/cvsroot, so that's the directory which is being backed up. The example was written on August 8, 2002, so that date was included as a demonstration of how backups might be timestamped for later reference. --Josh)

How do you restore a repository?

cvs-oa $ rcp me@some.other.box:backups/cvsroot.2002-08-01.tgz ~
cvs-oa $ cd /usr/local
cvs-oa $ sudo tar zxvf ~/cvsroot.2002-08-01.tgz

How do you control access to a repository?

Short-form: with Unix access privileges.

Right now, anyone with a login to cvs-oa has read-access to /usr/local/cvsroot, and anyone in the 'users' group has write-access as well. CVS authentication is currently set to use /etc/passwd, so in order to add someone to "CVS" you merely have to create an account for them on cvs-oa and add them to the "users" group.

To eliminate potential read-write problems, the entire repository was set to be owned by the group "users", regardless of the default group membership of individual users. This was accomplished by:

$ sudo chgrp -R 'users' /usr/local/cvsroot
$ sudo chmod -R g+s /usr/local/cvsroot


7.0 Gotchas, Troubleshooting & Miscellani

Gotcha: Option order

The syntax for any CVS command is this:
cvs [global-opts] command [cmd-opts] [file...]
You'll note that there are two places for options there, which I have colored green and blue for your edification. The green, global options are available and valid for every CVS command (AFAIK). The blue, command-specific options vary with context. It is frequently very important to know which options you're trying to use, and to use them in the correct order.

For instance, if you forgot that "-n" was a global option, you might accidentally try to check the status of your code with "cvs update -n". Well, if that's all you did, CVS would exit with a polite error message to the effect that there was no such "-n" option to "update". That might leave you feeling a bit puzzled ("I would have sworn that was the command I'd used...?"), but no harm done.

However, if combined with the filehandle redirection described above, then you'd get...nothing at all. The exact same output as if your code was exactly in sync with the repository, a mistaken conclusion which could lead to all kinds of pain. It gets worse. Many of the same letters are used for global and command options. For instance, although "update" doesn't have an "-n" option, it does have -l, -r, and -f options, whose meanings vary considerably from the global versions:

opt global meaning update context
-r make checked-out files read-only update using specified revision/tag (sticky)
-f skip ~/.cvsrc force a head revision match if tag/date not found
-l turn history logging off local directory only, no recursion

As you can see, things could get pretty weird -- and possibly dangerous -- if you used the "right" option in the "wrong" place.

Gotcha: ../.. and explicit paths

I tried commiting a file using
cvs commit $MAKE_HOME/config/sgi/config.mk
and it wouldn't let me, because MAKE_HOME was an "explicit path" (/home/mzieg/proj/prism/PRISM/BasePrograms).

Then I tried it again with

cvs commit ../../config/sgi/config.mk
and it wouldn't work because of "too many ..". Argh.

Finally it worked with

cd $MAKE_HOME && cvs commit config/sgi/config.mk
Bleah. Annoying.
What's with all these 'CVS' directories?

You can ignore them, but unless you really know what you're doing, I would neither delete nor edit them. It tends to really confuse CVS, because it loses a lot of critical state information. There may be a simple command to "regenerate" or "fix" a set of hosed CVS directories, but I haven't found one yet. Seriously, just leave them alone and don't worry about it.

Can I do all this stuff from a GUI? Please???

Wuss.

Oh, if you must:

How do I customize CVS to act like I want?

Like most Unix tools, CVS will check for the existence of a ~/.cvsrc file in your home directory at each invocation.

The primary function of this file is to store default arguments. Oddly enough, it's documented in 'man cvs', but not on cvshome.org. Go figure.

The format is pretty simple: each line starts with a cvs command name ("add", "commit", etc) followed by the default arguments you'd like to use with each command. Here's an example:

# ~/.cvsrc

# I like context diffs
diff -c

# checkout any new directories when doing updates
update -d


Appendix A: Other Sources of Information

Sadly, I've been unable to find a single source of comprehensive, intelligent, easily navigable CVS documentation. The nominal "home" of CVS, cvshome.org, is a poorly constructed quasi-commercial venture that has little historical connection with the original CVS developers/maintainers. For the time being, I've collected a list of some of the better online CVS references I've come across, more-or-less sorted by quality and general usefulness. Caveat Emptor.