An introduction to CVS

Questions are invited at any time.

Before you begin: users of pico

You should note that CVS will occasionally drop you into a full-screen editor. By default, this is vi. If you use pico, then you need to let CVS know that that is your editor of choice.

CVS follows the standard convention of using the VISUAL environment variable to specify a full-screen editor. If you want to use pico, append the following line to your .profile file in your home directory:

VISUAL=pico export VISUAL

and the corresponding line to your .cshrc file:

setenv VISUAL pico

then restart your shell (logging in again will do this).


Notation

I've tried to keep this as concise and cook-booky as possible. I indicate command-line sessions using blocks of text as follows, with the > standing in for your prompt. Stuff that you type is indicated in bold. Responses are shown (where they're interesting).

> echo "hello world"
hello world

Contents


What I'm not going to cover


Basic CVS operation


Checking out a module

CVS is organised into modules. Each cvs repository consists of one or more modules. You can think of a module as a file hierarchy with a history.

In day-to-day operation, you do your work in a local copy of a module called your working copy. Getting an initial copy of a module is called checking out the module.

In order to check out a module, you need to tell CVS where the repository is located and the name of the module. The CVS repository location is specified in your CVSROOT environment variable (which you configured as a part of the prep for this session).

I've prepared a small module for you to check out. It is called example. You can check it out as follows:

> cvs co example
cvs server: Updating example
U example/README
cvs server: Updating example/build
U example/build/build.sh
cvs server: Updating example/htdocs
U example/htdocs/index.html
cvs server: Updating example/htdocs/jan
U example/htdocs/jan/index.html

Now is the time to yell if this doesn't work.

You should now have a small directory hierarchy starting with example in your current directory. You can cd into the directory and have a look around.

You'll notice that as well as normal files and subdirectories, each directory has a subdirectory called CVS. The CVS directory contains a number of text files that cvs uses to do its book-keeping. You will not normally need to edit these files.


Adding directories and files

To begin with, you're going to create a subdirectory of example/htdocs/ to put your own files inside.

The general pattern for using CVS is to use normal file-manipulation tools to make the changes to your working copy, then use a cvs command to let CVS know about the changes.

> cd example/htdocs
> mkdir john
> cvs add john
Directory /usr/local/cvsroot/example/htdocs added to the repository

There are two things worth commenting on here.

The next step is to create a file. You're going to make a copy of jan/index.html under your own directory, and tell CVS about it.

> cd john
> cp ../jan/index.html .
> cvs add index.html
cvs add: scheduling file `index.html' for addition
cvs add: use 'cvs commit' to add this file permanently

This is a slightly different message. Although CVS contacts the repository when you issue the cvs add, it does not put a copy of the file there. (The repository is checked to ensure there isn't already a copy of index.html that your working copy doesn't know about. If there were, CVS would complain to you about it.)

You can edit the file to personalise it (the contents are a purely rudimentary example) using your editor of choice. When you've finally got a version that's ready to check in, you follow this two-command step to do so:

> cvs up
cvs update: Updating .
A index.html
> cvs ci

At this point, CVS will launch a full-screen editor and prompt you for a commit message. This message is associated with the changes you're making to the repository. Things that might be worth noting here are the reason for the change (a bug fix, a brief explanation of a new feature you're introducing, etc.); however, since CVS makes the precise nature of the change available (we'll see how later), you should avoid merely describing your changes literally. A high-level summary is better.

CVS: ----------------------------------------------------------------------
CVS: Enter Log.  Lines beginning with `CVS:' are removed automatically
CVS:
CVS: Committing in .
CVS:
CVS: Added Files:
CVS:    index.html
CVS: ----------------------------------------------------------------------

Alternatively, if you wish to specify a small commit message (sometimes called a log message), you can do so on the command line using the -m flag:

> cvs ci -m "A short commit message"
cvs commit: Examining .
RCS file: /usr/local/cvsroot/example/htdocs/john/index.html,v
done
Checking in index.html;
/usr/local/cvsroot/example/htdocs/john/index.html,v  <--  index.html
initial revision: 1.1
done

