Features
Features
Working Model
The CVS model assumes that there is one central official master copy
of everything. It is called "the CVS repository." This copy is managed
by CVS and is not meant to be touched directly.
Instead, when you wish to work on a CVS-managed project, you "check
out" your own private copy. Since work on a program is an ongoing process,
and other people may be working on the same program at the same time, you
generally want changes in the official master copy to appear in your private
copy. To facilitate this, the private copy is connected back to the master
copy. Some people think of this as a "subscription" -the private copy is
signed up to receive copies of updates that are made to the master copy.
Note, however, that these updates do not happen automatically. You must
explicitly update your private copy when the master copy changes.
When you transfer ("commit") changes in your private copy to the master
copy, the master copy is updated and other developers will then see those
changes when they update. Changes you make in your private copy that you
do not commit do not officially exist and will not be seen by anyone else
until you commit them.
You can have as many working trees (private copies connected to the
master copy) as you want. Often you will have only one, but circumstances
may arise in which it is more convenient to have two, or to make temporary
ones, or whatever. Just remember that they are all independent: while they're
all connected back to the master copy, they are not connected to each other
except through the master copy.
Repository
The official master copy is known as the "CVS repository". It is a directory
tree that must be kept in a place where all participating programmers can
access it. (CVS has various remote access features which will be described
briefly later on.) CVS uses the environment variable $CVSROOT to locate
the repository. Always be sure that this variable is set and that it points
to the correct CVS repository before invoking CVS.
The CVS repository contains one CVS file for each file in your project,
laid out in a directory tree that mirrors your project organization. These
files have a ",v" suffix and contain control information and version history
as well as the latest version of each file. (While in emergencies these
files can be edited by hand, doing so is strongly discouraged.)
The CVS repository also contains a directory CVSROOT that holds various
CVS configuration data. When you create a repository, you get default versions
of all these files that include comments briefly explaining the purpose
and syntax of each. If you want to edit these files, check out "CVSROOT"
as if it were another project, edit the files, and commit them. See below
for a couple of simple examples.
Note: do not confuse the CVSROOT directory with the $CVSROOT environment
variable. The CVSROOT directory is really $CVSROOT/CVSROOT.
Back up your CVS repository occasionally, just in case. If you lose
your working tree, you should only lose a small amount of work; but if
you lose your CVS repository, you lose everything.
Merging
Unlike many (most?) source control systems, CVS does not lock files
for modification. Instead, CVS works from a model where everybody edits
freely and changes to the same file are merged. CVS does not support locks
at all.
In the merge-based model, anyone can edit any file at any time. This
is both an advantage and a disadvantage: if two people have small unrelated
changes to make in the same file, they can do so without any difficulty.
On the other hand, if two people make sweeping changes to the same file
at once, the resulting merge becomes a nightmare.
(Another major advantage of merging is that when there are no locks,
nobody can hold up development by leaving on vacation while holding a lock
on a critical file.)
The way CVS merging works is as follows: when you check out a working
tree, CVS remembers what version of each file you got. When you update
your working tree, it updates these versions. When you go to commit, if
the version your changes are based on is not the latest one, CVS aborts
and tells you to update first.
Then, when you update, CVS notices that you have changed your copy and
the master copy has changed as well. It then tries to merge the two sets
of changes. If the changes are to unrelated areas of the file, this usually
succeeds. If the changes overlap, or the merge program gets confused, CVS
will say "conflicts during merge". The resulting file will contain blocks
that look like this:
int foo(void) {
<<<<<<< foo.c
bar();
=======
baz()
>>>>>>> 1.2
}
This means that your copy of foo.c changed function foo to call bar, but
that the official master copy, in version 1.2, changed foo in the same
place so it calls baz.
When you get merge conflicts like this, you need to resolve them before
committing your new versions. You might pick your version, or the latest
version from the repository, or some combination of the two, or whatever.
When doing this, it's up to you to make sure you do the right thing.
Some notes:
-
Even when there are conflicts, the conflict blocks do not necessarily reflect
all the changes associated with the merge. Some may have merged successfully.
If in doubt, look at diffs.
-
It is also not always the case that everything that may be involved in
resolving a conflict correctly is contained within the conflict block delimiters.
The merge program is only a program, not an omnipotent human being.
-
While the merge system is reasonably robust, once in a while it makes a
mistake, particularly if some but not all of the changes merged. It's prudent
to look at diffs after an automatic merge, just in case.
-
Merging is painful. Merging a big change is a lot more painful than merging
the same amount of change a bit at a time. Update early and often. Commit
early and often.
If you are planning to make huge changes to a file, like reordering all
the functions or moving large blocks of code into if clauses (which changes
the indent, making CVS think everything changed), it's a good idea to coordinate
manually with anyone else who might have pending changes to the file.
Log Messages
When you commit changes to the CVS repository, CVS gives you the opportunity
to provide a message explaining the change. These messages get saved in
the CVS file and can be reviewed later using cvs log. This can be quite
useful when trying to reconstruct the thinking that led to some piece of
code you wrote months previously.
These messages can also be logged centrally or mailed out to the people
working on the project. We recommend that you set up your CVS repository
to mail commit messages to you and your partner. (See below.) While the
volume of mail thus generated can be irritating, there's no better way
to stay in touch with what's going on.
The commit message should thus describe (briefly) what you did and why.
There's no need to report the exact changes, as they can be retrieved using
cvs diff.
When to commit
The general rule for commits is that any change should be committed
as soon as you're reasonably certain that it's correct and appropriate
in the long term, subject to the proviso that committing many small changes
in quick succession will probably annoy everyone working with you.
Remember that your partner won't see anything you don't commit, so get
bug fixes in quickly and hold back a little on new features that might
still have problems.
Ideally you and your partner should keep track of which tests you expect
to work at any particular time, and before committing check to make sure
that they all still do work.
In most cases, one should try to avoid committing changes that cause
the program to stop working properly (or, even, stop compiling at all.)
This rule can sometimes be profitably bent when you know your partner will
not be affected by the errors introduced.
Tags
CVS inherited part of its view of the world from an earlier toolset
known as "RCS", mentioned briefly below. One of the consequences of this
is that every file has its own private version number. Thus, while it is
possible to do things like look at version 1.5 of every file, it is not
very useful to do so.
Instead, CVS supports a concept known as a "tag". A tag is a symbolic
name (like asst4-debugged) that you attach to a particular version of some
set of files. You can then refer to that version of those files with the
name.
A tag is normally used to identify a single consistent version of an
entire project. For instance, the directions for each assignment (after
assignment 0) tell you to create one tag before starting and another tag
after you're done. These tags then identify the versions of all your files
that were current before and after you did the assignment. This lets you,
for example, ask CVS to show you all the changes in the entire system between
those two points.
Tags are more or less arbitrary alphanumeric text, but, to disambiguate
them from file version numbers, they may not begin with digits or contain
dots. Since tags are frequently used for program version numbers, and version
numbers regularly contain dots, it is conventional to use an underscore
(`_') in place of each dot.
See below for specific directions for manipulating tags with CVS.
Branches
Sometimes you might have more than one "line of development" in your
program. For instance, when you ship release 1.0 to customers, you might
have one team working on release 2.0, and another team making minor bug
fixes to the release 1.0 code for release 1.01.
In this case, most changes made for release 2.0 should not be incorporated
into release 1.01, and while many fixes made for release 1.01 should be
incorporated into release 2.0, some probably shouldn't be.
This sort of situation is handled using "branches". Each branch is a
(mostly) separate line of development, diverging from some common ancestor
version. (This divergence is where the term "branch" arose.)
Using branches in CVS can be fairly painful and is beyond the scope
of this document. The vendor branch functionality is an exception and is
discussed briefly below along with cvs import.
Use CVS Effectively
CVS is a tool, not a panacea. It helps you organize and maintain a project,
but it doesn't do it by itself. It requires that you use it in a manner
that makes it useful.
In order for the repository to be a useful tool for keeping track of
what is really part of the project and what is not, you have to actively
maintain the set of files CVS knows about. Don't add or commit temporary
files, editor backups, object files, and the like to the CVS repository.
Do remove files you're not using any more. (Even after telling CVS to remove
them, you can still get them back later, because removing them is just
a change that CVS tracks.)
In order for the version history to be useful, you have to add tags
at important points in development, like releases. You also have to write
at least minimally useful commit messages so you can look at them later
and be reminded of the circumstances.
In order for the merging features to be useful, you have to avoid making
sweeping changes without warning your partner, you have to update and commit
regularly but not insanely often, and you have to take the trouble to merge
correctly by hand when conflicts occur.
If you don't do these things, you will eventually end up in a hole,
and CVS will not save you from yourself.
RCS
Some of you may have seen RCS, the Revision Control
System. RCS is an ancestor of CVS; in fact, RCS more or less serves as
the "back end" of CVS.
If you have used RCS, think of CVS as like a super-RCS that works on
whole directory trees instead of single files. It also, as noted above,
does not support file locking, but uses merging instead. There are some
other minor differences as well.
If you haven't used RCS or don't know what it is, don't worry about
it.
|