This document is licensed under a Creative
Commons (Attribution-NonCommercial-ShareAlike 2.5)
license.
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 cvsor online, for instance at http://ximbiot.com/cvs/manual/feature.
The manual of the Emacs interface to CVS is
info pcl-cvs
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.
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 subcommandcvs --help will tell you how to get a list of what the different subcommands are.
When working with CVS, there are 2 copies of files that you need to be concerned with:
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 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.
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
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).
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.
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.
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:
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.
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.
$ ls -a .#* 1 .#foo.java.1.1However, being a dotfile, it's presence isn't immediately obvious
<<<<<<< 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.
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 file1will 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.
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.Let's suppose that you've committed a file, but this ended up breaking something horribly. Here's how to undo your commit:
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.
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.
$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.
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.
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.
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.
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.
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.
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 mytagThis 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).
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.
cd .. mkdir Release1 cd Release1 cvs checkout -r release_1 mymoduleIn 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.
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
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. |
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 mymoduleThis 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 ...
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
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 startwhere
myproj
signo
start
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 myprojin this way you prevent yourself from accidentally working with files NOT under CVS control.
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.
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.
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
There are a number of ways a repository can be used by remote users. Here we mention only two.
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
On the client machine do (e. g.)
export CVS_RSH=sshor put it in
~/.bashrc
On client do (perhaps in ~/.bash_profile
)
export CVSROOT=user@cvs-server:/var/cvsor 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.
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.