A Quick and Neat Guide to CVS

Giorgio F. Signorini, 2009

Note: this is a modified and extended version of A Quick and Dirty Guide to CVS by Steve Revilak

Creative
Commons License This document is licensed under a Creative Commons (Attribution-NonCommercial-ShareAlike 2.5) license.

Contents

Introduction

Overview

Like RCS, CVS is a version control system. Unlike RCS, it allows multiple developers to work on a file at the same time; the C in CVS stands for "concurrent".

This document is a simple introduction from a user's point of view. We will start assuming that a repository already exists, as well as a module inside it. That is to say, someone has already run cvs init, to initialize the repository, and cvs import to add the first group of files.

We will discuss initializing a repository and starting a module in a later section.

As a general reference, the main CVS manual is available with

info cvs
or online, for instance at http://ximbiot.com/cvs/manual/feature.

The manual of the Emacs interface to CVS is

info pcl-cvs

Setting up Your Environment

Before using CVS, you'll need to set up environment variables. For example, in bash:

export CVSROOT=/path/to/cvsroot

CVSROOT should be the directory path to the repository. Place these in your .profile, or where ever you normally put such things.

Another useful variable is CVSEDITOR. When ever you commit files, cvs will invoke this program and allow you to provide comments about the change you are making. As a personal preference, I like "emacs -nw --no-init-file"

However, if you are going to use CVS from within emacs (which I strongly recommend) you won't need to set an external editor.

General Syntax of CVS commands

CVS commands take the following form

cvs cvs-options subcommand subcommand-options

subcommand is the thing you are asking cvs to do -- check files in, check files out, do diffs, etc. To see the syntax of a given command

cvs -H subcommand
cvs --help will tell you how to get a list of what the different subcommands are.

Basic work cycle

Checking Files Out

When working with CVS, there are 2 copies of files that you need to be concerned with:

  1. The repository, which is visible to everyone
  2. Local copies, which are visible to you

Before doing anything else, you'll need to checkout a local copy of the repository files. Here's an example:

$ cvs checkout mymodule
cvs checkout: Updating mymodule
U mymodule/file1

$ ls
total 1
   1 mymodule/

So what just happened? "mymodule" is a module in the repository. Checking the module out placed a local copy of each file belonging to the module in the current directory. Changes can be made to files here, then put back (committed) to the repository.

Modules are really just directories underneath $CVSROOT. In other words:

$ ls $CVSROOT
total 2
   1 CVSROOT/     1 mymodule/

CVSROOT contains configuration and administrative files used by CVS. We won't worry about that now.

Editing Files

Editing files is easy - once you have local copies, just edit them. None of your changes will be visible to other users until after you've committed them.

If you mess up a local copy of a file, starting over is easy. Delete the file, and use cvs update to get a fresh copy from the repository.

Seeing What You Have Changed

To see the changes that you have made since you checked out your local copy, use the command cvs diff.

Keep in mind that cvs diff shows the difference between your working copy and the version it was based on. To view differences with respect to a different version, type, e.g.

$ cvs diff -r 1.12

Often, you want to see differences between your working copy and the most recent version of the same file (if a new version has been committed by others):

$ cvs diff -r HEAD
(HEAD is a "tag" that labels the latest version of a file).

Sometimes it is convenient to display the output of diff in "context output" format:

$ cvs diff -c

Seeing What's Changed in the Repository

If you're working with a group of developers, remember that they're making changes too, and possibly committing them.

To see if someone has changed a particular file, use cvs status.

$ cvs status file1
===================================================================
File: file1             Status: Up-to-date

   Working revision:    1.2     Thu Oct 10 14:49:15 2002
   Repository revision: 1.2     /home/srevilak/c/mymodule/file1,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)

"Up-to-date" means that the file is current. If the repository has a version that is newer than the one you checked out, you get a different "Status: " message.

Without a file name, cvs status prints out information about each file in the current dir.

To get a quick look at everything, one can use cvs -n update (see "refreshing local copies" below).

Refreshing Local Copies

Periodically you'll want to update your working copies. This is done with the cvs update command.

