Answers colored red indicate stubs that I plan to flesh-out when time permits and feedback requiresQuestions I need to add & answer:
- What does "sticky" mean?
- What is cdir?
- How does CVS compare to Razor?
Moreover, if you haven't yet picked a CM tool, then you should probably take
a look at CVS if:
The goals of CM, briefly, are these:
However, the recent explosion of Linux and "open-source" development
has made the RCS-derivative
CVS undisputedly the
most widely-used, though perhaps not best-loved,
Software CM (SCM) tool in the world.
Some good general-purpose CM links:
What is RCS?
RCS, or Revision Control System, is the suite of CM tools
based on /bin/ci (Check In), /bin/co (Check Out), rlog, ident, etc.
Basically, RCS allows developers to save a history of changes made
to a file. This allows users to resurrect previous versions of a
file, or to generate a list of exactly what changes were applied
between specific versions, or even to merge together two sets of
changes made to different "branches" of development.
RCS is easy to use, but (like 'make') it gets a little complicated
when used to manage source code spanning more than one directory,
and it completely falls apart if the code is being developed/tested
on more than one computer. It is the lack of network support which
really undermined RCS's post-internet utility. Collaboration is now
king, and that was the fundamental innovation CVS added to RCS.
Therefore, for historical interest, here are some good RCS links:
What is CVS?
Basically, CVS is a front-end to the ancient RCS toolset which
ships with virtually every Unix distribution in the world. To RCS,
CVS adds the ability for multiple developers to collaborate over
a network. Each user is able to checkout their own "working" copy
of the code tree, and modify, compile, and test the programs at-will.
Each user may checkout their tree onto their own computer, or simply
into their own directories on a single server. The point is that
each user has a full working copy of the complete source code, so their
short-term changes don't overwrite or obstruct one another.
Then, when the developer feels they have finished whatever changes
they were making, they can "check in" their version of the file,
which applies their changes back to the master code tree. Anytime
a developer checks out a new copy thereafter, or "updates" their
current working tree, the new version will be automatically (but
safely) distributed to everyone who should receive it.
All this is made possible through the use of the "repository."
The "pure, original" version of the code, along with a complete history
of every change ever made to every file (along with metadata such
as who made the change, when they made it, and ideally why the
change was applied) is stored in a single repository on a commonly
accessible networked computer.
This repository may be thought of as a "black box", a database that
"simply is", but for the record it's nothing more than a nested set
of directories and files which exactly matches the source code tree,
except that each file is stored as an RCS-formatted series of 'diff's.
It's noteworthy that the repository is "just a bunch of files"
because that greatly simplifies the task of backing up the repository
(just tar it up), verifying access privileges, etc.
The remainder of this document describes how to make use of these
features, and how to handle certain predictable points of confusion.
Find additional CVS documentation, tutorials, and references
at the end of this document.
1 remember, this .rhosts file should be in
your home directory on the server (ie, cvs-oa)
2 remember, this should be the domain name of
your client (ie, desktop) computer
3 remember, this should be your username on
your client (ie, desktop) computer
How do I check code out of the repository?
What you or
I might call a "project" or an "application" -- ie, a complete
collection of code and files that are interrelated and typically
distributed together -- CVS calls a "module". For instance,
"PRISM" is one module in my current CVS repository.
To checkout a module, then, you use the checkout
command, as so:
(See here for detailed options)
(See here for the Fogel docs)
How do I see if and where my local copy of the code base differs from the
current repository version (but without changing anything)?
cvs -n update will recurse down through
the current directory, comparing each file against what is in the
repository. If CVS detects that you have made your own modifications
to a file, it will print that filename preceeded by an M (modified)
code.
It's kind of like find ., except
that a code will preceed each file to let you know how it compares
to the version in the repository. Click
here
to see the full list of codes which may explain what is learned about
each file.
To build on that, a fast and simple way to find out which files you've modified,
but not-yet commited back to the repository, is to cd
to the root of your working tree ('cd $MAKE_HOME' or whatever), and
type:
Generally, it's quite safe to cvs update your code tree
without the "-n" option;
by itself, CVS shouldn't do anything that
would overwrite or destroy any changes you've made in your local
copy.
(See here for the Fogel docs)
How do I update my local working copy to the latest version of all files?
(See here for detailed options)
(See here for the Fogel docs)
How do I update code which is already in the repository?
If you get an error message, skip down to the
next section.
If there are no errors
Sample log messages are usually things like:
One is by just typing "cvs commit" by itself. This will recursively
commit every file in the current directory, and all subdirectories,
which have been modified since the last checkout/update. If you're
in a top-level directory, this can commit a LOT of files, so use with
care (although, as with most CVS operations, it is very difficult to
accidentally do any real damage, since undesired commits can always be
subsequently backed out or undone.)
The other is by specifying multiple filenames on the command line, such
as:
A command-line trick to skip the vi session is to specify the log message
in the commit message, as with:
CVS will check to make sure that the version of the file
you had when you started editing (ie, when you last
did a "cvs checkout" or "cvs update") is still the most
recent revision in the repository.
That is, if tracker.c was
at revision 1.3 when you checked out your sandbox, and
you've been making changes to revision 1.3, then CVS will
check to see that nobody else has subsequently committed
revision 1.4 (or 1.5 or 1.6) of tracker.c.
If the repository version of tracker.c is indeed
still at 1.3, then CVS will commit your changes
to the repository. tracker.c will be updated
to revision 1.4.
However, if someone has slipped in between your last checkout/update
and updated tracker.c "behind your back", then CVS
will report an error. You're not allowed to commit changes
made to tracker.c 1.3, if someone else has already updated
tracker.c to 1.4, because there can't be "two version 1.4's".
Instead, CVS will report an error, and tell you that you
need to update your copy of tracker.c.
You do that by typing "cvs update tracker.c". This will not
lose the changes you have just laboriously (and brilliantly) applied
to your version of tracker.c! This will merge your
changes to tracker.c 1.3 with "Bob's" changes to tracker.c 1.3.
The result will not be "your" version of tracker.c, nor Bob's
version, but the combination of both.
Basically, the result will be the same as if you had not made your
changes to tracker.c 1.3, but to tracker.c 1.4. Literally, CVS
will use "diff" to determine what changes you made to version 1.3;
then it will grab its own copy of 1.4; then it will apply those
same changes (your changes) to version 1.4.
(Can this magical merge process fail? Of course it can. See
the section on conflicts if you get
an error during a "cvs update".)
So now you do the "cvs commit" again...and this time it succeeds.
It succeeds because this time, when CVS looks at it's metadata for
your working copy of tracker.c, it sees that your changes were
applied to revision 1.4, which is indeed the current repository version.
Thus the merged version (with both your and Bob's changes) is commited
back to the repository as revision 1.5.
How do I add new code into the repository?
If you have added a new directory, beneath which are additional files
and/or directories, then you must add them in hierarchical order
(ie, the same order in which nested subdirectories must be created
with "mkdir tree", "mkdir tree/bough", "mkdir tree/bough/twig", etc):
Please note that not all files need to be in the repository. There is
typically no point in adding files with any of these extensions, because
it is almost guaranteed that they will be of no use to anyone:
I did a 'cvs update', but I'm still missing some new code! I know it was added
and commited to the repository! Where is it???
To "update my current tree, including checking out any new additions" (what you
probably mean), you need to use:
How do I compare my version of a file with earlier releases?
Note that this command can be a little tricky, and some people find
it's default behavior counterintuitive, so note the
next section if you get confused.
By default, cvs diff filename will show how filename
differs from it's "sticky version" (the version last extracted from
the repository via "cvs checkout", "cvs update", or "cvs commit").
That is, in a nutshell, it will show you what changes you have made
to that file, since you last synced it from the repository.
In most cases, that's probably what you want.
However, CVS can provide diffs between any arbitrary versions of any file
under version control.
Scenario One
You ask the user what the dates were on their previous, working CD
and their new, crashing one. He says the previous distribution was
labeled "2002-06-22" and the latest was made "2002-08-15".
You check the history of tracker.c using "cvs log tracker.c":
We open up a current version of tracker.c in our text editor,
go to that line, and...it's different! The NULL is gone, and
instead the address is being taken of a global variable. That
was the same change you were going to make!
You wonder when that happened, so you check with CVS:
So then you check the log output again, and now you actually read
the log message for revision 1.7, which already mentions fixing
that bug. It was a known problem that has already been fixed,
and all you need to do is send a fresh distribution to the user.
Issue solved!
'cvs update' says my file is out of date, but
'cvs diff' won't show me what's been changed! What gives?
Let's say that you checked out a current code tree last week, which
happened to include rev 1.13 of myfile.c. Over the next few days,
Bob and Jane made various tweaks to myfile.c, so the repository version
is now at rev 1.15.
You do a 'cvs -n update' and see that "U myfile.c", so you know it's
been updated. However, when you do "cvs diff myfile.c", it returns...
nothing. Hence the confusion.
This is because CVS remembers, via it's "sticky tags" stored in ./CVS/Entries,
that the version of myfile.c which you checked out -- either explicitly
or implicitly -- was version 1.13. When you do a 'cvs diff', it compares your
working copy against version 1.13, and shows that, sure enough, your copy
of revision 1.13 exactly matches the repository's copy of revision 1.13.
What you probably actually want -- and what might make for a more reasonable
default behavior of 'cvs diff' -- is to diff your version against the
most recently committed revision of myfile.c.
The "-r HEAD" argument
to 'cvs diff' will do that for you, because it explicitly says "diff my
working copy against the head of my current branch." "HEAD" is an
"implicit tag", automatically maintained by CVS, which points to the
latest version of each file in each branch. No matter which branch you
specified when you checked out your working tree (including the "trunk"
if you didn't specify a branch), "HEAD" is available as an automatic
tag pointing to the latest revisions of every file.
Note, therefore, that even "-r HEAD" may not diff your file against
the chronologically most recent version of your file, because
someone else might have committed a newer update to "myfile.c" in a
different branch.
If you really understand what you are doing, then you might choose
to add a line like "diff -rHEAD" to your .cvsrc file, so that this
will become the default behavior. Beware, though, that that might
have unanticipated consequences. For instance, if create such a
default, and then forget, you might inadvertently type
"cvs diff -rHEAD myfile.c" at the cmd-line.
What will this do?
Well, since 'cvs diff' (like many cvs commands) will accept two
-r arguments (to show the difference between two arbitrary revisions,
ignoring your working copy), then what you will actually request is
"cvs diff -rHEAD -rHEAD myfile.c" (ie, the difference between a thing
and itself), which is always guaranteed to be...nothing. Therefore,
this probably isn't a good choice for automating via .cshrc.
How do I checkout, or "rollback" to, an older release of a file?
More than that, you want to get rid of
revisions 1.11 and 1.12 altogether; they represent a demented
design that you want to just erase from history.
Well, you can't. Erase them, that is. The entire point of a
revision-control system is to preserve every revision
of a file, even the ones that are later proved to be stupid
and useless.
However, what you can do is bring the older version "forward"
and place a fresh copy of it on the head of the file's revision tree.
That is, you can make a new revision of foo.c, say 1.13, and make
that exactly equal to the older 1.10. Thenceforth, all edits
and updates will be made to 1.13 (to 1.14, 1.15, etc), which is
effectively what you want.
You can do this with the following procedure:
Working revision: 1.13 Wed May 7 15:42:39 2003
[snip]
Checking in acq.c;
The key step is the "cvs update -j now -j old file" command. Basically, this is a "cvs update"
command, and like any other "cvs update" command, it generates a "diff" from the repository and
applies that diff to your working copy. In this case, it's generating a diff between two
repository revisions of the same file. The important thing is the order of the revisions
specified in the "-j" options.
Normally you think of a diff as moving forward in time; to see recent modifications of a file,
you generate "the diff between the old copy and my new copy". In this case, you want to roll
the file backwards in time, so you ask for the diff "between the new copy and the old
copy"; in other words, "generate a patchfile, which, if applied to a RECENT revision of the file,
will delete a bunch of additions and leave an exact image of the OLD revision."
So that's what the "cvs update -j recent -j old file" command does. It generates a patchfile
which will DELETE all of the changes made between the "old" and "recent" revisions, and then
applies those patches to your working copy. This results in a modification to your working
copy, so if you follow-up with a "cvs commit", your modified version gets commited back to
the repository as the latest revision of that file's history.
You have just "rolled back"
the file's contents to those of a historical revision's, without actually destroying
the validity of the repository's history log.
Reference:
How do I move or rename files or directories that have already been
added and committed to the repository?
I only want to checkout a partial
subset of the tree, or maybe just a single file. How do I do
that?
To reduce the number of spurious directories created, see the
next tip.
I only want to checkout a partial
subset of the tree, or maybe just a single file...and I
want it checked out here. I don't want the whole
MODULE/long/path/to/one/file mess!
The third gives you only
the desired file (no companion ./CVS directory to store repository
metadata), and (AFAIK) only works for a single file.
How do I remove a file from the repository that was added by mistake?
The purpose of CVS is to maintain a historical trail of how the code looked
over time. If there was a file in the code tree at 8:34am on Tuesday, and
on Friday somebody says "quick, I need an exact copy of our code tree from
8:34am last Tuesday!", then by-God CVS is going to try to present the exact
state of the repository at that point in time.
It couldn't very well do that if people went and retroactively "removed" files
from the repository history, could it?
Therefore, the notion of "removing" files is a very sensitive one to any
revision-control system. The CVS approach is to not actually "remove" them,
but "move them to the Attic." The Attic is a "graveyard" within the repository
where old and useless files can be put out to pasture.
If you do a "checkout" or an "update", you will not see these files. They
will be "gone", "removed" as far as users can tell. But if, for some reason,
you decide in five years time that "Oh, if only I still had that old
snippet of obsolete SGI code we wrote for that one customer before we transitioned
to OpenGl"...you'll be able to extract the old version. (Exactly how is a subject
for a FAQ entry which will be created when someone has a need for that :-)
Having said that, note furthermore that there is no way to remove the file
from the repository without also removing (deleting) it from your working copy.
I don't know what the logic was behind that design decision, but there you
are. Therefore, both of the following procedures will end up deleting your
local copy of the file as well. (If it was important, you can of course
get it back, simply by checking it out of the Attic. Or you may simply decide
to back it up somewhere before deleting it. Whatever.)
The simplest way is:
How do I remove a directory from the repository when it no longer pertains
to the project?
Seriously. It's
in the manual.
There are reasons for this, which AFAIK have to
do with the fact that old files, which once existed in that directory, are
now stored in the "Attic"...and the current CVS implementation stores
a mini-Attic inside each directory of the repository. Meaning that you
can't "remove" the directory without also removing the Attic version of
that directory's old contents...which CVS will not allow you to do.
"What? I'm going to have these stupidly-named ghost directories floating
around for the life of my project?"
Well, not necessarily. You have at least three options:
# I like context diffs
# checkout any new (but non-empty) directories when doing updates
# don't checkout empty directories
Some of my scripts are executable when I check them out, others aren't!
How do I control that?
Basically, you just need to have an admin physically chmod the files +x
or -x in the repository itself. Otherwise, it will use whatever the perms
were when the file was first added (commited?).
Every time you modify (and commit) a particular file, the
revision number is incremented. These per-file revision
numbers have absolutely nothing to do with the official
"version number" of the overall software package.
That is, what we think of as "release 2.1" might actually
be made up of these file revisions:
In order to make it easier for humans (users) to go back
years from now and examine exactly what went into "release 2.1",
they create a label, called a tag, which represents
the set of those particular revisions of those files. In this case,
we might make a tag called "Release-2_1" to indicate the collection
of specific file revisions which were included in that release.
Tags are just text strings, but they have some funny restrictions
on what characters are allowed (basically A-Za-z0-9_ and hyphen;
the decimal point is a noticable ommision). Therefore, tags
usually look like "PRISM-2_1", "SNAPSHOT-Owego-2001_09_11", etc.
The usual practice is to use an underscore where you would
normally want a period, and a hyphen where you would otherwise
use an underscore :-)
While tags are one of the most useful features of CVS, they
are only useful if you use them! If you cut a release
of your code, or send a distribution to somebody, you really should
make yourself tag the release so that you can later re-generate
exactly what you sent them should a question arise.
Note that there are two "implicit" tags, which are maintained
by the system and are always available: BASE and HEAD. Those
refer to the very first (oldest) and most-recent (newest) revisions of each
file in the current branch, respectively. (Think of them as
analogs to list->tail and list->head pointers in a
queue structure.)
For more info on tags, check
Fogel's entry (which has an excellent ASCII drawing
of a wavefront :-)
How do I see what tags are defined?
How do I create a tag?
For reference, see cvsbook.html#tag.
How do I checkout a tagged release?
What is a branch?
How do I create a branch?
How do I merge a branch back into the main trunk?
It's easy if you merge all of the branch changes into
the trunk, all at once, one time. For that trivial case,
all you have to do is this:
For reference, definitely read the Fogel chapter. You also might like this newsgroup post.
The scope of the problem is proportional to the granularity with
which code can be addressed (directories, files, or even sub-files;
IBM's VisualAge can actually lock individual methods (functions)
within a single file). However, regardless of granularity (CVS and
most other environments are file-based), you eventually come up
with a point where two developers might try to modify the same
unit of code.
Many tools avoid the problem entirely by using
exclusive file-level locking. That is, if user Jane has already checked
out file "tracker.c" for editing, then user Bob is unable to do the same.
Bob may be able to checkout a read-only (non-editable) copy, but will
be unable to modify his version (or at least forbidden from commiting
his changes back to the repository). This is a reasonable approach,
and is the preferred solution for many developers.
However, it is not how CVS has elected to operate,
which is the subject of a long-lasting and heated debate on the internet.
If you are willing to brave the vitriol and invective that frequently
infect these discussions, you can quickly find them on
groups.google.com.
Some more-rational descriptions of the pros and cons between
locking and non-locking behaviour -- AKA "reserved vs unreserved",
AKA "pessimistic vs optimistic" checkouts, as the debate
is alternately described -- can be found here:
You will frequently find the CVS strategy described as "copy-edit-merge"
or "copy-modify-merge" in various documentation (in contrast,
"locking" strategies are likewise known as "lock-edit-unlock").
Here's a sample scenario to demonstrate how it works:
----------------------------
Wait, that can't possibly resolve all conflicts!
What are Bob's options at this point?
Bob has a number of options here, any of which will work, but all of which
will require the manual exercise of some plain 'ole human judgement.
(SuperCVS, which will totally obviate the need for human judgement and
is hoped to replace programmers altogether, won't be available
until next spring.)
The important
point to remember is not that CVS caused the conflict described
above -- it did not -- but that the proper execution of SCM procedures,
assisted by the CVS tool, helped the developers discover the
discrepancy in their algorithm assumptions, ideally well-before
inadvertantly shipping code with the wrong ratio to the wrong customer,
possibly skewing tracks, causing missiles to slew wildly out of kilter,
destroy the wrong targets, lead to international conflagration,
and ultimately bring about the end of civilization as we know it.
All that, and CVS is still free! Vat a bargain!
(See here for the Fogel docs)
$ sudo rpm -i /mnt/cdrom/RedHat/RPMS/cvs-1.11.1p1-3.i386.rpm
(actually, I used rpmfind.net
to grab a slightly-newer RPM for 1.11.2)
How do you create a repository?
How do you create a module?
How do you backup a repository?
(Note that there is nothing funky or CVS-specific about these commands; they're
just plain-jane Unix 'tar' commands to backup a directory. In this case,
the CVS repository is stored in /usr/local/cvsroot, so that's the directory
which is being backed up. The example was written on August 8, 2002, so that
date was included as a demonstration of how backups might be timestamped
for later reference. --Josh)
How do you restore a repository?
How do you control access to a repository?
Right now, anyone with
a login to cvs-oa has read-access to /usr/local/cvsroot, and anyone
in the 'users' group has write-access as well. CVS authentication
is currently set to use /etc/passwd, so in order to add someone
to "CVS" you merely have to create an account for them on cvs-oa
and add them to the "users" group.
To eliminate potential read-write problems, the entire
repository was set to be owned by the group "users",
regardless of the default group membership of individual
users. This was accomplished by:
For instance, if you forgot that "-n" was a global option, you might
accidentally try to check the status of your code with "cvs update -n".
Well, if that's all you did, CVS would exit with a polite error message
to the effect that there was no such "-n" option to "update". That
might leave you feeling a bit puzzled ("I would have sworn
that was the command I'd used...?"), but no harm done.
However,
if combined with the filehandle redirection described above, then
you'd get...nothing at all. The exact same output as if your code
was exactly in sync with the repository, a mistaken conclusion
which could lead to all kinds of pain.
It gets worse. Many of the same letters are used for global
and command options. For instance, although "update" doesn't
have an "-n" option, it does have -l, -r, and -f options,
whose meanings vary considerably from the global versions:
As you can see, things could get pretty weird -- and possibly
dangerous -- if you used the "right" option in the "wrong" place.
Gotcha: ../.. and explicit paths
Then I tried it again with
Finally it worked with
Can I do all this stuff from a GUI? Please???
Oh, if you must:
How do I customize CVS to act like I want?
The primary function of this file is to store default arguments.
Oddly enough, it's documented in 'man cvs', but not on cvshome.org.
Go figure.
The format is pretty simple: each line starts with a cvs command name
("add", "commit", etc) followed by the default arguments you'd like
to use with each command. Here's an example:
# I like context diffs
# checkout any new directories when doing updates
1.0 Introduction
Why should I care about CM or CVS?
You should care about CM if you fit any of these descriptions:
If any of the preceeding applied to you, then you need to learn about
CM, a fascinating and diverse niche
of softare tools which are specifically designed to meet those and
other needs.
That's why.
2.0 Concepts and History
What is CM?
CM stands for Configuration Management.
In abstract, Configuration Management refers to a set of
procedures used to track document changes. More specifically, it
typically describes a software program or programs used to
maintain a library of computer files, especially source code.
Some common examples of commercial CM packages include Microsoft
SourceSafe,
PVCS,
Razor, Rational
ClearCase,
etc. In the Unix freeware arena, CM historically meant a choice between
RCS and
SCCS.
In addition, countless teams of developers
have built proprietary in-house CM systems using the 'make'
utility, but they tend to be overly complex and difficult to maintain.
...and why should I care? Well, if you're reading this document,
then you should have a minimal awareness of RCS because CVS is
nothing more than a glorified wrapper around RCS. It is occasionally
significant that CVS still stores repository files in basically
the same format as pioneered by RCS, and to know that RCS-based
tools like "ident" work perfectly well on CVS-managed applications.
Also, occasional oddities
in the CVS implementation, which may seem strange or unintuitive
in isolation, may make more sense in consideration of its RCS
origins and legacy compatibility requirements.
CVS is a cross-platform, network-centric Configuration Management
(CM) tool, based on RCS,
which is used to automate the management of large source code trees being
simultaneously modified by multiple developers.
3.0 Basic Usage
How do I setup my computer to access an existing CVS repository?
C:\> ping cvs-oa
If you can't ping the CVS server, you're probably
not on the unclassified Lockheed network, and
shouldn't be attempting this procedure.
Configure rhosts-based authentication between your client and the CVS
repository server:
orlsr0072962 ziegm3
(Make sure the file is not writeable
by anyone other than yourself (ie,
chmod 644 ~/.rhosts), or rlogind
will refuse to trust it. --Josh)
Verify that you can rsh from your desktop to the server,
like so:
$ uname -a
$ rsh mzieg@cvs-oa uname -a
If the rsh command above fails, then confirm that
you have followed the .rhosts instructions correctly.
(Also, if you're trying to rsh from Cygwin, then make
sure that rsh/rlogin/etc are installed via the "inetutils"
package.)
From any Unix-like OS on the unclass network (Linux, SGI, Solaris,
or Cygwin), export the environment variable CVSROOT
as follows:
This would be a good thing to do in your .bash_profile, .login, etc.
Again, username in this case would be your username
on the host which contains the repository (ie, your login on cvs-oa).
export CVSROOT=:ext:username@cvs-oa:/usr/local/cvsroot
setenv CVSROOT :ext:username@cvs-oa:/usr/local/cvsroot
cvs checkout project_name
The way CVS works, you generally don't check out just "portions"
of a project; you check out the whole schebang.
cvs checkout PRISM
This will create a PRISM folder under your current
directory and extract the entire nested PRISM tree into it
(starting with BasePrograms, BaseData, and Docs at the top,
then all of their contents).
see cdir.
cvs -n update 2>&1 | grep ^M
The key argument here is the -n, which tells CVS to
do "nothing" (or "no action", or whatever). You'll note the location
of the option, between the program name ("cvs") and the command
verb ("update"). This is significant and initially confusing,
so if you haven't already read it,
take a look at some potential gotcha's
in the CVS command-line syntax.
As above, but just use cvs update.
Assuming that you have checked out your own working copy ("sandbox")
of a project, then you can commit your changes back to the repository
using:
cvs commit <filename>
(See here for detailed options)
Assuming there are no errors, a vi session will pop up (or whatever
program is specified in your EDITOR environment variable), looking
basically like this:
CVS: ----------------------------------------------------------------------
CVS: Enter Log. Lines beginning with `CVS:' are removed automatically
CVS:
CVS: Added Files:
CVS: flyer.cpp
CVS: ----------------------------------------------------------------------
This is where you specify a log comment for your commit.
You can type whatever you want into this window. As the
message says, any lines starting with "CVS:" will be ignored,
so the window is essentially blank.
Note that you can commit many files at once. There are two
main ways to do that.
For either of these methods, whatever log message you provide will be
applied to all files.
cvs commit -m "added --verbose cmd-line option" tracker.c loader.c
If there is an error
This is largely a shorter form of the discussion on
CVS's copy-edit-merge paradigm found in the section on
Locking vs Merging.
Here's what will happen:
Assuming that you are within an existing project ("module", "sandbox")
which has been checked out of a CVS repository, you can add new files
and directories via:
cvs add <filename>
(See here for detailed options)
Note that adding a file to the repository does not automatically
commit that file (although intuitively it probably should).
Therefore, you still have to commit the "1.1" version of your newly added
file, in the normal way.
(See here for the Fogel docs)
"backup" versions of such files should already be
in the repository under version control, ie 1.3, 1.4, 1.5, 1.6...)
(if these are generated by application runs)
By itself, "cvs update" only updates directories you've already checked out.
If the new files also happen to be inside new directories, then "cvs update"
by itself will not (by default) automatically "checkout" those new directories.
cvs update -d
(note that this might be a handy option to add to your ~/.cvsrc)
The basic command is:
cvs diff <filename>
(See here for detailed options)
A user (or another developer) has
complained that, since they upgraded to the latest distribution, they are
starting to get frequent crashes under SGI. They say the program worked
fine off their old CD.
cvs-oa [~/PRISM/BasePrograms] mzieg $ cvs log tracker.c
RCS file: /usr/local/cvsroot/PRISM/BasePrograms/tracker.c,v
Working file: tracker.c
head: 1.7
branch:
locks: strict
access list:
symbolic names:
keyword substitution: kv
total revisions: 7; selected revisions: 7
description:
----------------------------
revision 1.7
date: 2002/09/04 14:47:40; author: josh; state: Exp; lines: +1 -1
fixed GUI display under SGI
----------------------------
revision 1.6
date: 2002/08/29 18:13:55; author: josh; state: Exp; lines: +18 -3
added debug option
----------------------------
revision 1.5
date: 2002/06/25 19:02:46; author: mzieg; state: Exp; lines: +7 -4
*** empty log message ***
----------------------------
revision 1.4
date: 2002/06/25 19:00:34; author: mzieg; state: Exp; lines: +11 -4
compiles under Cygwin!!!
----------------------------
revision 1.3
date: 2002/06/20 19:42:07; author: mzieg; state: Exp; lines: +22 -11
more fixes
----------------------------
revision 1.2
date: 2002/06/19 16:54:16; author: mzieg; state: Exp; lines: +9 -6
testing pointer logic
----------------------------
revision 1.1
date: 2002/06/19 16:53:20; author: mzieg; state: Exp;
Initial CVS release
=====================================================================
What does this tell us?
So we wonder, just what was done between those versions? The log
messages weren't exactly descriptive, so we check the source:
cvs diff -r1.3 -r1.5 tracker.c
Okay, that showed us exactly what changes were made to the source
between those versions, and sure enough there was a change made
in a glutCreateWindow call, referencing an uncasted NULL, that looks like it
could provoke a crash.
(sorry, I'm not going to make up sample output :-)
cvs diff -r1.5 -r1.6 tracker.c
Nope, that was in another part of the program...
cvs diff -r1.6 -r1.7 tracker.c
Ahah! There it is. Somebody had already made that fix.
Use this:
cvs diff -rHEAD [filename...]
Explanation: by default, 'cvs diff' will show you how your current
tree (or just particular file(s)) differ from their base version.
If you're trying to simply throw away any changes you made to your local copy,
which have not yet been committed back to the repository,
and make your working copy exactly equal to the latest revision in the repository,
then you can use either of these methods:
cvs update -C filename
or
rm filename && cvs update
Otherwise, perhaps you want to rollback to a previous revision. For
instance, perhaps foo.c is currently at 1.12, but you want to
go back to revision 1.10.
Okay, what happened here?
cvs status filename
Example:
$ cvs status acq.c
In this case, we see that acq.c is at revision 1.13,
and that our working copy is up-to-date with the repository.
===================================================================
File: acq.c Status: Up-to-date
Repository revision: 1.13 /usr/local/cvsroot/PRISM/src/Acquire/acq.c,v
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)
cvs log filename
Example:
$ cvs log acq.c
In this example, let's say we want to "rollback" to revision 1.11.
RCS file: /usr/local/cvsroot/PRISM/src/Acquire/acq.c,v
Working file: acq.c
head: 1.13
branch:
locks: strict
access list:
keyword substitution: kv
total revisions: 13; selected revisions: 13
description:
----------------------------
revision 1.13
date: 2003/05/07 15:42:39; author: mzieg; state: Exp; lines: +10 -21
rolled back zieg's malloc checks, added Tom's assert checks
----------------------------
revision 1.12
date: 2003/05/07 15:28:05; author: mzieg; state: Exp; lines: +22 -14
zieg's malloc checks
----------------------------
revision 1.11
date: 2003/04/30 16:06:05; author: mzieg; state: Exp; lines: +2 -0
added rcs_id
cvs update -j current_rev -j rollback_rev filename
Example:
$ cvs update -j 1.13 -j 1.11 acq.c
cvs commit filename
Example:
$ cvs commit -m "threw out bad changes; rolled back to working rev (1.11)" acq.c
/usr/local/cvsroot/PRISM/src/Acquire/acq.c,v <-- acq.c
new revision: 1.14; previous revision: 1.13
done
Short-form: http://www.cvshome.org/docs/manual/cvs_7.html#SEC70
To check out an entire module, you use this:
cvs checkout MODULE
In the same way, to checkout part of a module, you would use this:
cvs checkout MODULE/just/this/part
Note that while this will only checkout the file(s) under
just/this/part, it will place them into a fully-nested
directory tree. Ie, if you are in /home/mzieg/work when
you execute the command, your files will end up in
/home/mzieg/work/MODULE/just/this/part.
Three methods are presented. The first two give you a genuine
"working sub-tree", to which you can update, commit, diff, etc
in normal CVS fashion.
example:
example:
example:
First of all, there is no way to "remove" any files from the CVS repository
short of having an administrator physically pluck them out of the repository
filesystem. This is for a reason.
cvs remove -f <filename>
If you don't include the -f ("delete the file before removing it"), then you'll
need to make it a 3-step process:
cvs commit <filename>
rm <filename>
cvs remove <filename>
cvs commit <filename>
Well, now, that's a tricky one. The thing is...you can't.
# ~/.cvsrc
diff -c
update -d -P
checkout -P
Note that this will break your ability to recreate historical
snapshots, but at least you'll still have all the files that
were ever in the code tree.
See the Fogel explanation.
4.0 Tags and Branches
What is a tag?
A tag is an English-like, human-readable label that refers
to a what snapshot of the project looked like at a
historical point in time. (Razor calls these "Threads.")
gui.c 1.54 sensor.c 1.24 sim.c 1.11 socket.c 1.5 tracker.c 1.79
See cvsFileTags.sh.
This is the basic command:
cvs tag new_tag_name
Example:
cvs tag release-2003_01_21-Name_of_Receiving_User
Note that you probably want to be in the top-level directory
when you do that (ie, ./PRISM, not ./PRISM/BasePrograms or somesuch).
This is the basic command:
cvs checkout -rtag_name
Example:
cvs checkout -r release-2002_09_17-Chad_McCrae
For reference, see cvsbook.html#checkout.
I can't possibly improve on Ken Fogel's excellent
narrative tutorial. Read it..
It's actually implemented as a special sort of tag (a "branch tag"). This
is wonderful, because it gives us a nice English-language label to use when
referencing the branch.
cvs tag -b branch_label
Example:
cvs tag -b Branch-2002_11_16-EOTDS_Testing
That's assuming, of course, that you're already in the top-level directory
of the code that you want to use as the branch "base".
A more complete example might be:
# STEP 1: grab a snapshot of the code circa Nov 16, 2002
$ cd ~
~ $ mkdir test
~ $ cd test
~/test $ cvs checkout -D 2002/11/16 PRISM
# STEP 2: create a branch (with associated English tag) starting with this snapshot
~/test $ cd PRISM
~/test/PRISM $ cvs tag -b Branch-2002_11_16-EOTDS_Testing
# STEP 3: make sure it worked, by deleting the tree and checking out that branch
~/test/PRISM $ cd ~/test
~/test $ rm -rf PRISM
~/test $ cvs checkout -r Branch-2002_11_16-EOTDS_Testing PRISM
STEP 4: build PRISM for kicks
~/test $ cd PRISM
~/test $ cd BasePrograms
~/test $ export MAKE_HOME=~/test/PRISM/BasePrograms
~/test $ export DATA_HOME=~/test/PRISM/BaseData
~/test $ ./configure
[...snip]
~/test $ make
[...snip]
Now, the end-result of all of this is that code changes (commits)
made to this "test" sandbox WILL be recorded in CVS, but will NOT
affect the main development trunk.
Now, this can be tricky.
# make sure you're in the top-level directory of an updated
# "trunk" sandbox
$ cd ~/PRISM
# update (merge) the branch changes to the trunk
$ cvs update -j Branch-2002_11_16-EOTDS_Testing
# fix any conflicts (there may be some)
# commit the merged version into the trunk
$ cvs commit -m "merged changes from Branch-2002_11_16-EOTDS_Testing"
# all done!
Where it gets tricky is if:
What will seriously help in those cases is if you follow a procedure like this:
Then, the next time you want to merge in the "latest" changes from that
particular branch, all you've got to do is repeat the process (skipping
the first two steps). Let's say it's two days later:
(cvs tag -b Branch-2002_11_16-EOTDS_Testing)
(cvs tag Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_21)
cvs update -j Branch-2002_11_16-EOTDS_Testing -j Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_21
...yes, those were two -j options, saying "take the changes between 'this' tag and 'that' tag, and merge them into my current working directory.
(cvs commit -m "merged changes from Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_21")
(cvs tag Merged-Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_21)
Sound like a pain in the ass? Yes, I'd agree with you -- but that seems to be
the process.
(cvs tag Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_23)
cvs update -j Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_21 -j Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_23
(cvs commit -m "merged changes from Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_23")
(cvs tag Merged-Branch-2002_11_16-EOTDS_Testing-Merge-2003_01_23)
5.0 Locking vs. Merging
What is the "Locking Problem"?
The Locking Problem refers to the situation of what to do when two
developers are trying to modify the same code at the same time.
How does CVS address the Locking Problem?
From the CVS FAQ-o-matic primer:
In other words, when CVS calls itself a "Concurrent Versioning
System", it means concurrent. More than one person can edit
the same file at the same time.
cvs commit: Up-to-date check failed for `tracker.c'
Whoops! So, to correct the error, he types
cvs update tracker.c.
This is what he gets back:
cvs [commit aborted]: correct above errors first!
RCS file: /usr/local/cvsroot/testproj/src/tracker.c,v
retrieving revision 1.6
retrieving revision 1.7
Merging differences between 1.6 and 1.7 into tracker.c
M tracker.c
revision 1.7
date: 2002/08/05 19:02:44; author: jane; state: Exp; lines: +13 -5
fixed getNextTrack() bug which skipped every 16th track
----------------------------
No, it doesn't. In particular, it abysmally fails
when encountering any variation of this scenario:
Okay, there's a conflict; I see what it is, and how it came about.
$ cvs commit tracker.c
cvs commit: Up-to-date check failed for `tracker.c'
cvs [commit aborted]: correct above errors first!
$ cvs update tracker.cc
RCS file: /usr/local/cvsroot/testproj/src/tracker.c,v
retrieving revision 1.8
retrieving revision 1.9
Merging differences between 1.8 and 1.9 into tracker.c
rcsmerge: warning: conflicts during merge
cvs update: conflicts found in tracker.c
C tracker.c
int computeSomething( int *a, int *b ) {
(He won't actually see all those pretty colors, of course,
unless he's using an editor includes syntax rules for CVS.)
(like vim :-)
<<<<<<< tracker.c
int ratio = 2;
=======
int ratio = 4;
>>>>>>> 1.9
*a = *b * ratio;
return *b / *a;
}
In short, Bob and Jane will simply have to work this out exactly as they
normally would in the absence of a CM tool such as CVS.
6.0 Administration
How do you install CVS?
Under RedHat Linux 7.2, find it on "CD-ROM #2":
With the init command: http://www.cvshome.org/docs/manual/cvs_2.html#SEC23
With the import command: http://www.cvshome.org/docs/manual/cvs_3.html#SEC38.
cvs-oa $ cd /usr/local
cvs-oa $ tar zcvf ~/cvsroot.2002-08-01.tgz cvsroot
cvs-oa $ rcp ~/cvsroot.2002-08-01.tgz me@some.other.box:backups/
cvs-oa $ rcp me@some.other.box:backups/cvsroot.2002-08-01.tgz ~
cvs-oa $ cd /usr/local
cvs-oa $ sudo tar zxvf ~/cvsroot.2002-08-01.tgz
Short-form: with Unix access privileges.
$ sudo chgrp -R 'users' /usr/local/cvsroot
$ sudo chmod -R g+s /usr/local/cvsroot
7.0 Gotchas, Troubleshooting & Miscellani
Gotcha: Option order
The syntax for any CVS command is this:
cvs [global-opts]
command
[cmd-opts]
[file...]
You'll note that there are two places for options there, which I have
colored green and blue for your edification. The green, global options
are available and valid for every CVS command (AFAIK). The
blue, command-specific options vary with context. It is frequently
very important to know which options you're trying to use,
and to use them in the correct order.
opt
global meaning
update context
-r
make checked-out files read-only
update using specified revision/tag (sticky)
-f
skip ~/.cvsrc
force a head revision match if tag/date not found
-l
turn history logging off
local directory only, no recursion
I tried commiting a file using
What's with all these 'CVS' directories?
cvs commit $MAKE_HOME/config/sgi/config.mk
and it wouldn't let me, because MAKE_HOME was an "explicit path"
(/home/mzieg/proj/prism/PRISM/BasePrograms).
cvs commit ../../config/sgi/config.mk
and it wouldn't work because of "too many ..". Argh.
cd $MAKE_HOME && cvs commit config/sgi/config.mk
Bleah. Annoying.
You can ignore them, but unless you really know what you're doing,
I would neither delete nor edit them. It tends to really confuse
CVS, because it loses a lot of critical state information. There
may be a simple command to "regenerate" or "fix" a set of hosed
CVS directories, but I haven't found one yet. Seriously, just leave
them alone and don't worry about it.
Wuss.
Like most Unix tools, CVS will check for the existence of a ~/.cvsrc
file in your home directory at each invocation.
# ~/.cvsrc
diff -c
update -d
Appendix A: Other Sources of Information
Sadly, I've been unable to find a single source of comprehensive,
intelligent, easily navigable CVS documentation. The nominal
"home" of CVS, cvshome.org, is a poorly constructed quasi-commercial
venture that has little historical connection with the original
CVS developers/maintainers. For the time being, I've collected
a list of some of the better online CVS references I've come across,
more-or-less sorted by quality and general usefulness. Caveat Emptor.