CVS is used by open projects like Apache's WWW server, Free-BSD, Net-BSD, Open-BSD, Gnome, Netscape's Mozilla, and PostgreSQL. Unlike other systems, CVS seems to have been designed (or evolved comfortably) for distributed development by a large number of developers. CVS makes it easier for developers to work on the same code at different paces. Most source control systems emphasize merging your changes into the repository safely. CVS gives equal importance to merging recent changes in the repository safely into code that you have begun to modify.
The mechanics of CVS are open. You can see how it works, and you could fix the code if you had to (you won't). With so many users and open development, you are unlikely to encounter a blatant bug, or an obviously needed feature. CVS seems to have no more complexity than necessary to do what it does. You can get all the online support you need. CVS clients are available on all platforms, and a variety of GUI's. (A server must run on Unix.)
Most Unix boxes will have the client command cvs
already installed. You'll have to google for a Microsoft version,
which seems to move around. Put the cvs.exe
in C:\WINNT\system32
.
Set the environmental variable CVSROOT
, for example:
$ export CVSROOT=":pserver:username@pserver.denver.lgc.com:/cm/cvs/prowess"
Most everything can be done with a few commands: login, checkout (co), commit (ci), update (up), add, and remove (rm).
Login once with your password.
$ cvs login (Logging in to username@pserver.denver.lgc.com) CVS password: *****A
.cvspass
file should appear in your home directory.
A shared read-only username like build
accepts an empty password.
Get a new copy of the entire tree (your "working copy" or "sandbox"):
$ cvs checkout -P prowessThis will create a directory
prowess
with the entire tree in your current directory.
Substitute your project name for prowess
.
Do not try to write over an existing tree.
You can also checkout just a portion of the tree with a longer path:
$ cvs checkout -P prowess/port/com/lgcSpecify an alternative name for the working directory with
$ cvs checkout -P -d my_lgc prowess/port/com/lgcYou can avoid the environmental variable
CVSROOT
by using with the login and checkout commands.
After these steps, you no longer need the environmental variable:
$ cvs -d:pserver:username@pserver.denver.lgc.com:/cm/cvs/prowess login $ cvs -d:pserver:username@pserver.denver.lgc.com:/cm/cvs/prowess checkout -P prowess
Update your copy from the repository often to see recent changes:
$ cd prowess $ cvs update -dPA(The flag
-d
is necessary to see new directories,
-P
will prune obsolete directories,
and -A
will reset "sticky tags"
to the latest versions.
Do not use -A
if you are working in a branch.)
Edit files, then commit changes to the file or to whole directories:
$ cvs commit filename or $ cvs commit dir or $ cvs commit .Delete and add text files and directories with
$ cvs remove -f file.f $ cvs add file.java $ cvs commit .Add binary files with
$ cvs add -kb file.jarIf you forget, you can specify binary later with
$ cvs admin -kb *.so(This will not work on Windows, where adding a binary as text will immediately corrupt it. You'll need to change type, then delete and add again.) Create and remove entire directories with
$ cvs remove -f -R old_directory $ cvs add new_directory
Throw away your changes to a file by deleting and updating again.
$ rm filename ; cvs update filename $ rm -rf subdirectory ; cvs update subdirectoryDo not delete CVS subdirectories unless you delete its entire directory as well. This is enough to start working on code. To remember syntax, type
$ cvs -H [command]If you are worried about what your command will do, try a dry run with
$ cvs -n [command]
Many ask "how do I lock a file?" The short answer is "you can't." The short justification is "You don't need to, and you don't want to." File locking does not scale well, even with a small number of developers. Two developers should be able to work on different parts of a file at the same time. File locking does not prevent incompatibilities that involve more than one file. File boundaries are not good code boundaries. File locking is too coarse-grained and too fine-grained.
File locking "pessimistically" assumes that the work of different developers will be hard to reconcile. In fact, real conflicts are rare. File locking tries to avoid the problem at checkout time. CVS "optimistically" waits and fixes problems, if any, before code is checked back into the repository.
CVS remembers which version of a file
you began to modify. (Most systems do not.)
If someone else checks in a newer version,
then you will not be allowed to commit and over-write
previous changes. First, you must perform a cvs update -dP
to merge recent changes into your modified version.
Usually, you find those merged changes do not conflict with
your own. In the rare cases that you modified the very same
lines, CVS will warn you of a conflict, and
insert into your file a diff
of the affected lines.
(Clearcase does the same thing when synchronizing.)
You can edit this merged file and then commit.
Conflicts happen less often than broken locks with other systems.
Developers may disagree on a design or required changes. If developers avoid talking to each other, then a source control system will not resolve their problem.
CVS does allow you to "watch" files. To receive an email when a file is changed in the repository, type
$ cvs watch add -a commit filenameThis should be more than enough notification.
A really stubborn team can emulate locking of a file with
cvs watch on filename
and
cvs watch add -a edit filename
.
Checked-out copies of this file will be read-only, so others should
type cvs edit filename
before modification.
A user can still "break" the lock by simply typing
chmod +w filename
. See if you can live without
locking instead.
To see all changes committed to a repository recently, type
$ cvs history -c -a -D "3 days ago"The first letter on the line may be A for "file added," M for "file modified," or R for "removed file."
This history is difficult to read, and worse, users
can hide their modifications from the history command.
More reliably, go the the base directory and create a GNU-style ChangeLog
file with the perl script cvs2cl.pl
from
http://www.red-bean.com/cvs2cl/.
Check this ChangeLog file into the tree and update regularly.
(I recommend the --revisions
flag.)
To see the history and comments for a specific file, type
$ cvs log filenameTo see the log for the most recent revision of a file, type
$ cvs log -rHEAD filename
You can change a log message after a commit with
$ cvs admin -m 1.32:"New log message here" file or $ cvs admin -m HEAD:"New log message here" file
To compare your working version of a file against the copy you checked out from the repository, type
$ cvs diff filename(Try Unix format options such as
cvs diff -u filename
.)
To compare your copy against the most recent copy in the repository type
$ cvs diff -D now filenameTo compare against any particular version, type
$ cvs diff -r 1.7 filename $ cvs diff -r tag_2003_1_3 filename $ cvs diff -D "2 hour ago" filename $ cvs diff -D "1 day ago" filename $ cvs diff -D "2 months ago" filename $ cvs diff -D "March 14 200X" filenameTo see modifications between two specific versions, type
$ cvs diff -r 1.7 -r 1.8 filename(to see changes introduced by version 1.8).
To see any differences between your entire tree and the repository, type
$ cvs -q diff or $ cvs -q diff -D now(
-q
suppresses verbosity.)
All subdirectories will be examined. Any files you have changed
but have not committed will be listed with a Unix-like diff
of the different lines. Files that appear in your sandbox,
but not in the repository, are marked with a question mark.
Unix diff
flags are also supported.
A diff of an entire tree can be verbose. You may prefer the more concise listing of what an update would do with
$ cvs -nq update -dPThe
-n
flag ensures that no changes will be performed.
New files will be marked with a "?", and changed files will be marked
with capital letters (as with the history command).
U marks a file that needs to be updated in your
working directory. C marks a file that will require hand
editing of a conflict after updating (rare).
Count the number of lines of code changed in the past week for all directories under the current one:
$ cvs diff -b -D "1 week ago" 2>/dev/null | grep '^[<>]' | wc -lAnd for the previous week
$ cvs diff -b -D "1 week ago" -D "2 weeks ago" 2>/dev/null | grep '^[<>]' | wc -lThis command counts a new line or a deleted line as one, and a replaced line as two. Check only new lines of code with
$ cvs diff -b -D "1 week ago" 2>/dev/null | grep '^>' | wc -lYou can also specify specific dates as -D "March 13 200X" The flag
-b
ignores trivial changes to whitespace.
If you want to look at an old version 1.3 of a file without stepping on the new one, type
$ cvs update -p -r 1.3 filename > file.old or $ cvs update -p -D 2002/03/07 filename > file.oldI frequently throw away a working copy of the tree and checkout a fresh one. Before doing so, I type
cvs -q diff
in the root directory.
If I want see what will happen before an update, I type
cvs -nq update -dP
.
Always add comments when you commit. These will help
you more than anyone else. When you add a file, add a comment
saying where it came from, particularly if it was removed
elsewhere in the tree. You can change the editor for comments
by setting the environmental variable EDITOR
.
This handy command will show the last revision where every line was modified and the user who made the change:
$ cvs annotate filenameTo see what has been removed from the current directory and subdirectories type
$ cvs history -x R or $ cvs log .To see inside a deleted directory, first recover the empty directory without pruning:
cvs update -dA
.
Get the appropriate date or revision number from the log,
and recover files with cvs update ...
.
If you would like to undo changes of previous revisions in your working copy, type
$ cvs update -j 1.7 -j 1.4 file.cto remove all changes between version 1.4 and 1.7. The order of the
-j 1.7 -j 1.4
flags matters. Immediately follow this command
by a cvs diff file.c
to see if you got
the expected results.
CVS forces you to update before you commit, so rarely should you accidently remove someone else's revisions.
If you copy individual files from one sandbox to another, be very careful. Update both copies first. You might copy an old version onto a more recently updated file and lose more recent changes. Perform a diff after copying to be sure. Better, try to avoid this situation.
If you have updated to version 1.18, and you discover that changes from 1.16 to 1.17 were lost, then you can recover those changes with
$ cvs update -j 1.16 -j 1.17 file $ cvs diff ... $ cvs commitAgain the order of the
-j 1.16 -j 1.17
flags is important.
Never copy someone else's working copy. You can move your own working copy, intact, but do not make two copies. Do not mess around with CVS subdirectories.
First make sure you have an up-to-date working copy of the main trunk. Then mark every file with an informative tag that includes the purpose and date:
$ cvs -q tag -c beta-200X-06-28(The
-c
flag checks for uncommited modifications.)
Test by getting a new copy of the tree with the tagged version of every file:
$ cvs -q export -d beta-200X-06-28 -r beta-200X-06-28 prowess or $ cvs checkout -P -d beta-200X-06-28 -r beta-200X-06-28 prowess
Use -d
to create a directory with the
same name as the snapshot tag. Most of the time
you want just the read-only copy of the tree with
cvs export
.
Use cvs checkout
only if you later
need to modify the tag or want to branch.
If this turns out to be a useless snapshot, then you can delete the tag with
$ cvs rtag -d beta-200X-06-28 prowess(An
rtag
command can be used without a working copy.)
You can see which versions of a file have been tagged with
$ cvs log filenameA tag is just a symbolic name for a particular version of every file.
If you later decide that you want your snapshot to point to a different version of a particular file, then type
$ cvs checkout -P -d beta-200X-06-28 -r beta-200X-06-28 prowess $ cd beta-200X-06-28/subdirectory $ cvs tag -r 1.6 -F beta-200X-06-28 filename $ cvs update -dPThe
-r 1.6
specifies an earlier or later version
of the filename
to be associated with the tag.
Use -r HEAD
to specify the most recent one.
Update to see the revised version.
The above commands should be enough to make a stable snapshot for a short amount of time. If you need to fix bugs in the snapshot, try to fix them in the main trunk, and update the versions used by the snapshot.
You may need to modify the snapshot with changes that no longer make sense in the main trunk. To do so, you must branch the code. You should NOT intend to merge these changes back into the main trunk. (This is a sound policy, not a CVS restriction.)
To create a new branch from a snapshot, type
$ cvs rtag -b -r beta-200X-06-28 beta-200X-06-28-branch prowessUse the suffix "-branch" so you can distinguish this new tag from ordinary snapshots. You can type this command without a working directory.
You can see a history of rtag operations in the "prowess" respository with
$ cvs history -T -a -p prowess
You can switch an existing working copy to the new branch with
$ cvs update -dP -r beta-200X-06-28-branchThis sets a "sticky tag" so that files will be updated from the branch instead from the main trunk. Or simply throw away the snapshot and make a new working copy for the branch.
$ cvs checkout -P -d beta-200X-06-28-branch -r beta-200X-06-28-branch prowess
See how sticky tags are set in your working copy with
$ cvs status -v [filename]The
-v
flag also shows what other tags are available.
You can selectively update files and subdirectories in the branch with the latest versions from the main trunk:
$ cd beta-200X-06-28-branch/subdirectory $ cvs update -j HEAD [filename](
HEAD
specifies the tip of
the main trunk. The flag -j
updates the file but leaves the sticky tag.) You can commit this update to the branch by
$ cvs commit -m "updated from main trunk" [filename]Modify files directly in the branch only if the fix does not make sense in the main trunk. Otherwise, make the fix in the main trunk and update the branch from the trunk.
If someone modified a branch and the same change belongs in the main trunk, then merge into a working copy of the main trunk:
$ cvs update -j branch_name or $ cvs update -j branch_name filename
$ cd /usr/src/your_copy_of_junit3.7 $ cvs -n import -I! prowess/port/test/junit3.7 junit version3_7 ... [look for conflicts] $ cvs import -I! prowess/port/test/junit3.7 junit version3_7 or $ cd /your/path/j2re1.4.1_01 $ cvs -n import -I! prowess/sys/linux/JRE/j2re1.4.1_01 Sun v1_4_1_01 $ cvs import -I! prowess/sys/linux/JRE/j2re1.4.1_01 Sun v1_4_1_01The vendors ("junit", "Sun") and releases ("version3_7", "v1_4_1_01") are relics, and you can put anything you like. The repository path "prowess/port/test/junit3.7" specifies the subdirectory of the repository for the code. Your directory "/usr/src/your_copy_of_junit3.7" not be a working copy of a respository.
Before importing an entire directory, look for and replace any symbolic links:
$ find . -type l -printAlso make sure all permissions are readable and writable:
$ chmod -R a+rw .
$ cvs -z 3 -q update -dPA $ cvs -z 3 checkout -P prowess $ cvs -z 3 -q diff
-z 9
does the most compression and uses the least bandwidth,
but uses the most CPU. Values of 3 to 5 are usually found to be optimum.
alias cvco="cvs -z3 co -P" alias cvex="cvs -z3 export -D now" alias cvd="cvs -z3 -q diff -b" alias cvup="cvs -z3 -q update -dP" alias cvu="cvs -z3 -nq update -P" # see what changes are pending
Create a directory in your home directory for the repository,
such as $HOME/mycvsroot
. My login script
sets CVSROOT
to this directory as my default.
When I access others' repositories, I check out with the
-d
flag. Initialize your personal repository
with
$ export CVSROOT=$HOME/mycvsroot $ cvs initFrom my home machine, which has a different mounted home directory, I can access the office repository by using ssh, and setting
$ export CVS_RSH=/usr/bin/ssh $ export CVSROOT=:ext:user@hostname:/home/user/mycvsrootHere you must be explicit with the remote path.
Here is how I created a repository called docs
from a directory docs
that was previously
under RCS control. First identify and remove any symbolic links:
$ find $HOME/docs -type l -printReplace links by copies, or write a script that can regenerate them. Next import the directory into CVS
$ export CVSROOT=$HOME/mycvsroot $ cd $HOME/docs $ cvs -n import -m "Converted from RCS" -I RCS docs user v0 $ cvs import -m "Converted from RCS" -I RCS docs user v0Test once with
-n
to see that it will work.
The last three arguments arguments are required (repository, vendor,
and release), even though the last two are pretty useless.
The -I RCS
flag avoids checking in RCS subdirectories.
You can omit that if you have no directories to ignore.
To retain previous revisions for files that used RCS,
I then run a simple script called rcs2cvs.sh.
This script copies any RCS/*,v
files into the equivalent
repository subdirectory. If you have no files using RCS, then
skip this step entirely.
$ export CVSROOT=$HOME/mycvsroot $ cd $HOME/docs $ rcs2cvs.shIf you make a mistake, do not worry. You have not altered your original directory. Simply delete the repository subdirectory
$HOME/mycvsroot/docs
and start over.
Test checking out the new repository and view revisions as a sanity check.
$ export CVSROOT=$HOME/mycvsroot $ cd $HOME $ cvs checkout -d docs_test docs $ cd docs_test $ cvs log | less $ treeFinally, you can backup your original directory and replace it with the working copy.
Your big import may have included some
files that you did not want to keep, such
as large binary files. You don't want these
files taking up space in your repository
after you remove them from your working
directory. To remove a file forever, first
remove it from your working copy with
cvs rm -f file
. The repository will
move the version file into a a subdirectory
called Attic
. You can now
remove the Attic/file,v
file,
and the repository will not know that it ever
existed. Do not attempt to remove a
file,v
before it has moved
to the Attic.
You can also
remove entire subdirectories this way.
Hacking directly on repository files is
almost always a bad idea, but this exception
seems justified. If the file is not large,
leave it in the Attic.
You might
change your mind and want to recover it
later.
Like VSS and ClearCase, this has clever features that are harder to contrive from the command line. You can easily select lists of files and directories to commit as a group. Modified files have different icons. Versions are very comprehensible.
When WinCVS first starts up give the
wizard your CVSROOT
, specify
password authentication, and point to your
home directory with .cvspass. This much
configuration will allow you to connect.
Go to the Change Location icon (with binoculars) to select your local working directory, which may already exist.
You should be able to figure out the rest by just playing with it.
You can specify an different default file
viewer Go to Admin -> Preferences ->
WinCvs tab -> Default viewer used to open
files:
and enter
C:\emacs\bin\runemacs.exe
Or specify an external graphical diff
program (or file editor) at Admin ->
Preferences -> WinCvs tab -> External diff
program:
Enter C:\Program
Files\Microsoft Visual Studio\Common
Tools\windiff
for example. The next
time you actually do a diff, check the box on
the Diff settings
dialog that
says Use the external diff.