CVS Source Control System
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.)
- A very readable book: http://cvsbook.red-bean.com/
- Here's an excellent Microsoft Windows GUI: http://www.wincvs.org/
Quick start
Most Unix boxes will have the client commandcvs
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 prowessUpdate 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_directoryThrow 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]
What? No file locking?
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.
History
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 filenameYou 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
Checking differences
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.
Seeing old versions
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.
Avoid stepping on revisions
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.
Tagging snapshots
Occasionally you want to work with a stable version of your tree for a few days or weeks at a time. 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 prowessUse
-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.
Minimal branching
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 prowessYou 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 prowessSee 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
Creating Branches
Creating a branch is easy, but it's also easy to do wrong. The basic point to remember is that cvs uses tags (symbolic names, usually used for naming cut versions) in two distinct ways on branches, both as the name of the branch and the name of the revision on the branch. The simplest way to create a branch calledbranchname
is to
cd to a copy of the code that you want to branch, say my_prog
,
and say:
cvs tag branchname_0
cvs tag -r branchname_0 -b branchname
which first names the branch's point of attachment (branchname_0
),
and then actually creates the branch.
It's probably a good idea to update
first to check that your version really is up-to-date; I usually say
cvs -nq update
.
You are now ready to check out a copy of your branch.
If you want the original version of my_prog
to reside on
the new branch (i.e. the version that you ran the
taq
command on), now say
cvs update -r branchname
If you'd rather keep a copy of the main line as well as the new
branch, go to some directory that doesn't have the main line
checked out in it, and say
cvs co -r branchname my_prog
After either one of these last two paragraphs, any further work that
you do on the branched copy of my_prog
will automatically be
committed onto the branch. You can get very confused if you forget that
your files have been `poisoned' by the branch tag --- see the section
on sticky tags for details.
Let's ask cvs what it knows about some file on this branch; both the branch name and the attachment point name are visible:
> cvs log my_prog/rhl.c
...
symbolic names:
branchname_0: 1.15
branchname: 1.15.0.2
v1_2: 1.9
V1_0: 1.1
Note that the name branchname_0
refers to revision
1.15
, that is before the branch was made (branches on the revision
have names like 1.15.2.1
; see the section on
branch numbers for a discussion).
Add an entire directory
Add a directory and all its contents by$ 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 .
Compression
Use the -z flag before any cvs command, to get compression of the cvs protocol.$ 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.
Handy aliases
Here are the only aliases I seem to need.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
Local repository for personal files
I also use CVS for personal files in my home directory, such as publications, login configuration, utility scripts, and this web page. Converting from RCS to CVS was easy. 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.
WinCVS
Download a GUI wrapper for CVS from http://www.wincvs.org/ . 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.
Merging changes from a Branch into the Main Line
You can useco
or update
with the -j
flag, for example
to merge all changes made on branch branchname
into the
mainline, you'd find some directory which didn't have my_prog
checked out, and say:
cvs co -j branchname my_prog
(you could use update instead).
The very next thing to do is to tag the branch --- if you don't,
you'll never again be able to merge the branch into the main line without
gnashing of teeth. So do it:
cvs rtag -r branchname branchname_1 my_prog
(that reads, ``give the name branchname_1
to the top of
branch branchname
'').
If ever you want to merge from the branch again, if you say:
cvs co -j branchname my_prog
you'll get lots of merge conflicts, because cvs will try to merge the
changes made before branchname_1
for a second time. What you
want to say is:
cvs co -j branchname_1 -j branchname my_prog
As an alternative, you could cd to the main line, and say
cvs update -j branchname_1 -j branchname
After either of these commands you naturally immediately typed:
cvs rtag -r branchname branchname_2 my_prog
so as to be able to continue merging if more bugs appear in the future.
If you now said cvs log
, the output is discussed in
the section on branch numbers.
Go back to Bill Harlan's homepage or hotlist.
http://glacier.lbl.gov/DAQ/cvs_branch.html
No comments:
Post a Comment