After you finish giving the log message, CVS completes the operation and updates the repository.

Terminology: occasionally you will hear a checkin referred to as a putback. They mean the same thing but CVS only knows about cvs ci, so cvs pb will just make it complain.


Making changes

There isn't a great deal more to CVS operation than that. You can continue to add files, or make changes to existing files. When you've got a set of changes ready to put back, you simply issue the two-step sequence again:

> cvs up
> cvs ci

The sequence of operations to delete a file is pretty straightforward. First, you remove the file using normal file-manipulation commands. Then, you tell CVS to schedule the removal of that file: here's an example. We create and add the file my_file, then remove it. I've elided server responses that we've already seen.

> touch my_file
> cvs add my_file
> cvs ci my_file

Now the file has been added. We delete it as follows:

> rm my_file
> cvs rm my_file
cvs remove: scheduling `foo' for removal
cvs remove: use 'cvs commit' to remove this file permanently
> cvs ci my_file

At this point, CVS reports a slightly different change when it asks you for a commit message:

CVS: ----------------------------------------------------------------------
CVS: Enter Log.  Lines beginning with `CVS:' are removed automatically
CVS:
CVS: Removed Files:
CVS:    my_file
CVS: ----------------------------------------------------------------------

Finally, CVS completes the operation:

Removing foo;
/usr/local/cvsroot/example/htdocs/john/foo,v  <--  foo
new revision: delete; previous revision: 1.1
done

Binary files

CVS knows about two kinds of files: text files (the default) and binary files. When you check out a copy of a text file, CVS will politely change all the end-of-line characters to conform to your client machine's convention. It will also expand certain special character sequences within the file (eg, $Revision: 39 $) - which is where those odd version numbers you may have seen on the bottom of web pages come from.

This is great unless you're trying to use CVS to store a file with a binary format, such as PNG. There are two options you can use to control CVS' behaviour when you first add a file. The first tells it to treat the file as a text one, but not to perform any keyword expansion:

> cvs add -ko my_file.txt

The second option tells CVS to store the file as a raw, binary file. If you do this it will be unable to offer you meaningful "difference" information - but since CVS' diffs are text-based (as we'll see), this isn't a great loss.

> cvs add -kb my_file.png

You can see the flags on a file by using the cvs status command. The flags are listed as Sticky Options.

> cvs status index.html my_face.png
===================================================================
File: index.html        Status: Up-to-date

   Working revision:    1.1     Mon Jul  7 21:33:00 2003
   Repository revision: 1.1     /usr/local/cvsroot/example/htdocs/john/index.html,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)

===================================================================
File: my_face.png       Status: Up-to-date

   Working revision:    1.1     Mon Jul  7 21:34:23 2003
   Repository revision: 1.1     /usr/local/cvsroot/example/htdocs/my_face.png,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      -kb

I'll explain the other fields shortly.


Renaming files (renaming directories)

Unfortunately, CVS has no direct equivalent of the mv command. To rename a file, you need to use mv src dest to move the source file to the destination, then cvs rm src and cvs add dest before finally committing the change with cvs ci src dest.

This is unfortunately somewhat klunky. In addition, CVS does not know the relationship between the contents of the source and destination files; thus, this operation will effectively mean that the destination file doesn't carry any of the change history of the source file.

The situation with directories is slightly more awkward. CVS versions file contents, not file names, not file permissions (or other metadata), and not directories.

In fact, this is one of CVS' biggest niggles. You need to be aware of the issue and to plan your directory structure a bit before you begin to compensate.


Multi-user CVS operation - introduction


Organisation (CVS and yours!)

Up to now, you've worked in separate directories. CVS can actually manage multiple people working on files in the same directory; it can even intelligently merge two people's changes to the same file! However, CVS is not a panacea.

For this session, we can coordinate by yelling across the room. In the wild, CVS is best used in conjunction with some other channel of communication. The value of a mailing list for the developers on your project really cannot be underestimated. If you are planning some changes to a set of files, warn people beforehand!