$ cvs update -P -d
cvs update: Updating .
U file1

Above, we see that someone had modified and committed file1, and the copy in the current directory was out of date; cvs updated file1 to the current version. The flags to update are optional. -P "prunes" directories that are empty, and -d tells cvs to include any new directories that aren't in your local workspace (the default behavior is only to include directories that you have checked out). Once you have a local copy, cvs checkout and cvs update -d are more or less equivalent. Note however that in general update operates on files, while checkout operates on a whole module.

The update subcommand can also take arguments, if you want to update specific directories, or specific files within a directory. If no arguments are given, cvs recursively updates the directory tree rooted at the current directory.

Where local files don't match the repository copies, cvs update prints one line per file, with a one letter code in front of the file name:

U the working file has not changed from the version it was based on; but a more recent version was found in the repository, and the working copy was brought Up-to-date with it
M the working file has been Modified with respect to the version it was based on; also, if the repository copy is newer than that one and contains changes that do not conflict with yours, these were merged into the working copy
C like M, but a Conflict was found; a merge was attempted (see "resolving conflicts" below)
? the file is in your working dir, but not in the repository

If you type cvs -n update, you'll see the name of the files that need updating/committing and the status codes (U, M, C, ?, ...) corresponding to what update will do if called without the -n option

$ cvs -n update

The -n option tells cvs "don't change the disk".

You can think of cvs -n update as a means of getting the same info as from cvs status in a more compact form.

Committing Changes

Okay, you've done some work and you're happy with the results. To incorporate your changes into the repository, use cvs commit.

$ cvs commit filename

CVS will invoke CVSEDITOR so that you can make comments. Once you quit the editor, the changes will be put back into the repository.

Commit comments should be a short description of what you did, enough to allow other developers to look at the file's log and get a sense of what's been done to it. The GNU folks have an entire article dedicated to the subject of documenting changes: http://www.gnu.org/prep/standards/html_node/Change-Logs.html#Change-Logs.

If a file is not up to date with the current revision, commit will notify you, and exit without committing. What you must do is: update the file first, then commit.

Concurrent editing

By now, you are probably wondering what exactly happens if someone has committed a new version of some file while you were making changes to the same file. When you run update, one of two things may happen:

In the former case, the changes in the repository are merged into your working copy (and you are informed of that); in the latter case, a conflict is evidenced and you have to sort this out. This is the subject of the next section.

Be aware of what "overlap" means in this context. CVS has no knowledge of the semantics of your file, so it only checks whether lexical changes were made to the same lines; even if not, the meaning of your file (for instance, a FORTRAN routine) may have been altered.

Resolving Conflicts

Eventually, something like this will happen:

