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...
import,
checkout, commit, update
status and log and annotate...oh my!
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.
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 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.
import,
checkout, commit, updateImporting 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."
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.)
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.
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".
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)
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).
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.
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.
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.
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.
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.
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.
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.
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)