When you make a commit, your single log message gets attached to the changes to every file involved in the putback. Therefore, you may want to separate your commits into small logical parcels by specifying a few files or directories at a time when you use the cvs ci command.


Getting updates

Now that everyone's made some changes and put them back, we'd like to get a revised view from the repository. I'm going to demonstrate a common "gotcha!" when using CVS here.

If you move back into the htdocs directory, you can run cvs up to call down updates from the repository. However, if you do this (try it now!) you don't automatically get a copy of the new subdirectories that the other people have added.

By default, CVS will recurse over the current directory and over subdirectories that already exist in your working copy. If you want it to check for new directories, you need to tell it like this:

> cd ..
> cvs up -d
[cvs output elided]

This option is easy to forget but about 98% of the time you run an update, you're interested in new directories. You can get CVS to supply this flag automatically by putting the following line in the .cvsrc file in your home directory:

update -d

OK, now you should have an up-to-date copy of the htdocs directory and all the new subdirectories in your working copy.

You can explore the log messages of other people's commits now by using the cvs log command:

> cvs log jane/index.html

RCS file: /usr/local/cvsroot/example/htdocs/jane/index.html,v
Working file: jane/index.html
head: 1.1
branch:
locks: strict
access list:
symbolic names:
keyword substitution: kv
total revisions: 1;     selected revisions: 1
description:
----------------------------
revision 1.1
date: 2003/07/07 21:55:03;  author: jane;  state: Exp;
a short commit message
=============================================================================

Conflicts, and how to avoid them

OK, now we're going to look at what happens when multiple people make changes to the same file. Quite often, CVS will be able to do "the right thing". We'll conduct this as an experiment with multiple people gathering around keyboards (it's clearer what's going on that way).

In the top htdocs directory is another index.html file. Make two edits to this: in the first, just add your name to the first line ("People on the course were:") and a link to your subdirectory similar to the one to "jan's pages". In the latter case, keep the lines in alphabetical order. Don't check in your changes yet! When everyone's ready, shout, and we'll gather around someone's keyboard to do the checkin.

This section should be done with everyone in the same place! Don't go past this point until I tell you.

The first person's checkin of the new top-level index.html goes perfectly:

> cvs up
cvs update: Updating .
M index.html
cvs update: Updating jane
cvs update: Updating john
> cvs ci -m "jane added to top page" index.html
Checking in index.html;
/usr/local/cvsroot/example/htdocs/index.html,v  <--  index.html
new revision: 1.2; previous revision: 1.1
done

Now it's worthwhile looking slightly more closely at the cvs up output. Each line corresponds to a file. The character at the start of the line represents the file's status. The M means that the repository latest version has not changed, but the file has been modified locally in the working copy. OK so far - so we proceed with the commit as normal.

Now, we shift to the second person's keyboard. Here, things don't quite go as before:

> cvs up
cvs update: Updating .
RCS file: /usr/local/cvsroot/example/htdocs/index.html,v
retrieving revision 1.1
retrieving revision 1.2
Merging differences between 1.1 and 1.2 into index.html
rcsmerge: warning: conflicts during merge
cvs update: conflicts found in index.html
C index.html
cvs update: Updating jane
cvs update: Updating john

What happened here? Well, normally when a file has been changed in the repository, CVS will synchronise quietly by updating your local copy of the file (the status letter is U or P). Here, however, the local changes we'd made to the file were too close to the changes made in the repository; so CVS complains with a C status letter (standing for conflict).

Before we can go any further, we must resolve the conflicts. Let's look at the conflicting file with our editor.

<html>
<head>
<title> An introduction to CVS </title>
</head>
<body>

<h1> These documents are under source-code control </h1>

<<<<<<< index.html
<p> People on the course were: jan, john </p>
=======
<p> People on the course were: jan, jane </p>
>>>>>>> 1.2

<ul>
<li> <a href="jan/">jan's pages</a> </li>
<<<<<<< index.html
<li> <a href="john/">john's pages</a> </li>
=======
<li> <a href="jane/">jane's pages</a> </li>
>>>>>>> 1.2
</ul>