$ cvs commit foo.java
cvs commit: Up-to-date check failed for `foo.java'
cvs [commit aborted]: correct above errors first!

Here, you've made changes to the foo.java, but someone else has already committed a new version to the repository (e.g. the repository version has a higher number than your local copy). Before you can commit the file, you'll need to update your working copy.

If you and the other developer were working on different areas of the file, cvs is pretty intelligent about merging the changes together; it might see that the last set of modifications are in lines 75-100, and your changes are in lines 12-36. In this situation, the file can be patched and your work is unaffected.

However, if the two of you changed the same area of the file it's possible to have conflicts:

$ cvs update foo.java
RCS file: /home/srevilak/c/mymodule/foo.java,v
retrieving revision 1.1
retrieving revision 1.2
Merging differences between 1.1 and 1.2 into foo.java
rcsmerge: warning: conflicts during merge
cvs update: conflicts found in foo.java
C foo.java

Oh dear! What do we do now? The answer is "fix the merge". Two things have been done to help you with this.

  1. A pre-merge copy of the file has been made.
    $ ls -a .#*
       1 .#foo.java.1.1
    
    However, being a dotfile, it's presence isn't immediately obvious
  2. cvs has inserted a conflict marker in your working copy.
    <<<<<<< foo.java
      static final int MYCONST = 3;
    =======
      static final int MYCONST = 2;
    >>>>>>> 1.2
    

The conflict lies between the rows of greater than and less than signs. The thing to do now is decide what version is right, remove the conflict markers, and commit the file.

Common tasks

Retrieving a different revision

Normally, cvs subcommands like checkout and update use the latest revision of files as reference. This can be changed using the -r option, which tells cvs to retrieve a different revision; for example, the command

cvs update -r 1.2 file1
will merge the content of revision 1.2, instead of that of the latest revision, with your local changes in the working file.

Since the -r option brings the file back to a past revision, it prevents the file from being committed afterwards (which would change the file's history). So use the -r option if you want a static copy of a file - but don't want to work on it.

Merging Revisions

In some special cases, changes in your working copy are to be merged, not with a named revision, but rather with differences between two named revisions of the file. This is done with two -j options

cvs update -j old.rev -j new.rev

After you have done this, you can commit to a new revision. Two examples will show useful applications of the -j option:

Note that, when working with multiple files, the -r and -j options are most useful with tags and branch tags.

Backing out a Bad Commit

Let's suppose that you've committed a file, but this ended up breaking something horribly. Here's how to undo your commit:

  1. Get the version number from after the commit. You can use an $Id$ keyword within the file, or cvs status. Let's say that the new version is 1.5.
  2. Get the version number from before the commit. Typically, this will be one lower than the current version. Let's say that the old version is 1.4.

Now do this:

cvs update -j 1.5 -j 1.4 filename
cvs commit filename

The above is an example of a merge. You've asked cvs to take the difference between versions 1.5 and 1.4 and apply them to your working copy. The ordering of version numbers is significant - think of it as removing changes, or going backward in version history.

Editing a file ouside the CVS directory

Normally, it's best to edit files in the directory that you're using for checkouts. This way, cvs will automatically take care of merging in changes, just by running cvs update. However, in some cases that might not always be possible.

Hypothetical Situation: you took a copy of Myfile.java home, and did some work on it. In the meantime, your fellow developers have committed changes to the file. The dilemma - you'd like to incorporate what you've done, but your copy of the file is now out of date. Of course, you also don't want to undo work that others have done. Here's a way to deal with this situation.

  1. Find out what revision your copy of the file is based on. This will be the revision number in the $Id$ or $Revision$ tags. If you can't determine the revision, this approach won't work, and you'll need to do a manual merge.
  2. Run cvs update to refresh your repository copy.
  3. Run cvs log MyFile.java (in the appropriate directory) to get the revision number of the copy that you just checked out of the repository.

For the sake of illustration, lets say that the copy of MyFile.java that you were working on at home is revision 1.6, and the current repository version is 1.10.

Copy the MyFile.java that you worked on at home to your checkout directory. We now have the following arrangement:

To pick up the modifications made from 1.7 - 1.10, you need to merge:

cvs update -j 1.7 -j 1.10 MyFile.java

In cvs-speak, this means "take the changes from revision 1.7 through revision 1.10, and apply them to the local copy of the file." Assuming that there were no merge conflicts, examine the results:

cvs diff -w MyFile.java

Make sure it compiles, then commit.

If things didn't go well, you'll need to examine the results and resolve any conflicts that happened as a result of the merge.

Adding Files and Directories

Files and directories are added with the cvs add command. To add a directory:

mkdir newdir
cvs add newdir

To add a file

# create the file, edit it
cvs add newfile
cvs commit newfile

To add a binary file

cvs add -kb newfile
cvs commit newfile

-kb tells cvs that the file is a binary file, and that it shouldn't try to expand tags (such as $Id$.) that appear in the file's body. CVS understands many of the same tags that RCS does. See http://ximbiot.com/cvs/manual/feature/cvs_12.html#SEC100, or the co(1) manpage.

Deleting Files

To get rid of files, use cvs delete:

rm filename         # must remove working copy first
cvs delete filename
cvs commit

CVS uses a "lazy" system for file deletion; delete just changes the way that the file is stored in the repository. It's still possible to undelete the file, or to check out revisions that existed before the file was deleted. However, the file will no longer appear when you do checkouts or updates.

Because of this, it's not possible to delete a directory entirely. However, one can use the -P flag with cvs checkout and cvs update to prevent empty directories from being retrieved.

Tags and Branching

Static tags

Creating tags

A tag is a symbolic name that can be used in place of a revision number. Although it is possible to tag a single file, usually a tag is given to all files in a module, to make a snapshot of it at a certain stage.

cvs tag tagname [ file ... ]
tags one or more checked out files (default: all files in the working dir)
cvs rtag tagname module
tags one module altogether

The difference between the two subcommands is that tag is applied to the checked out revision of files, while rtag is applied to the most recent revision.

Note that what is tagged is the repository copy, not the working file; use `cvs tag -c' to check for uncommitted changes in your working files.

