By Mitch Stuart
Copyright © 2002-2003 FullSpan Software - Usage subject to license
Document Version: $Revision: 1.3 $, $Date: 2004/01/28 05:40:26 $
CVS is an open source version control system. This document gives some basic information and tips on using CVS, and provides pointers to CVS information and tools.
2. CVS Software
3.2. Cross Platform
3.3. Web Based
4. Comparison with SourceSafe
4.1. Concurrent Development
4.2.Terminology and Behavior
5. Example Scenario
6. Engineer Usage
6.1. Occasional Tasks
6.2 Regular Tasks
6.3. Other Useful Commands
6.4. Working with Branches
7. Release Manager Usage
7.2. Tags and Branches
7.3. Branch and Merge Example
8. Administrator Usage
9. CVS Annoyances
The CVSNT project started as a Windows-specific port of CVS but now runs on *nix platforms. Some users seem to prefer this distribution (I have not tried it).
If you use WinCVS you will need a good, external, graphical diff program. The one that I use is Araxis Merge, however this costs (as of this writing) about $125 per seat. There are various free diff utilities listed on the WinCVS home page, including WinMerge.
TkDiff is a Tcl/Tk GUI diff utility that works well with TkCVS.
CVSGraph works well with ViewCVS to provide a graphical tree view of the CVS repository.
In contrast, CVS allows multiple users to edit the same file. The first user to "commit" (check in) the updated file wins - their changes are accepted with no special handling. When the next user attempts to commit the same file, they will be warned that their copy is not up to date and they must update the file from the repository and have CVS merge in their local changes. This is discussed in detail in the Engineer Usage section below.
It is possible (using the CVS "watch" command) to force files to be gotten in read-only mode (I have never used this feature). However, this does not change the ability of any user to commit their changes in any order, so it does not really enforce the one-writer-per-file in the way that SourceSafe does. The CVS watch facility is more of way to keep the team informed of who is doing what, rather than a way to enforce a usage model.
In SourceSafe, you create top-level projects in the root ($/) of the SourceSafe tree using the
In CVS, projects are formally known as modules. (However, it is common to use the term project when discussing a CVS module, as I do later in this document.) You create modules with the
Get Latest Version
In SourceSafe, you use
In CVS, all the files you retrieve from the repository are writable by default. Use the
However, the files you add with SourceSafe are immediately available for others to retrieve. With CVS, the add feature only "registers" the files with CVS - they are not actually available in the repository until you
The word processor uses the names of mountains as its release code words: Everest is the upcoming release, and Ranier is the release after that.
When working on a new project or wanting to get a clean slate, use the
cvs checkout command to get a local working copy of a module.
cvs -nqupdate command whenever you want to see how your local working copy differs from the repository (the
-nsays to take no action, to just report the differences, and the
-qis the quiet flag to generate less verbose output). This command does not show you the differences within files, it just shows you a list of files that are different. These could be files in the repository that were added or modified by other users, or local files that you have created or modified.
cvs updatecommand to update your local working copy from the repository. This should be done on a more or less daily basis. If you are working on any files that others might be changing, you generally shouldn't go more than a few days between updates. It becomes more painful, error-prone, and risky to do any necessary merging when there have been many changes by various users. On the other hand, if you are working on an isolated project, then you can wait longer in between updates.
In most cases, when you use the
cvs update command, you will want to use the
cvs update -P. This "prunes" any empty directories from your local copy and is necessary because CVS does not allow directories to be deleted (see CVS Annoyances).
cvs addcommand to add files or directories.
cvs updateto update your local working copy from the repository. CVS will bring any new or updated files into your working copy. If any of the files you have edited have been changed by others, CVS will merge the changes into your local copy. For each file, CVS will tell you if the merge was successful or if it encountered conflicts.
The scope of the update command is a judgement call.
cvs statuscommand first (discussed below) to see if any other user has changed those files.
cvs diff(or use one of the external visual diff utilities) to ensure that the changes you will be committing are exactly what you expect - no more and no less.
cvs diffcommands will have a large impact on how much additional testing (if any) you need to do before committing.
cvs committo commit your local changes (including any merges) to the repository.
cvs statusto see the status of a file. CVS will tell you if you have modified the file locally, or if there is an updated version present in the repository (i.e., if another user has committed the file since the time that you retrieved your version).
cvs logto see the history of a file. It is easier to see the history using ViewCVS or one of the other GUI tools discussed above, since they can show the history in a graphical way - especially if the file has many branches and/or revisions.
You can check out to a named directory; this is useful if you need to work on several releases in parallel:
cvs checkout -r fsword_everest
cvs checkout -r fsword_everest -d everest
-roption is "sticky" - after you have checked out a branch, all of your updates and commits (in that working directory and its subdirectories) apply only to that branch.
The directories common, fscalc, and fsword are modules within CVS; each can be imported or checked out independently. The directories within the modules can have any organization that makes sense. In this case, we have split each module along organizational lines. There would be further breakdowns in the directories by functional classification. For example, the dev/src directory would have subdirectories for each of the components that make up the product.
common doc dev build src ext qa fscalc doc dev build src ext qa fsword doc dev build src ext qa
cvs tagcommand to tag the repository based on the current snapshot in your working directory. Use the
cvs rtagcommand to tag the repository based on the repository contents (often indicated by some attribute such as an existing tag or a date).
The CVS main line or trunk of each module has a special built-in tag called
There are various strategies for tags and branches. It is important to have a controlled, documented process. My suggestion is that when development is to begin for a given release, a new branch is created for that release. Development of several releases can occur in parallel, each release on its own branch.
In addition to branches, non-branch tags should be created:
cvs update -j(or
cvs checkout -j) command to merge changes. Without these tags you would be stuck with doing date-based merges and possibly missing changes (or getting unwanted changes), and doing cleanup by hand.
For example, you could have a naming convention like this:
where project is the name of the project, release is the name of the release, taginfo specifies the purpose of the tag, and tag is an indicator as to whether this is a non-branch tag. Branches do not have the _tag suffix, but non-branch tags do. If taginfo has multiple words, they should be separated with hyphens instead of underscores, to make the components of the tag name clear (e.g., use
The following table illustrates my recommended tag and branch strategy and naming convention.
Tag Format and
|This is the branch tag for the everest release of the fsword product. Notice that it does not have the _tag suffix, which tells us that this is a branch tag.|
|This tag is created just after the branch tag, before anything is added or changed in the branch.|
|These tags mark various milestones that may be useful build or merge points. These could be tagged with a build number (e.g., build23), a release candidate number (e.g., beta1rc4, rc3), or a status (e.g., ga, for "general availability" to identify the final product that is shipped to customers).|
|These tags mark the merge points between branches (merging is discussed in the following section). In this case, we are merging from everest SP2 to ranier, and this is the third merge we have done between these two branches (identified by the sequence number 3).|
Create CVS project. First we will create the CVS module (project) fsword.
Note that you are actually in the fsword directory (not above it) when you create the module. The
cd /somepath/fsword cvs import -m "import of project fsword" fsword vstart start
-moption gives a message (comment) for the operation; the message is visible in the CVS history log. The next parameter,
fsword, is the name of the project. Choose this carefully; this is the name that engineers will use to check out the project. (If you later want to change or abbreviate the name, CVS allows you to create an alias.)
start parameters are not important for our purposes (see the Fogel book for details).
Create fsword branch. We are about to begin development on the everest release of fsword. As discussed above, I think it is beneficial to tag the project before development begins. This allows engineers to refer to the same branch tag throughout the development cycle. If we began development on the main line, and then branched later, engineers would have to reset their workspaces to point to the branch. (Not a big deal, but just an annoyance that is easily avoided.)
cvs rtag -r HEAD fsword_pre_branch_everest_tag cvs rtag -r HEAD -b fsword_everest cvs rtag -r fsword_everest fsword_everest_start_tag
rtag command adds a tag to the repository based on the contents of the repository itself (as opposed to the
tag command, which tags the repository based on the contents of the local workspace). We are using
rtag here because: (a) we have not yet run
checkout on the fsword project, so we have no local CVS workspace with which to use the
tag command, and (b) I prefer to use
rtag whenever possible, since it is a server-side-only operation.
-b option says to create a branch tag; remember, branch tags are just a special type of tag. As per our naming convention, we do not use the suffix "_tag" for this tag, because it is a branch tag.
fsword_everest is the name of the branch (recall that this is a branch of the fsword project).
Check out the everest branch.
This sequence saves the old fsword directory (the source of the import) under a new name - we need to move it out of the way so we can get the directory fresh from CVS.
cd .. mv fsword fsword-before-cvs cvs checkout -r fsword_everest fsword
cvs checkout command retrieves the fsword project from CVS to our local disk, into a directory called fsword. (You can retrieve projects into other directories with the
-d option on checkout. This can come in handy if you need to have several parallel versions of the same project in your workspace.)
-r fsword_everest option tells CVS to check out the fsword_everest branch. If you leave out the
-r option, CVS would retrieve the main line (trunk) version. In our current state, this would be the same thing, since we have not made any changes since importing the project. But the important thing is that the
-r tag is sticky, meaning that any changes we make to our local workspace, and any CVS commands that we run in this workspace, apply to (and only to) the branch that we are working on.
Engineers can now check out the fsword project with the same command (
cvs checkout -r fsword_everest fsword) shown above, and proceed with their work. When they commit their changes, those changes will be committed to the fsword_everest branch only.
Create fsword-patch1 branch. Let's say a customer patch is needed on the original fsword product. We don't want to use the fsword_everest branch to create this because there are many other changes for the everest release that we don't want to be part of the patch. So we create a branch to work on those changes.
This above commands create and check out the fsword-patch1 branch. The
cvs rtag -r HEAD fsword_pre_branch_patch1_tag cvs rtag -r HEAD -b fsword-patch1 cvs rtag -r fsword-patch1 fsword-patch1_start_tag cvs checkout -d fsword-patch1 -r fsword-patch1 fsword
-roption tells CVS to give us the fsword-patch1 branch of the fsword project. The
-doption tells CVS to put the files in a directory called fsword-patch1 (instead of the default directory for the project, fsword).
Development and testing of the patch is completed, and the files are all commited to CVS in the fsword-patch1 branch.
Merge fsword-patch1 branch to everest. This sequence merges the patch1 branch changes into the everest branch:
cvs rtag -r fsword-patch1 fsword-patch1_mergeto_everest_1_tag cvs checkout -r fsword_everest fsword cd fsword cvs tag fsword_everest_pre_mergefrom_patch1_1_tag cvs update -j fsword-patch1_mergeto_everest_1_tag [resolve any conflicts, build, test, fix] cvs commit -m "Merging patch1 into everest" cvs tag fsword_everest_post_mergefrom_patch1_1_tag
rtag command tags the patch1 branch so we know what changes have been merged to everest in this merge (merge sequence number 1, since this is the first merge from patch1 to everest).
checkout command gets a clean copy of everest to our workspace. This command can be skipped if you already have a copy of everest in your workspace.
tag command tags the everest branch before the merge.
update command actually does the merge. It will notify you of any conflicts. After this you need to resolve merge conflicts (if any), build and test.
commit command puts the merged changes into the repository on the everest branch.
tag command tags the everest branch after the merge.
Second merge of fsword-patch1 to everest. Oops! It turns out that patch1 was not really ready to ship after all. Some problems were found and additional changes were committed. This sequence shows how to get those latest changes from the patch1 branch into the everest branch.
cvs rtag -r fsword-patch1 fsword-patch1_mergeto_everest_2_tag cvs checkout -r fsword_everest fsword cd fsword cvs tag fsword_everest_pre_mergefrom_patch1_2_tag cvs update -j fsword-patch1_mergeto_everest_1_tag -j fsword-patch1_mergeto_everest_2_tag [resolve any conflicts, build, test, fix] cvs commit -m "Merging patch1 into everest (2nd merge)" cvs tag fsword_everest_post_mergefrom_patch1_2_tag
The commands are almost identical to the previous set (except for using merge sequence number 2 instead of 1), so I will not review them again.
However, notice the
update command. It has two
-j options this time. This tells CVS to merge only the changes between fsword-patch1_mergeto_everest_1_tag and fsword-patch1_mergeto_everest_2_tag into our local workspace. If we specified only one
-j option (
-j fsword-patch1_mergeto_everest_2_tag), CVS would attempt to merge all of the changes from the beginning of the patch1 branch to our local copy. But we have already merged in all the changes up to fsword-patch1_mergeto_everest_1_tag, so we don't want CVS to re-merge those changes again.
From this example, the benefit of rigorously tagging merge points should be clear.
Create everest-sp1 branch. The everest release has not shipped yet, but we want to start parallel development on the first service pack. So we create an everest-sp1 branch from the everest branch (a nested branch).
cvs rtag -r fsword_everest fsword_pre_branch_everest-sp1_tag cvs rtag -r fsword_everest -b fsword_everest-sp1 cvs rtag -r fsword_everest-sp1 fsword_everest-sp1_start_tag
Engineers can now begin using the everest-sp1 branch.
Create ranier branch.We still have not shipped the everest product (what's up with that!?), but we've been lucky to hire some additional developers so we are going to start parallel development of the next major fsword release, code-named ranier. We need to create a branch for the ranier development.
We could branch off the everest branch, like we did for Service Pack 1. But if we keep branching like this, we will never get back to the main line, and the CVS branch tree will grow in a very narrow and deep way, which makes it awkward to work with.
Therefore, we want to create the ranier branch from the main line, to keep the CVS tree reasonably flat. Before we create the ranier branch, we need to refresh the main line with the current code.
cvs rtag -r fsword_everest fsword_everest_mergeto_head_1_tag cvs checkout -r HEAD fsword cvs tag fsword_head_pre_mergefrom_everest_1_tag cvs update -j fsword_everest_mergeto_head_1_tag [resolve any conflicts, build, test, fix] cvs commit -m "Merging everest into main line" cvs tag fsword_head_post_mergefrom_everest_1_tag cvs rtag -r HEAD fsword_pre_branch_ranier_tag cvs rtag -r HEAD -b fsword_ranier cvs rtag -r fsword_ranier fsword_ranier_start_tag
Notice that in the abvove example, we are using the everest branch as the baseline for the ranier branch. We could have instead used everest-sp1 branch. We just need to pick which one best accomodates our needs. For example, if the everest-sp1 branch is very unstable right now, we would want to use the everest branch, so the ranier developers are not stuck with all the same bugs as the everest-sp1 developers are fighting.
In either case, assume that changes will continue to both the everest and everest-sp1 branches. All of those changes will eventually need to be merged into ranier. Ideally this will be done by merging from everest to everest-sp1, and then from everest-sp1 to ranier. It could also be done by merging separately from everest to ranier, and from everest-sp1 to ranier. Because we are using tags to keep track of every branch and merge point, there are many options for merging. However, it seems like a good idea to do serial merges (as opposed to parallel merges) whenever possible. In other words, we would rather merge from A to B and then B to C, rather than from A to B and from A to C - it is likely that there are other changes in B so we will need to merge from B to C anyway, and that may cause conflicts with our direct A to C merge.
Release everest. Finally it is time to release everest. We need to go through a release cycle, tagging release candidates (or build numbers, however you want to do it), and finally tagging the "final" GA (general availability) or FCS (first customer ship) version.
cvs tag fsword_everest_rc1_tag [test, fix] cvs tag fsword_everest_rc2_tag [test, fix]... cvs tag fsword_everest_rcN_tag [test - OK, ready to ship] cvs tag fsword_everest_ga_tag
Now we need to merge the final everest changes into everest-sp1, and then everest-sp1 into ranier. It is not urgent that this be done on the same day as the final release is tagged. It can wait a few days or a couple of weeks until the branch being merged into is in a stable enough state to accept the merges. But don't wait too long, because that will increase the chance of conflits.
Normally you would now merge everest back to the main line, and then branch off for the next release. However, in this example we have already created the ranier branch, so it is not strictly necessary to merge everest into the main line - as long as everest is merged into everest-sp1 and ranier, everything is OK. When ranier is released (or when ranier is branched for the next main release, whichever comes first), ranier will be merged to the main line.
Release everest-sp1. Now we are ready to release everest-sp1. We go through a similar testing and release candidate cycle as for everest (perhaps with fewer candidates - since this is only a service pack there should be a smaller scope of changes, and hoppefully a smaller number of bugs). Then we merge from everest-sp1 into the next main release (ranier in this case).
What if we need a patch for Service Pack 1? We would create an everest-sp1-patch1 branch from everest-sp1.
What if we need a Service Pack 2 for everest? We would create an everest-sp2 branch from the everest branch. If there are any changes in everest-sp1 or any everest-sp1-patchN branches that have not yet been merged into everest, those would need to either be merged into everest before the everest-sp2 branch is created, or separately merged into the everest-sp2 branch.
Depending on the access method and configuration you choose, there may be some administrative work involved in issuing CVS userids and passwords.
After installing CVS, the administrator will initialize the repository and set the permissions. It is then ready for usage by release managers and engineers.
-kkoption to disable keyword expansion, but that causes binary files (marked with
-kb) to lose their binary flag. Therefore, do not use the
-kkflag unless you know exactly what you are doing.
The Fogel book discusses this issue and recommends to "Just deal with the conflicts by hand instead."
You can avoid many cases of this problem if you have a policy that fixes are not to be manually committed on multiple branches. Instead, commit the fix on one branch and use CVS to merge the fix into the other branch. Also, explore using the
-ko option on the cvs update command so that CVS will not adjust the keyword text. This will disable keyword expansion during the merge.