</body>
</html>

Yikes! What happened here? Well, CVS has highlighted sections that were too close together for it to merge successfully. Let's look more closely at the first section:

<<<<<<< index.html
<p> People on the course were: jan, john </p>
=======
<p> People on the course were: jan, jane </p>
>>>>>>> 1.2

What this is saying that the section (in this case, a single line) that corresponds to our local copy, index.html, has the "jan, john" text. However, the corresponding section in the latest repository copy (revision number 1.2) has text that reads "jan, jane". CVS doesn't know what to do here - it merges lines, but can't merge intra-line changes - so it relies on the user to resolve the conflict. We can replace these five lines with the single, correctly merged result:

<p> People on the course were: jan, jane, john </p>

Resolving comflicts is simply the case of repeating this procedure until no conflicts are left in the file. When that is the case, we try the update again:

> cvs up index.html
M index.html

Now CVS tells us that there are no longer conflicts; simply local modifications. At this point we can finish the checkin:

> cvs ci -m "john's changes" index.html
Checking in index.html;
/usr/local/cvsroot/example/htdocs/index.html,v  <--  index.html
new revision: 1.3; previous revision: 1.2
done

Conflicts can usually be avoided by two things: firstly, ensuring that you run a cvs up prior to a checkin and clearing up any awkward merges it reports. Secondly, and this really cannot be understated, communicate what you're doing on your handy developer email list!

By carefully laying out your file tree before you start, and allocating informal "ownership" of various portions of your file hierarchy, and by good communication, multiple developers can work happily on a shared CVS module with a minimum of fuss.


Congratulations

Now that you've reached this point, you have sufficient know-how to make good day-to-day use of CVS. It really is that straightforward! What follows for the rest of the sessions will be a quick look at some of the other things you can get CVS to do for you.


Tags; checking out a particular version

I've called CVS a filesystem with a history. It's possible to "go back in time" to get views of your source code earlier in time. In fact, you can label (or tag) points in time and ask to get a copy of the code as it looked then.

I laid down a tag prior to the session start. Here's the command I executed in the top level directory:

> cvs tag -cR T_INITIAL
cvs server: Tagging .
T README
cvs server: Tagging build
T build/build.sh
cvs server: Tagging htdocs
T htdocs/index.html
cvs server: Tagging htdocs/jan
T htdocs/jan/index.html

In other words, I marked that moment in time with the label T_INITIAL. Now, it's possible to get a copy of the module at that point in time like this. Which leads to an important point: just as different developers all have their own working copies, there is no reason why you should not check out multiple working copies yourself. Here's how I can get a copy of the initial example directory state:

> cd
> mkdir eg-initial
> cd eg-initial
> cvs co example
cvs server: Updating example
U example/README
cvs server: Updating example/build
U example/build/build.sh
cvs server: Updating example/htdocs
U example/htdocs/index.html
cvs server: Updating example/htdocs/jan
U example/htdocs/jan/index.html
> cd example
> cvs up -r T_INITIAL
cvs server: Updating .
cvs server: Updating build
cvs server: Updating htdocs
cvs server: Updating htdocs/jan