Using tags

With checkout, update, diff, etc, one can use the tag name instead of the version number after the '-r' option.
cvs checkout -r mytag myproj
cvs update -r mytag file1.c 

Remember that, unless mytag is a branch tag, -r produces a static, unchangeable copy of a project or file. To revert from the latest revision to a tag, and work on, one must use the -j option instead:

cvs update
cvs update -j HEAD -j mytag 
This brings the working copy back from the latest revision (HEAD) to `mytag'. A `cvs commit' will now save the backed-out versions of files as new revisions (the revisions between mytag and HEAD are not deleted from the repository).

Branches

Creating branch tags

Branching is a process that allows different versions of a file to be developed in parallel. The cvs manual gives a visual representation here: http://ximbiot.com/cvs/manual/feature/cvs_5.html#SEC60.

You create a branch with

cvs tag -b branchtag .
or
cvs rtag -b branchtag myproj

When creating branches, it's always useful to make both a static tag and a branch tag. For example

  cvs rtag -r HEAD release_1_base mymodule
  cvs rtag -b -r release_1_base release_1 mymodule

This makes it very easy to see what's changed on the release_1 branch afterwards.

Working on a branch

To start working on a branch you must first checkout your module to that branch-tag. This is best done on a separate dir; following on the previous example you would do something like
  cd ..
  mkdir Release1
  cd Release1
  cvs checkout -r release_1 mymodule
In old, buggy CVS versions you may have to split the last command in two, first a checkout then an update:
  cvs checkout mymodule
  cvs update -r release_1 

Unlike normal tags, branch tags are not static: they are not associated to a definite revision, but with the most recent revision on the branch. Like HEAD on the trunk, a branch tag is dynamically attached to the tip of the branch. Thus, if you update (-r) to a branch, you can make changes to files and commit them: the new revisions will be placed on that branch.

Merging from a branch to the trunk

Occasionally, you want to merge the work done on a branch into the main trunk.

To do this, you place yourself on the trunk and do a merge using the branch tag:

  cvs checkout myproj
  cvs update -j branchtag   

If you use keywords in your files, you will probably want to clear up their values using the -kk flag when merging from branch to trunk

  cvs update -kk -j branchtag   

More on this in the CVS manual, section Merging an entire branch and following

Miscellanea

Other Useful Commands

There are a variety of other useful cvs commands. Here are a few examples:

cvs diff -r 1.2 -r 1.3 filename Shows differences between versions 1.2 and 1.3. (regardless of what version your local copy is).
cvs log filename Show the commit log for filename (like rlog does with rcs).
cvs annotate filename Shows each line of filename, prefixed with the version number where the line was added, and the name of the person who added it. Useful for seeing who made a particular set of changes.

How to split a branch at the root

If created with cvs import, a project will always set up a branch 1.1.1 (tag vendortag) with file revision number 1.1.1.1 (tag releasetag). However, on the first checkout you are placed on the main trunk, and if you edit and commit, revision 1.2 (not 1.1.1.1.2) will be created. Branch 1.1.1 is just a placeholder for possible new "releases" of the "vendor".

To split a branch right at the root, use

cvs rtag -r 1.1 -b branch_starting_from_root mymodule
This new branch will have the number 1.1.2.

A branch 1.1.1.1.2 would have been created if you had used cvs rtag -r releasetag ...

