A Brief Guide To Using CVS



 

Introduction


CVS is the version control system being used preferentially in MIDAG.  This document will give you enough information to begin using it.  The official reference manual (about 170 pages long) at www.cvshome.org should be consulted for details.  The online manual page, cvs(1), is also very helpful (and is only 17 pages long).  This document is an abridgment of the official reference manual.

Please note that some care should be taken when using CVS.  Because it is used to store code, make files or other text that will be used and modified by others, it is easy to cause confusion and unnecessary effort by others. Remember, using CVS is no substitute for communications between its users.

This document will give you a basic working knowledge of CVS as applicable to the Pablo project.  When in doubt about doing something not adequately covered here, please check the complete reference manual.

On Windows NT systems, the WinCVS tool, available at www.wincvs.org, may be used, or Cygwin 1.0 contains CVS in the C:/usr1/bin directory (usually mounted on /usr1/bin). However, before you try using any of these tools, please read the rest of this document.
 

The basic commands


To begin, you will want to set the CVSROOT environment variable; for example, those working on M-Reps projects will want to use this definition:

    % setenv CVSROOT /afs/unc/proj/mip/mreps/cvs_repository
As an alternative, the option
    -d /afs/unc/proj/mip/mreps/cvs_repository
may be used on all CVS commands.

Also, be certain you are using the latest version of CVS, 1.10, by using the command

    % cvs -v
to print the version number.  At least two versions are installed in Computer Science, and they are known to not be compatible with each other.

Typing the cvs command with no options or arguments will give a message describing the syntax of the possible commands.  Notice that there are primarily three different help commands:

    % cvs --help-options
    % cvs --help-commands
    % cvs -H <cvs command>
The last of these will ultimately be the most useful.
 

Getting a copy of existing source


The cvs checkout (abbreviated, "co") command is used to obtain part or all of an entire project.  It can be used in several ways.  If the entire project or a full directory is desired, the command

    % cvs checkout <module name>
will create a directory having the specified module name and containing the requested files.  A module name is usually the name of a directory in the CVS repository.   It may refer to an entire project or to part of the project.  For example, at the time of this writing, the M-Reps repository contains, among other directories, one  named "Pablo".  To check it out use:

    % cvs co Pablo

This will create the Pablo directory, recursively, at the current location.  To get only the "planes" subdirectory of "Pablo", do this instead:

    % cvs co Pablo/planes

A module can also be a named collection of projects.  For example, the name "pablo_all" is a substitute for the "Pablo" directory and the "pablo_users_manual" directory.  Checking out "pablo_all" will produce both trees.

The only way to determine the list of "collective" module names is to look at the end of file $(CVSROOT)/CVSROOT/modules, where they are defined.  Not all CVS commands work with module names.  Note that the CVSROOT directory is for administrative use by the CVS program; in general you should just ignore it until you have the appropriate expertise.  You will also notice that CVS will create a few directories and "dot" files in your checked out code.  Don't tamper with these until you understand their function.  The cvs export command can be used to check out a directory tree that does not contain the administrative files.

Besides these directory specific checkouts, it is possible to get single files.  For example, the command

    % cvs co Pablo/m3d/src/Quat.cpp
will get obtain the Quat.cpp file, inside the intermediate directories,  "m3d" and "m3d/src".  Note that at least one directory is always created.

Unlike other CVS commands, which take file names as arguments, the <module name> argument of cvs checkout is really a reference to the contents of $(CVSROOT).  Therefore, any checkout performed causes the files produced to be rooted at the current location.
 

Making your changes
 

Now, assume that you checked out a copy of the Pablo source and want to make a change to it and then update the repository copy, so that others may make use of your cleverness.  How should you proceed?

First, you may want to assure that you have the latest copy of the project code, before making your change.  To do this, type

    % cvs update <files>
where the argument is a list of file names, usually directories.  If you are inside a directory checked out from CVS and want to update that directory and all its files and directories recursively, you may say
    % cvs update .
Using update will guarantee that you have the latest copy, including any changes recently made by others.  Any modifications you have made to a file are never lost when you use update.  If no newer revision exists, running update has no effect. If you have edited the file, and a newer revision is available, CVS will merge all changes into your working copy.

The last sentence is important.  CVS does not use RCS-style file locking, so two (or more) individuals may be simultaneously developing changes in the same file.  As some are checked in and others updated, conflicts between the revisions may arise.  If update finds a conflict, it uses the symbol 'C' in its output to tell you the conflict exists.  You will have to open the file to find the conflict.  It will be visible as sections of code delimited much like the output of diff using '<<<<<<<' and '>>>>>>>'.  Files that CVS has modified this way will, of course, fail to compile, until you resolve the conflicts.  In this way, CVS assures that no one's changes are lost as a result of its actions.  After you have resolved the conflict, you should check in the corrected file; CVS never checks in files that contain conflicts.

