There are 3 commands you need to know to use CVS:

  • cvs checkout
  • cvs update
  • cvs commit

More commands are necessary to set up a repository, but the above are all you need to get things out of and back into an existing repository.

To work with a repository you can either pass a -d /path/to/cvsroot argument to cvs(1) for every operation, or set the CVSROOT EnvironmentVariable to point to it:

export CVSROOT=/path/to/cvsroot

This is most useful when you actually work with a particular repository. Passing the -d switch is more convenient for one-off operations, such as checking someone else's project out of their repository for compiling with no intent to actually commit back any changes.

Checking stuff out of the repository is trivial. You just need to know which “module” you want to check out:

cd ~/src # where to check out the code to
cvs checkout report # check out module named "report"

Note that the files are not checked out into the current directory, but into a directory named after the module. So next you'll probably want to go in there:

cd report

Now make the desired changes to the respective files. Getting the changes back into the repository is very easy:

cvs commit

You need to do this from the correct directory, because CVS needs to find the files with MetaData it created when you checked things out. These records store information such as which module in which repository things came from.

You can also commit specific files only:

cvs commit README todo.txt

Setting up a local CVS repository

If the machine does not have a repository set up, you need to decide where to store the modules. /var/lib/cvs/ might be a good place for a multiuser system:

sudo mkdir -m 775 /var/lib/cvs/
export CVSROOT=/var/lib/cvs/

Or for just yourself, use your home directory:

mkdir ~/cvs
export CVSROOT=~/cvs

Or pick any other place you might prefer. Then create a skeleton repository:

cvs init

Now, you need to import a SourceCode module and store it in the repository. First, decide on a module name; this will be a directory name internally, so it should probably have no spaces. Additionally, you need a “vendor tag” to identify the owner of the files; in practice, any arbitrary name will do. You also need to pick a “release tag” which will be used as the initial version of the files being imported.

cd source/code/topdir # wherever the files and directories to import are
VENDORTAG="original upstream"

cvs(1) will launch the editor specified in your EDITOR or VISUAL EnvironmentVariable to ask for a comment. Just put initial version or something.

This will create a new directory $CSVROOT/$MODULENAME. Note that this only sets up the module. You now need to check it out of the repository before making any changes.

cvs checkout "$MODULENAME"

Legend of the cvs update status letters

added to your checked out version by CVS
conflict between your local changes and someone else's committed changes
modified on your end but not yet committed
patched to bring your (unmodified) version up-to-date with the last change.
removed from the main repository
updated your (unmodified) version with other people's (multiple?) changes

An examples using the GreenStone SourceCode

This particular repository allows anonymous, read-only checkout. pserver is the protocol used for that.


You would need a username and password on that particular machine and use SSH to have write access. If you do, the EnvironmentVariables will be slightly different:

export CVS_RSH=ssh
export CVSROOT=:ext:username@server:/directory/to/repository

Now check out the code:

cvs checkout gsdl

Make your own changes to the source code:

dd if=/dev/random of=a_source_file.cpp bs=1k count=$RANDOM

(I swear this is how some of our 4th years come up with their code...)

Now, you can use CVS to see how your stuff differs from everyone else's, and how to get everyone else's changes into your checked out version.

$ cvs update
? mht
cvs server: Updating .

P means "patched"; my version was patched to be updated to other people's changes that they have committed. M means locally modified (ie by me).

You can see what is different between my version and the current checked in version:

$ cvs diff
RCS file: /usr/local/global-cvs/gsdl-src/gsdl/perllib/plugins/,v
retrieving revision 1.58
diff -r1.58
<       $tmptext = s/\s\S*$/…/;
>       $tmptext = s/\s\S*$/…/;   # horizontal ellipsis ...
<     $$textref = s/&(lt|gt|amp|quot|nbsp);/&z$1;/go;
<     $$textref = s/&([^;]+);/&ghtml::getcharequiv($1,1)/gseo;
<     $$textref = s/&z(lt|gt|amp|quot|nbsp);/&$1;/go;
> #    $$textref = s/&(lt|gt|amp|quot|nbsp);/&z$1;/go;
>     $$textref = s/&([^;]+);/&ghtml::getcharequiv($1,0)/gseo;
> #    $$textref = s/&z(lt|gt|amp|quot|nbsp);/&$1;/go;

So this diff shows that I added a comment to line 524, and commented out lines 705 and 707 (and changed line 706). You may or may not recognise this gibberish as Perl code...

Other useful CVS commands:

$ cvs status
src/gsdl/perllib/plugins$ cvs status
File:       Status: Locally Modified

 Working revision:    1.58
 Repository revision: 1.58    /usr/local/global-cvs/gsdl-src/gsdl/perllib/plugins/,v
 Sticky Tag:          (none)
 Sticky Date:         (none)
 Sticky Options:      (none)
$ cvs annotate
src/gsdl/perllib/plugins$ cvs annotate
[ trimmed for brevity ]
1.7      (sjboddie 06-Dec-99):       # if no title use first 100 characters
1.7      (sjboddie 06-Dec-99):       my $tmptext = $$textref;
1.43     (jrm21    21-May-01):       $tmptext = s/<\/([^>]+)><\1>//g; # (eg) </b><b> - no space
1.30     (say1     14-Oct-00):       $tmptext = s/<[^>]*>/ /g;
1.56     (jrm21    11-Jul-02):       $tmptext = s/(?:&nbsp;|\xc2\xa0)/ /g; # utf-8 for nbsp...
1.43     (jrm21    21-May-01):       $tmptext = s/^\s+//s;
1.16     (gwp      22-Jun-00):       $tmptext = s/\s+$//;
1.14     (gwp      24-May-00):       $tmptext = s/\s+/ /gs;
1.56     (jrm21    11-Jul-02):       $tmptext = s/^$self->{'title_sub'}// if ($self->{'title_sub'});
1.56     (jrm21    11-Jul-02):       $tmptext = s/^\s+//s; # in case title_sub introduced any...
1.30     (say1     14-Oct-00):       $tmptext = substr ($tmptext, 0, 100);
1.14     (gwp      24-May-00):       $tmptext = s/\s\S*$/.../;
1.15     (sjboddie 20-Jun-00):       $doc_obj->add_utf8_metadata ($section, $field, $tmptext);
1.33     (paynter  02-Nov-00):       print $outhandle " extracted \"$field\" metadata \"$tmptext\"\n"
1.36     (sjboddie 18-Jan-01):           if ($self->{'verbosity'} > 2);
1.16     (gwp      22-Jun-00):       next;
[ trimmed for brevity ]

This shows each line (as checked in) of the file, showing when that line was last edited, who by, when, and which version. So if you make a stuff-up, everyone else knows who to blame.

CategoryVersionControl CategoryHowto