Start-up tasks

Initializing a repository

To create a repository, run the cvs init command. It will set up an empty repository in the CVS root specified in the usual way. For example,

cvs -d /usr/local/cvsroot init

Starting a new project

A project is self-contained collection of files and directories known in CVS as a "module". Most commonly, you will create a new module starting from a directory tree of files you already have. If that directory is named myproj you will type e.g.

cd myproj
cvs import -m "initial import into CVS" myproj signo start
where
myproj
is the name of the subdir of $CVSROOT where the files will be stored (not necessarily the same name of the original dir) that will become the name of the "module"
signo
is the "vendor tag" (your username is OK)
start
is the "release tag"

After you did this, it is best to remove the original directory (make a backup first) and then check it back out with CVS:

cd ..
rm -r myproj
cvs checkout myproj 
in this way you prevent yourself from accidentally working with files NOT under CVS control.

The emacs interface to CVS

GNU Emacs has a built-in universal interface to the most popular versioning systems (RCS, CVS etc), called VC (Version Control).

There is also, as a separate package, a more powerful interface specifically designed for CVS: PCL-CVS.

You can see and access them in the "Tools" Menu.

Both interfaces must be used after making an initial checkout of a module from the command line.

I would recommend using PCL-CVS, if available, since it is closer to the CVS way of thinking, and resort to VC only if PCL-CVS is not available.

PCL-CVS

PCL-CVS is used mainly through its own directory buffer. So you have to examine the directory (using the Tools / PCL-CVS menu) first.

By moving the cursor to one file in the *cvs* buffer and typing a key sequence or using the CVS menu you can

Task CVS command PCL-CVS key sequence PCL-CVS command
see what you have changed cvs diff = cvs-mode-diff
see what you have changed in an ediff section d e cvs-mode-idiff
see what has changed in the repository cvs -n update e cvs-mode-examine
refresh local copies cvs update O cvs-mode-update
commit changes cvs commit C cvs-mode-commit-setup
add files cvs add a cvs-mode-add
remove files cvs remove r cvs-mode-remove

If you mark a group of files in the *cvs* buffer, then the commands you give will be executed against all of them.

VC

Task CVS command VC key sequence VC command
see what you have changed cvs diff C-x v = vc-diff
see what has changed in the repository cvs -n update C-x v d vc-dir
refresh local copies cvs update C-x v + vc-update
refresh local copies / commit changes cvs update / cvs commit C-x v v vc-next-action
add files cvs add ; cvs commit C-x v i vc-register
revert to base version cvs update -C -p -r BASE file > file C-x v u vc-revert
annotate cvs annotate BASE C-x v g vc-annotate

The command vc-next-action or C-x v v is special; it is supposed to do what you mean to do:

The command vc-dir or C-x v d prompts for a directory and then opens a buffer containing all modified files in that dir.

In the *vc-dir* buffer you can give the above commands against one file moving the cursor to the file and typing the appropriate key sequence or clicking the appropriate item in the VC-dir menu

Using a remote server

There are a number of ways a repository can be used by remote users. Here we mention only two.

Using a remote server through rsh/ssh

A remote user that has a login account on the host where the repository is placed, and can access it through rsh or ssh, can send CVS commands to the repository through that remote shell.

Once the remote shell (say, ssh) is working, the remote user must

  1. set up CVS_RSH

    On the client machine do (e. g.)

    export CVS_RSH=ssh
    or put it in ~/.bashrc

  2. define a remote CVSROOT

    On client do (perhaps in ~/.bash_profile)

    export CVSROOT=user@cvs-server:/var/cvs
    or something like that

    (NOTE that user on cvs-server must have write access to /var/cvs )

After that, he can use cvs-server in a transparent way.

As for any CVS command, you can avoid setting CVSROOT and use the "-d cvs-server:/var/cvs" option.

Password Authenticating Server

The Password Authenticating Server (inetd service port 2401) is only useful when CVS is used by many remote clients that don't have a user account on the server. See the manual section on that.


$Id: cvs.html,v 1.32 2009/10/21 13:40:09 signo Exp $