A command similar to update is status; it essentially reports what an update would do without doing it.  This can be useful, if you are afraid an update may do more harm than good.  Its only drawback is that its output is more verbose.  When run on a directory tree, piping the output through grep to look for the words "conflicts", "Locally" and "Needs" will identify files that may require further consideration.  Section 10.1 of the CVS reference manual lists the full text of the reports to which these strings correspond.  The command

    % cvs status | grep Status | grep -v Up-

is also useful for listing all statuses different from "Up-to-date". Alternatively, all statuses can be listed by piping the cvs status command to grep "File".

At this point it is safe to modify your checked out copy.  Before checking in your changed files, you may want to compare them with the repository copy.  This can be done with

    % cvs diff <files>
The cvs diff command only compares your files with the "revision" of the files they were based on.  Unless you have just used the update command, you cannot be certain that someone else has not recently changed the file, thus producing a new revision.

You should then make all your changes and verify that they are correct.  When you are finished, it is a good idea to clean things up (run "make clean", for example), before inserting your changes into CVS.  This is not necessary, but is less likely to be confusing, at least at first.

To update the repository to reflect your changed files, use the "commit" command:

    % cvs commit <files>

CVS will then start an editor, to allow you to enter a log message. You should enter text that will be reasonably likely to give others an idea what was changed and the reason for the change.  You need not put the date or your name because they are automatically recorded.  After you exit the editor the repository will be updated.  The environment variable $CVSEDITOR, or $EDITOR if  it is not set, determines which editor is started.

In some cases, you will find that CVS will put you into editing mode more than once.   You won't have to change the message, however.  if you don't want to.  If you want to guarantee this won't happen and avoid starting an editor, you can instead specify the log message on the command line using the '-m' flag, like this:

    % cvs ci -m "Replaced the matrix class" m3d
Here the abbreviation "ci" has been used for "commit"; "ci" derives from the RCS "check in" command.

It is important to realize that the cvs commit command by itself does not add or delete files from the CVS registry. The next sections will discuss ways to do this.

Once you are finished with a directory you can simply remove it, of course, but CVS provides a way to do this that makes certain you have not made changes you have forgotten:

    % cvs release [-d] <module name>

Basically, the release command provides a safe way to cancel a checkout; it will report all uncommitted changes.  The '-d' option causes the checked out directory to be removed.  Whether or not it is used,  the command will update the CVS history database to indicate that you are finished with the module.
 

Adding files to a directory


Creating single files
 

To add a new file to a directory, follow these steps.

Thus, the "add" command schedules files for addition to the repository. The files or directories specified with add must already exist in the current directory.  To add a whole new directory hierarchy to the source repository (for example, files received from a third party vendor), use the import command instead.

Here is an example of adding C++ code:

    % cd Pablo/pablo
    % cvs add src/P3DRotate.cpp include/P3DRotate.cpp
    % cvs commit -m "Early version.  Not yet compilable." include src
Here is an example of checking in a binary file:

    % cvs add P3DRotate.doc
    % cvs commit -kb -m "Early version. Not yet compilable." P3DRotate.doc
 

Creating a directory tree from a number of files


First, a warning.  The import command described here can be rather dangerous.  If you misuse it, for example, you may inadvertently check in your entire home directory.  Mistakes in importing are not so easy to correct, so please be careful to use it correctly.

Let's assume you need to add a new library to Pablo and it will go in a new directory tree.  To integrate it into Pablo and test it, you first should have obtained a copy of Pablo and added your new files to it.  Before proceeding, you may want to check to be sure the file permissions are reasonable.  Once you have cleaned everything up (be sure to remove all files not being checked in!) and are ready to check the new hierarchy into CVS, you can do this:

    % cd Pablo
    % cvs import -m "Imported the new library sources" Pablo/newlib MIDAG start
Unless you supply a log message with the '-m' flag, CVS starts an editor and prompts for a message. The string "MIDAG" is a vendor tag, and "start" is a release tag. They may fill no purpose in this context, but CVS requires them, and they should not be changed so our registry retains consistency.

You can now verify that the import worked, and remove your original source directory, as follows:

    % cd ..
    % mv Pablo/newlib newlib.orig
    % cvs checkout Pablo/newlib
    % diff -r newlib.orig Pablo/newlib
    % rm -r newlib.orig    # Do this if the previous command reported no errors
Erasing the original sources outside of CVS is a healthy precaution against accidentally removing the CVS copy.

Note: this procedure will not work correctly if the directory tree contains binary files.  Refer to the CVS manual for details.

Methods similar to this can be used to create a new project in CVS.  Again, see the documentation for more information.
 

Removing files


Here is what you can do to remove a file, but remain able to retrieve old revisions:

The remove command schedules file(s) to be removed from the repository (files which have not already been removed from the working directory are not processed). This command does not actually remove the file from the repository until you commit the removal.
Here is an example of removing several files:
    % cd test
    % rm *.c
    % cvs remove
    % cd ..
    % cvs commit test
As a convenience you can remove the file and cvs remove it in one step, by specifying the '-f' option.  For example, the above example could also be done like this:

    % cd test
    % cvs remove -f *.c
    % cvs ci *.c