(The two-step checkout and update shouldn't strictly be necessary: I should be able to simply use cvs co -r T_INITIAL example - however, some CVS versions have a bug and this is how I work around it.)

Now I've got a second copy of the module, checked out as it was initially. I treat this as a read-only copy, a reference point. The working copy remembers that it is tracking a particular tag, however, as can be seen here:

> cvs status README
===================================================================
File: README            Status: Up-to-date

   Working revision:    1.2
   Repository revision: 1.2     /usr/local/cvsroot/example/README,v
   Sticky Tag:          T_INITIAL (revision: 1.2)
   Sticky Date:         (none)
   Sticky Options:      (none)

The "Sticky Tag" means that any updates on that file will bring it in line with the version in the repository that's marked by that tag.

Why is this useful? Well, amongst other things, it's a very simple way of keeping a single line of "production" and "development" under CVS control. Developers can check out the latest copy as normal. When they've produced a stable set of files, they can tag that point in time. (A tag can be moved by using cvs tag -cFR TAGNAME - the old position of the tag is lost when you do this.) Meanwhile, a working copy is kept checked out to the LAST_KNOWN_GOOD tag (say) on the production machine. By moving this tag forwards as they make improvements, the main production copy of the system remains stable. (This tactic is in use on the SPP project.)


Coffee will probably come here


Other stuff we can do with CVS here


Commit lists

If you have a developer list, it is quite handy to see the commit logs for a particular project as developers work on them. We can set you up with a commit list - this is simply a mailing list (that generally parallels the developer list) that all commit logs get send to, as checkins happen.

To get a commit list set up, you need to ask ilrt-sysadmin.


CVSWeb

If your project lives on our main CVS server, you can browse the CVS repository directly using CVSweb. This is a web interface that lives at http://cvs.ilrt.org/cvsweb/.

(I'll demonstrate this interface briefly now.)


Anonymous CVS

If you look at any open-source project, you'll probably find that they offer public access to their latest "developer snapshot". This is normally done via what's called anonymous CVS - that is, read-only access to a repository that doesn't require a login on the CVS server.

If you need it, we can offer anonymous CVS access to your projects. The example module is also available via anonymous CVS. Here's how I fetched it earlier:

> cd
> mkdir anon
> cd anon
> cvs -d :pserver:anonymous@cvs.ilrt.org:/cvsroot co example
cvs checkout: warning: failed to open /home/jan/.cvspass for reading: No such file or directory
cvs server: Updating example
U example/README
cvs server: Updating example/build
U example/build/build.sh
cvs server: Updating example/htdocs
U example/htdocs/index.html
cvs server: Updating example/htdocs/jan
U example/htdocs/jan/index.html

Automatic checkout

It's possible to configure our CVS repository to trigger pretty much arbitrary processing when commits are made. That's how SPP manages its commit list. It also has a developer website (managed as part of the SPP CVS module). As changes are checked into the website portion of the tree, the live website is updated to reflect those changes.


Things to plan ahead for


Laying out a module directory structure

OK, so let's say you're convinced, and want to start using CVS for your own projects. The important decisions it's worth making early are: how you're going to lay out your module directory tree; what you're going to commit; when you're going to commit; and how you can use simple automation to make your life easy. I present some simple guidelines here.

Separate concerns in your directory tree

If you have multiple developers working on your module, it's worthwhile trying to avoid conflicts as much as possible. If you can partition work up so that it naturally falls into separate directories, you'll find people treading on each others' toes much less frequently.

Future-proof: give yourself elbow-room

Even if you are only keeping a simple, static website under version control initially, avoid the temptation to put the top level of the htdocs hierarchy right in the top directory of your module. That way, you've room to add source code, build scripts, XSLT stylesheets, etc, in the future.

What to commit

Commit source files. If those source files are processed to produce some end product, commit the programs that do the processing. Generally, however, you don't want to commit the end results. Instead, you should try to automate your module so that anyone can check out the "starting conditions" and run your build process to produce the results for themselves.

For example, you might want to commit source code (.c files) and Makefiles, but you don't want to commit the compiled results (the .o files and a.out). You can stop CVS complaining about these files by using a .cvsignore file, as we'll see in a minute.

The one exception to not checking in compiled files may be where you're using a third-party library. There, it's sometimes worthwhile to check a copy of that library in. Don't forget to mark it as a binary file!

When to commit

Up to now, we've discussed static layout of files. Now we move on to policy and process issues.

The more developers you have working on a project, the more some simple guidelines about what makes a good commit are necessary. These don't have to be onerous.

For example, if you're checking in source-code, you should ensure that (at least) the code doesn't "break the build" - that is, it should compile, and the system should still run.

If you're running with a level of project automation that includes automated testing, then you should endeavour to ensure that all your test code runs correctly prior to a checkin. In particular, you should be careful to avoid "regressions" where changes you make will cause other people's code to fail the tests. Automation is largely about producing a development environment that your developers can trust. If you damage that trust by making automated tests worthless, you're doing yourself (and your project) a disservice.

This brings us neatly on to automation.


Automation

A small amount of automation can work wonders. Even our trivial example project can use automation. One of the benefits of CVS is that it lets multiple developers work in parallel on their own copy of the project. In order to maximise this benefit, it'd obviously be nice if they can roll out a running version from their own working copy. Rather than have them do this by hand, we'd like to make the process as simple as possible - so the developers can concentrate on manifesting Excellence(tm) in their work!

OK, what can we do to automate the production of a website? Well, we can make it available on the web, for a start. I've written a small script that can be used to copy our HTML files to a subdirectory of your public_html directory. (Providing you configure this correctly, files in your top-level directory will not be overwritten.)

You can take advantage of this small piece of automation as follows: firstly, use cd to move into your top-level example directory. You should be able to see the README file, and the build and htdocs directories.

To run the deployment script, you type the following:

> sh build/build.sh deploy

This is just a Unix shell script. From here on, the details I'm giving are particular to this script (which just copies files from one place to another). Obviously your own project might require a different set of operations to build; in which case, the specifics would vary - I'm just attempting to illustrate the principle of automation here.

Initially, this script will complain. What's going on here? Well, one of the things that can differ between developers is the precise detail of how they want this project deployed. In our case, we need to specify a destination directory for our HTML files (although nothing else; this deployment is trivial).

The idea of separating out local parameters for a deployment is an important automation pattern. how do we supply these parameters? In this case, the build script looks for a file called build.properties that contains a single line specifying the destination directory. My copy of the file looks like this:

DESTDIR=/space/home/cmjg/public_html/example

You should create this file (with the appropriate value) in your working copy - but do not check it in. This is important: this file is used to control the way your working copy is deployed. (Obviously, if I was running a production version of this website, I would be able to use the same script to target my production webserver.)

Once you have created the file, you should be able to run the deployment script to completion:

> sh build/build.sh deploy
Deploy progress:
Making destination directory /space/home/cmjg/public_html/example
Copying across HTML contents
mkdir: created directory `/space/home/cmjg/public_html/example/.'
mkdir: created directory `/space/home/cmjg/public_html/example/./jan'
`htdocs/./jan/index.html' -> `/space/home/cmjg/public_html/example/./jan/index.html'
`htdocs/./index.html' -> `/space/home/cmjg/public_html/example/./index.html'
Setting destination directory world-readable

At this point, I've got a deployed version of my project (and you should have too!). You can view my copy of this here: http://mail.ilrt.bris.ac.uk/~cmjg/example/ and your copy will be analogously available.

There's one final tweak we can make. If you run cvs up in your example directory now, you'll see CVS carp about the build.properties file - it hasn't been told about it.

> cvs up
? build.properties
cvs server: Updating .
cvs server: Updating build
cvs server: Updating htdocs
cvs server: Updating htdocs/jan

You can tell CVS to ignore files when it scans a directory. You do this by adding filenames ("*"-patterns can be used too) to a file called .cvsignore - this will have the desired effect. Of course, .cvsignore is just another file, so if we want every working copy to come with it preconfigured to ignore build.properties, we should check it in:

> echo "build.properties" >> .cvsignore
> cvs add .cvsignore
> cvs ci -m "Stop CVS carping about build.properties" .cvsignore

Miscellaneous

Phew! That's all the practical stuff for today. What's left are some odds and sods and the final opportunity for questions. (Although in general, CVS questions are welcomed on ilrt-tech).


Getting a CVS module for your project

If you want to version-control your project using CVS, your first port of call should be the ilrt-sysadmin mailing list. There we can deal with the generalities. You need to think about who will be working on the project (we don't need to know about everyone in advance) and whether it will need to be accessible for contributions from the non-ILRT people. It normally only takes about ten minutes to get a new project off the ground.

If you're after specific help with planning build automation (with or without CVS), then ilrt-tech is the right place to go.


Getting the latest copy (HEAD) of a third-party piece of software

Occasionally you may want to get the latest and greatest copy of a piece of open-source software. They may well offer anonymous CVS access. An example is the Apache Foundation's Jakarta project: http://jakarta.apache.org/site/cvsindex.html. They've got simple instructions there. I'm not going to demonstrate this fully because it can take a while to download some of their software, but there really is nothing more to getting hold of software via anonymous CVS than that!


Line end conventions / binary files

Troubleshooting tip: occasionally (and particularly noticeably if you use vi) you'll come across a text file that's full of MS-DOS linefeeds. You can tell this because vi shows you the ^M at the end of every line.

If this is happening it's because someone is using a version of CVS that thinks it's running on Unix in conjunction with an editor that thinks it's on MS-DOS. This can arise in a number of ways: for example, by using Samba to export a filesystem from Unix (where the CVS operations are performed) to a Windows desktop (where the files are edited).

Although it's tempting to just fix the files and check them back in, be careful. CVS will consider that to be a change to every line of the file and correspondingly think the change is larger than you anticipated. If this does occur, there are two steps to correct the problem:

In general, direct manipulation of the central repository is not for the uninitiated. It can have an impact on everyone's working copy and needs to be carefully coordinated with your developers.


Renaming part two

We mentioned the problem of lost history that arises when CVS "renames" a file. This can be mitigated somewhat be a repo-copy. This is where a CVS administrator copies the change history from one file in the CVS repository to another. The result is that the destination file "miraculously" inherits the change log of the source file.

For large-scale reorganisation of projects, it can be simpler to work directly in the repository. You should try to plan to avoid this; however, if you need to shuffle your project drastically then you should contact ilrt-sysadmin for assistance.


Troubleshooting working copy problems

Here are two more things that can catch you during day-to-day operation of CVS.

Forgetting to add -kb

> cvs add my_face.png

When you add a binary file, it's easy to forget to specify the -kb flag. Unfortuantely, it's a pain to get CVS to change the flag for you. A quick way to correct this error is to edit the CVS/Entries file. You'll see that this file has one line per version-controlled file. Identify the line that's wrong:

/my_face.png/0/Initial my_face.png//

and change it so that -kb appears between the final slash marks:

/my_face.png/0/Initial my_face.png/-kb/

Now, you're a real CVS wizard! cvs status will report the file as being marked as a binary, and CVS will be able to deal with it correctly:

> cvs status my_face.png
===================================================================
File: my_face.png       Status: Locally Added

   Working revision:    New file!
   Repository revision: No revision control file
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      -kb

If you've got as far as checking in the file, you need to look at the cvs admin command. There are specific details on this in the CVS Book.

When your working copy (or a file, or a subdirectory) goes really bad

Occasionally, you just want to wipe the slate clean. Fortunately, CVS is pretty forgiving in this regard. If things go badly wrong in a working copy, you can (generally) move (rename or delete) the offending files "out of the way" and run an update, which should restore your working copy to a reasonably sane state.

(If you use the rename rather than delete option, you still have the chance to then salvage - by hand, alas - any very recent changes you'd made to the files.)


CVS on a Windows desktop

Although it has Unix roots, CVS is available on Windows too. There are two options: Cygwin and WinCVS.

Cygwin will essentially give you a bunch of Unix shell utilities. If you select it for inclusion, you'll get CVS. However, you'll get a CVS that "knows" that it's running on Unix, and will consequently adopt the Unix line-end convention. If you are planning on going down this route, then you'd be well advised to get a Windows editor that can understand Unix line-ends (PFE or TextPad are examples).

WinCVS comes in two flavours. There are command-line utilities (which know they're running with MS-DOS line-ends!) and a graphical CVS client, about which I have some reservations. I tend to find that once you've got a crib sheet of the common CVS operations (adding, editing, updating) a graphical client isn't as convenient as the command-line tools - possibly used in conjunction with CVSWeb.

I've instructions on setting up a Windows environment to use CVS here: http://ioctl.org/unix/cvs/client-win32.

And finally...

After this whistle-stop tour, you might want to take a look at the on-line CVS Book [http://cvsbook.red-bean.com/] which has more details - plus a very good FAQ section.