To remove a directory, first remove all the files in it.  Then remove the directory in the same way.
 

Moving and renaming files


Moving files to a different directory or renaming them is not difficult, but some of the ways in which this works may ot be obvious.  (Moving or renaming a directory is even harder).  The normal way to move a file is to copy it, and then issue the normal CVS commands to remove the old file from the repository, and add the new one to it.

    % mv old.cpp new.cpp
    % cvs remove old.cpp
    % cvs add new.cpp
    % cvs commit -m "Renamed old.cpp to new.cpp" old.cpp new.cpp
This is the simplest way to move a file, it is not error prone, and it preserves the history of what was done.  Note that to access the history of the file you must specify the old or the new name, depending on what portion of the history you are accessing.
 

Other features

 

Adding release numbers


It is possible to designate release numbers periodically for an entire project stored in CVS, so that past releases can be checked out at later times, without having to figure out which revisions of files to use.  The cvs rtag command is used to achieve this for an entire project:

    % cvs rtag Pablo-0-5-0 Pablo

There are similar commands for tagging part of a project, but this is generally not a desirable thing to do.  Tagging does not work for empty directories, but an easy way around this is to first check in an empty .cvsignore file in all empty directories that must be produced when a release is checked out.

This example also gives the preferred syntax for naming releases in MIDAG:

    <product name>-<major release>-<minor release>-<version>.

Sticking to this syntax exactly will avoid confusion in the future.  This particular release of Pablo can be checked at any time in the future out by the command:

    % cvs co -r Pablo-0-5-0 Pablo

Tagging should not be used very often, but at strategic times, when it is appropriate to designate a new release number.
 

Learning who is editing files


CVS provides a capability for watching files to see when someone changes them.  For example:

    % cd Pablo/pablo/src
    % cvs watch on pablo.cpp

This capability can be refined to indicate the kind of actions for which CVS should watch.  Read the documentation, if you are interested in using this feature.
 

Access collisions


Occasionally, two people may try to update the same file simultaneously.  When this happens, the one that started the update last will receive a message something like this:

    [11:43:23] waiting for bach's lock in /usr/local/cvsroot/foo

CVS will try again every 30 seconds, and either continue with the operation or print the message again, if it still needs to wait. If the problem seems to stick around for an undue amount of time, find the person mentioned in the message and ask him about the CVS command he is running. If he is not running a CVS command, ask him to look in the repository directory mentioned in the message and remove files which he owns with names that start with '#cvs.rfl', '#cvs.wfl', or '#cvs.lock'.
 

File histories


All files stored in CVS have some history recorded about them.  This takes two forms, the log messages required when any file is changed, and a record of the access history of each file.  The later may be disabled, but we have chosen not to do so.  To see the log messages of a file use a command such as:

    % cvs log Makefile

This will list the log messages by release number for the indicated file.  To see the access history of the same file, the command used would be:

    % cvs history Makefile

The resulting output is far less useful, in general, than the log.
 

Cross-platform issues


Problems can easily arise when moving files between UNIX or LINUX and Windows NT. Windows terminates lines with a <return><newline> combination, while UNIX merely uses <newline>. Files created on a Windows ystem, transfered without change to UNIX, and edited with vi(1) will show each line ending with "^M", the escape sequence for <return>.

This issue is more complex than it might initially seem. The different compilers one might use can be sensitive to the presence or absence of <return> characters. In particular, it appears necessary to use the Solaris C++ compiler, that all header (.h) files end with a newline, that is, that their last line be a proper line in UNIX. The Visual C++ compiler on Windows can have problems counting lines, and thus reporting errors, if the lines of code do not end in <return><newline> combinations.

For cross-plaform development, there must clearly be a decision regarding which style to use when storing files in the CVS repository. Based on the properties of the compilers and the fact that Pablo is primarily being developed on Windows NT, it will be best to have <return> characters at the end of each line, except the last line of included header files. The Pablo project has been put in CVS in this way. Persons editing files on UNIX or LINUX can readily adapt to the presence of the carriage returns.

The bigger problem is assuring the last line of header files has no <return>, when working on Windows systems. This cannot be done within Visual Studio, so it is probably best for Windows users to check any new header files into CVS and then use a UNIX (or LINUX) system to add a last line that is UNIX-compatible. If developers take the time to assure that the code compiles on UNIX or LINUX, most of this effort will be needed anyway.

There appears to be no effective process for transferring a copy of Pablo checked out in UNIX to Windows, without corrupting the line terminations. However, this is never really necessary. The Cygwin 1.0 version of CVS can be used, if the files are checked out in binary mode:

    % setenv CVSROOT //samba1/unc_afs/project/mip/mreps/cvs_repository
    % cvs co -kb Pablo
The WinCVS tool also seems to handle the conversion correctly [I've not yet verified this]. Thus directly checking out files can be made to work. Before checking files in, as described above, a cvs diff should always be run and examined for evidence of a conversion of line termination characters. If you see an unexpectedly large number of changes, that could be the reason.