Originally written: a long time ago, probably sometime in 2001.
by John Clarke
Note: I wrote this tutorial many years ago (I'm not sure exactly when, but probably some time in 2001) when CVS 1.10.8 was the current version. Therefore, some of the information in this tutorial may be out of date or incorrect. I've left it here purely for any historical (hysterical?) value it may have, and I've updated the links to point to the latest (v1.11.23 at the time I made the changes) CVS documentation because I'm no longer hosting the old manual.
These notes are intended as an introduction to CVS and some of its features. The full CVS documentation can be found here.
CVS stands for Concurrent Version System. It was designed to allow multiple developers to share access to the same source code, and to handle the potential conflicts that can result when two people modify the same file at the same time. You can find out more about what CVS is and what it isn't in the CVS documentation.
These notes use the cvs command line tools. For the GUI-dependent Windows users, WinCvs provides most of the same functionality.
The procedure for starting a new project depends upon whether you have an existing set of files or you're really starting from scratch. Existing files may come from another configuration management system, or they mey never have been under configuration control. The import command is used to import the source into the repository.
Starting a Project From Existing Files
There's little difference between starting with files already under configuration control and files which are uncontrolled. All you need to do is copy out a working copy from your old repository first. If you want to copy the enitre revision history, then you'll need to start with the first revisions you checked in to your old repository rather than the latest revisions.
For this example, your working files are in
~/work/test1
. The project is to be called "test1", and the directory within the repository istest/test1
. Here's how you start a new project:The parameters you need to pass to[johnc@dropbear ~/work/test1]$ ls hello.c [johnc@dropbear ~/work/test1]$ cvs import -m"Imported files to start the project" test/test1 vast start N test/test1/hello.c No conflicts created by this importimport
are:The vendor and release tags are rather meaningless when creating a new project, but the
-m "Imported files ..."
: the log message;test/test1
: the repository directory;vast
: the vendor tag; andstart
: the release tag.import
command requires them. They're more useful for tracking third-party sources.Now you need to delete your old copy (or rename the directory), and checkout a working copy from the repository, because importing files doesn't setup the CVS sub-directories. Read this for details of what CVS puts in these sub-directories in your working directory.
Starting a Project From Scratch
In this case, you create your project's directory structure, then
import
as above. Theimport
creates an empty directory structure in the repository to which you can add files later. Theadd
command will be described later.Defining the Module
This isn't strictly necessary, but it is more convenient to have a short name which refers to all the files and directories in a project. This is how you'd define a module called
test1
which consists of all the files in the repository directorytest/test1
:This gets you a working copy of the CVS admin files. You don't need to know about most of them for now, indeed you'll probably never need to know about most of them. The file we're interested in is[johnc@dropbear ~/work]$ cvs co CVSROOT cvs checkout: Updating CVSROOT U CVSROOT/checkoutlist U CVSROOT/commitinfo U CVSROOT/config U CVSROOT/cvsignore U CVSROOT/cvswrappers U CVSROOT/editinfo U CVSROOT/loginfo U CVSROOT/modules U CVSROOT/notify U CVSROOT/rcsinfo U CVSROOT/tagcheck.pl U CVSROOT/taginfo U CVSROOT/users U CVSROOT/verifymsgCVSROOT/modules
. You need to add this line at the end of the file:This defines the moduletest1 test/test1test1
as consisting of the repository directorytest/test1
. Now commit the change:The admin files are a little different from normal files in that there's a checked out copy of each file in the[johnc@dropbear ~/work]$ cvs commit -m"Defined test1 module" CVSROOT/modules Checking in CVSROOT/modules; /home/cvsroot/CVSROOT/modules,v <-- modules new revision: 1.14; previous revision: 1.13 done cvs commit: Rebuilding administrative file database [johnc@dropbear ~/work]$ cvs release -d CVSROOT You have [0] altered files in this repository. Are you sure you want to release (and delete) directory `CVSROOT': yCVSROOT
directory in the repository. TheRebuilding ...
message indicates that CVS has updated these working copies. If you don't see this message, something's wrong.Note also that I released the CVSROOT directory as soon as I'd made my change. You'll rarely, if ever, need to changes the admin files so it's a good idea not to keep a working copy around.
The first thing you'll need to do is checkout a working copy of a module. You do this with the checkout command, like this:
This creates a directory with the same name as the module, in this case "test2", containing all files and directories in the module.[johnc@dropbear ~/work]$ cvs checkout test2 cvs checkout: Updating test2 U test2/Makefile U test2/alloc.c U test2/alloc.h U test2/avr.sh U test2/breakpoint.c U test2/breakpoint.h ... cvs checkout: Updating test2 U test2/op/op1.c U test2/op/op1.hThe letter at the beginning of each line tells you what CVS has done with each file. A "U" means that the file has been brought up to date with respect to the repository, in other words, a copy of the latest revision of the file has been placed in your working directory. A more complete list of these codes is available here.
An important point to note here. Most CVS commands operate recursively unless told otherwise. Files or directories may be given as arguments, but if no argument is given, the current directory is assumed.
When you checkout files, CVS creates a directory, called
CVS
, in each directory within the module. The contents of these directories are described here.
Since CVS allows concurrent access to files, there's no distinction between "checkout" and "copyout". Once you have a working copy of the files, you can start editing. However, like all rules, there is an exception. The exception is "watched" files. Watches will be described later, but for now, you can ignore them.
Let's start with a simple change. We'll update the version number in
version.h
.
A Modified File
CVS will tell you the status of your working copy with the status command. Because it can produce a large amount of output, I'll demonstrate it with a couple of files:
The fields of interest to us at the moment are "Status", "Working revision" and "Repository revision". Since we've changed our working copy, the file status is "Locally Modified". The revision number of our working copy is "1.34", and the latest revision in the repository is also "1.34".[johnc@dropbear ~/work/test2]$ cvs status version.h =================================================================== File: version.h Status: Locally Modified Working revision: 1.34 Thu Jun 14 08:10:46 2001 Repository revision: 1.34 /home/cvsroot/test2/version.h,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none)An Unmodified File
Compare this with a file that hasn't been changed:
Note the status of "Up-to-date" because this file is identical to the copy in the repository.[johnc@dropbear ~/work/test2]$ cvs status alloc.c =================================================================== File: alloc.c Status: Up-to-date Working revision: 1.4 Thu Jun 7 05:45:10 2001 Repository revision: 1.4 /home/cvsroot/test2/alloc.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none)A File Which Has Been Changed By Someone Else
Now let's see what would happen if someone else had checked in a new version of a file whilst we've been working:
The status is now "Needs Patch" because our working version is "1.40" but the version in the repository is "1.41". We'll fix this with the[johnc@dropbear ~/work/test2]$ cvs status main.c =================================================================== File: main.c Status: Needs Patch Working revision: 1.40 Mon Jun 25 05:46:16 2001 Repository revision: 1.41 /home/cvsroot/test2/main.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none)update
command soon.A File Which Has Been Changed By You And By Someone Else
Here's another example, but this time we've modified the file and there's a new version in the repository:
Note that the status is now "Needs Merge" and the repository revision is higher than the working revision.[johnc@dropbear ~/work/test2]$ cvs status test_main.c =================================================================== File: test_main.c Status: Needs Merge Working revision: 1.25 Mon Jun 25 07:28:41 2001 Repository revision: 1.26 /home/cvsroot/test2/test_main.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none)
You can ask CVS to show you the differences between your working copy and the repository with the
diff
command. By default, it will display the differences between your working files and the revision you last checked out. Note that this is not necessarily the latest revision.The reasoning behind this is simple. Usually you use the diff command to view the changes you've made to your working copy. In this case, you're interested in the differences between your working copy and the revision it was based on. You don't want someone else's changes getting in the way.
Here's an example:
Note that there's no output for main.c, because although our working revision of main.c is not the latest, there are no differences between our working copy and the same revision in the repository.[johnc@dropbear ~/work/test2]$ cvs diff -Bbu Index: version.h =================================================================== RCS file: /home/cvsroot/test2/version.h,v retrieving revision 1.34 diff -B -b -u -r1.34 version.h --- version.h 2001/06/14 08:10:46 1.34 +++ version.h 2001/06/25 05:37:45 @@ -162,6 +162,6 @@ **----------------------------------------------------------------------------- */ -#define PROJECT_VERSION "1.2.9.0" +#define PROJECT_VERSION "1.2.10.0" #endifYou can override the default behaviour and view the differences between your working copy and the latest copy in the repository by specifying a revision number or tag. To get the latest revision, you use the magic tag
HEAD
, which always refers to the latest revision on the main trunk. You can also view the difference between any two arbitrary revisions by specifying the two revision numbers or tags (or one of each).
You check your changes in to the repository using the commit command:
After the[johnc@dropbear ~/work/test2]$ cvs commit version.h Checking in version.h; /home/cvsroot/test2/version.h,v <-- version.h new revision: 1.35; previous revision: 1.34 doneChecking in ...
message, you will be asked to enter the change log message. Your editor will start (as defined by the EDITOR or VISUAL environment variables) and you should write a descriptive log message. This information should be enough to outline the changes you've made. A message such as "Bug fixes" or "Changes" is clearly inadequate. For our example, an acceptable log message would be "Updated the version number".There's no need to completely detail the changes to each line. If someone wants to know the changes to that level, they can use the diff command. Your message should be a clear and concise description of what you did, for example:
Replaced printf with fprintf. Changed GP->pHcb to a union.
Adding a new file is a two stage process. First, you create the file. Then you tell CVS that there's a new file to be added with the add command. For example, we'll add a makefile to the
test1
module we created earlier:Finally, you need to commit the new file:[johnc@dropbear ~/work/test1]$ cvs add Makefile cvs add: scheduling file `Makefile' for addition cvs add: use 'cvs commit' to add this file permanentlyNote: Unlike most other CVS commands,[johnc@dropbear ~/work/test1]$ cvs commit -m"Initial revision" Makefile RCS file: /home/cvsroot/test/test1/Makefile,v done Checking in Makefile; /home/cvsroot/test/test1/Makefile,v <-- Makefile initial revision: 1.1 doneadd
is not recursive.
Your Working Copy Is Unchanged
Earlier, we noticed that our working copy of
main.c
was out of date, so we use the update command to get the latest version:Note that the working and repository revisions are now the same.[johnc@dropbear ~/work/test2]$ cvs update main.c U main.c [johnc@dropbear ~/work/test2]$ cvs status main.c =================================================================== File: main.c Status: Up-to-date Working revision: 1.41 Mon Jun 25 07:09:31 2001 Repository revision: 1.41 /home/cvsroot/test2/main.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none)Your Working Copy Has Been Changed
Now we'll try the same thing with the file we modified,
test_main.c
:The status command also tells us that there were conflicts:[johnc@dropbear ~/work/test2]$ cvs update test_main.c RCS file: /home/cvsroot/test2/test_main.c,v retrieving revision 1.25 retrieving revision 1.26 Merging differences between 1.25 and 1.26 into test_main.c rcsmerge: warning: conflicts during merge cvs update: conflicts found in test_main.c C test_main.cNow we need to fix the conflicts before we can commit our changes to the repository. Opening the file in the editor, we find the merge conflict indicated like this (note the conflict markers):[johnc@dropbear ~/work/test2]$ cvs status test_main.c =================================================================== File: test_main.c Status: File had conflicts on merge Working revision: 1.26 Result of merge Repository revision: 1.26 /home/cvsroot/test2/test_main.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none)Our original working copy has been saved as<<<<<<< test_main.c SETLABEL(pRunInnerLoop2); ======= SETLABEL(GP->pRunInnerLoop); >>>>>>> 1.26.#test_main.c.1.25
, but in this case, the conflict is so simple that we can easily resolve it. We change the code to read:SETLABEL(GP->pRunInnerLoop2);Now that the conflict has been resolved, we can commit our changes:
[johnc@dropbear ~/work/test2]$ cvs commit test_main.c Checking in test_main.c; /home/cvsroot/test2/test_main.c,v <-- test_main.c new revision: 1.27; previous revision: 1.26 doneAbandoning Your Changes
You can remove all changes you've made to your working copy by using the update command with the
-C
switch. This checks out a clean copy of the working file and saves your working file with a prefix of.#
and a suffix which is the version number:[johnc@dropbear ~/work/test2]$ cvs status main.c =================================================================== File: main.c Status: Locally Modified Working revision: 1.41 Tue Jun 26 04:01:57 2001 Repository revision: 1.41 /home/cvsroot/test2/main.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) [johnc@dropbear ~/work/test2]$ cvs update -C main.c (Locally modified main.c moved to .#main.c.1.41) U main.c [johnc@dropbear ~/work/test2]$ cvs status main.c =================================================================== File: main.c Status: Up-to-date Working revision: 1.41 Tue Jun 26 04:02:27 2001 Repository revision: 1.41 /home/cvsroot/test2/main.c,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none)
To delete your working copy, you could simply remove the directory. However, to ensure that the transaction is logged in CVS's history database, and users who are watching part or all of the module are informed, it's preferrable to use the release command:
The[johnc@dropbear ~/work]$ cvs release -d test2 You have [0] altered files in this repository. Are you sure you want to release (and delete) directory `test2': y-d
switch tells CVS to delete the working directory.
What Are Tags?
A tag is a symbolic name for a revision. Tags are generally applied to the entire module at once rather than to individual files, and are used to mark significant points during development. For example, a released version would typically be tagged with the version number, possibly in conjunction with the module name.
Tagging Files
By default, a tag is applied to the revisions of files in your working directory. Note also that it is applied to the checked-in revisions, not including any changes which you haven't yet committed. Here's an example of the tag command:
[johnc@dropbear ~/work/test2]$ cvs tag CVS_DEMO cvs tag: Tagging . T Makefile T alloc.c T alloc.h T avr.sh T breakpoint.c T breakpoint.h ...Listing Tags
To find out what tags gave been applied to a file, use the status command in verbose mode:
[johnc@dropbear ~/work/test2]$ cvs status -v version.h =================================================================== File: version.h Status: Up-to-date Working revision: 1.35 Mon Jun 25 06:55:30 2001 Repository revision: 1.35 /home/cvsroot/test2/version.h,v Sticky Tag: (none) Sticky Date: (none) Sticky Options: (none) Existing Tags: CVS_DEMO (revision: 1.35) v1_2_8_5 (revision: 1.28.2.6) v1_2_8_4 (revision: 1.28.2.5) v1_2_8_3 (revision: 1.28.2.4) v1_2_8_2 (revision: 1.28.2.3) v1_2_8_1 (revision: 1.28.2.2) v1_2_8_0 (revision: 1.28.2.1) v1_2_8_branch (branch: 1.28.2) v1_2_7_0 (revision: 1.28) v1_2_6_3 (revision: 1.27) v1_2_6_2 (revision: 1.26) v1_2_6_1 (revision: 1.26) v1_2_6_0 (revision: 1.25) ...Checking Out By Tag
To checkout a module or file by tag, use the
-r
switch to the checkout command. For example, to get release version1.2.8.3
of thetest2
module:[johnc@dropbear ~/work]$ cvs co -r v1_2_8_3 test2 cvs checkout: Updating test2 U test2/Makefile U test2/alloc.c U test2/alloc.h ...Sticky Tags
Now that we've checked out a particular tagged version, CVS will remember that fact and any commands will be relative to the tagged revision. This is indicated by the "Sticky Tag" field in the status output:
Further information on Sticky Tags is available here.[johnc@dropbear ~/work/test2]$ cvs status version.h =================================================================== File: version.h Status: Up-to-date Working revision: 1.28.2.4 Fri Jun 8 00:16:46 2001 Repository revision: 1.28.2.4 /home/cvsroot/test2/version.h,v Sticky Tag: v1_2_8_3 (revision: 1.28.2.4) Sticky Date: (none) Sticky Options: (none)
What Are Watches?
Watches are used to notify users when someone else does something. They can help multiple developers co-ordinate their activity, but they are not a substitute for proper project management and communication.
Watches have two distinct but related uses:
- To force read-only checkouts
- To notify users of changes to files
Forcing Read-Only Checkouts
To force all files to be checked out as read-only, use the watch on command. This setting can be disabled by issuing the
watch off
command.To edit a file which is watched, use the edit command rather than simply changing the read-only attribute of your working file. This allows CVS to notify users which have requested notification of the
edit
action. We can't force you to do this, but be nice to your fellow developers. One day you may need them to help you.If watches are on and you want to abandon some changes you've made, use the unedit command. This replaces your working copy with the same revision from the repository and notifies anyone who has an
unedit
watch on the file. Note that this only works if watches are on (i.e. you had to use theedit
command before editing the file). If watches are off you should useupdate -C
as explained earlier.Enabling Watches
With the watch add command, you can enable notification one or more of the following actions by others:
Also allowed are two special keywords:
- edit
- unedit
- commit
The latter is only really useful when using the edit command to prevent a temporary edit or commit watch being established.
- all
- none
For example:
Running[johnc@dropbear ~/work/test2]$ cvs watch add -a edit -a commit version.h [johnc@dropbear ~/work/test2]$ cvs watchers version.h version.h johnc edit commitwatch add
without any arguments adds all watches to all files recursively:[johnc@dropbear ~/work/test2]$ cvs watch add [johnc@dropbear ~/work/test2]$ cvs watchers version.h version.h johnc edit unedit commitRemoving Watches
Watches are removed using the watch remove command:
Note that watches can be removed independently, even if they were established with the one command. Each watch is independent of the others.[johnc@dropbear ~/work/test2]$ cvs watch remove -a commit version.h [johnc@dropbear ~/work/test2]$ cvs watchers version.h version.h johnc editRunning
watch remove
without any arguments removes all watches from all files recursively:[johnc@dropbear ~/work/test2]$ cvs watch remove [johnc@dropbear ~/work/test2]$ cvs watchers version.h
Why Branch?
A branch allows you to isolate changes into a separate line of development. Consider our test2 project. v1.2.7.0 was released on 27 April. Assume for the sake of this argument that one customer is still running this version and have reported a bug, and that the latest development is unstable and not suitable for release yet.
So, you check out v1.2.7.0 and find the bug:
Note the message about the file which is not in the repository. This file has been added since v1.2.7.0. Your working copy of this file is deleted because it's not part of the older version.[johnc@dropbear ~/work/test2]$ cvs update -r v1_2_7_0 cvs update: Updating . U Makefile U alloc.c U alloc.h ... cvs update: target.c is no longer in the repository ... [johnc@dropbear ~/work/test2]$ cvs status version.h =================================================================== File: version.h Status: Up-to-date Working revision: 1.28 Tue Jun 26 05:53:12 2001 Repository revision: 1.28 /home/cvsroot/test2/version.h,v Sticky Tag: v1_2_7_0 (revision: 1.28) Sticky Date: (none) Sticky Options: (none)Creating the Branch
Once you've found the bug, you create and checkout the branch before you make your changes:
Note that you need to check out the branch after creating it. This is because the branch is created in the repository, not in the working copy.[johnc@dropbear ~/work/test2]$ cvs tag -b v1_2_7_branch cvs tag: Tagging . T Makefile T alloc.c T alloc.h ... [johnc@dropbear ~/work/test2]$ cvs update -r v1_2_7_branchChecking In To the Branch
Now you make your changes and commit them:
Note the new revision numbers: "1.28.4.1;" and "1.18.8.1;". Revision numbers with more than two parts refer to branches. You don't need to know about them; they're assigned automatically by CVS, but if you really want the gory details, they're described here.[johnc@dropbear ~/work/test2]$ cvs commit Checking in version.h; /home/cvsroot/test2/version.h.,v <-- version.h new revision: 1.28.4.1; previous revision: 1.28 done Checking in op/op1.c; /home/cvsroot/test2/op/op1.c,v <-- op1.c new revision: 1.18.8.1; previous revision: 1.18 doneMerging Between the Branch and the Trunk
Now that you've made a change on the new branch, how do you make sure that the change is propagated to the main trunk? You could simply make the same changes manually, but your changes may be quite complex. So, you can use CVS's merging abilities to do it for you. First, get the latest revisions:
Now, merge all changes from the branch into the current working copy:[johnc@dropbear ~/work/test2]$ cvs update -A cvs update: Updating . U Makefile U alloc.c U alloc.h ...Fix any merge conflicts and commit the changes:[johnc@dropbear ~/work/test2]$ cvs update -j v1_2_7_branch cvs update: Updating . ... RCS file: /home/cvsroot/test2/version.h,v retrieving revision 1.28 retrieving revision 1.28.4.1 Merging differences between 1.28 and 1.28.4.1 into version.h ... RCS file: /home/cvsroot/test2/op/op1.c,v retrieving revision 1.18 retrieving revision 1.18.8.1 Merging differences between 1.18 and 1.18.8.1 into op1.c ...You can also merge from the branch several times, rather than all at once. There's a trick to managing it: tag the branch each time you do the merge, and use that tag to specify where to merge from.[johnc@dropbear ~/work/test2]$ cvs commit -m "Included v1.2.7.1 fix" cvs commit: Examining . Checking in version.h; /home/cvsroot/test2/version.h,v <-- version.h new revision: 1.36 previous revision: 1.35 done cvs commit: Examining flexlm_nt cvs commit: Examining flexlm_nt/include cvs commit: Examining flexlm_nt/lib cvs commit: Examining loader Checking in op1.c; /home/cvsroot/test2/op/op1.c,v <-- op1.c new revision: 1.20 previous revision: 1.19 done
Each CVS repository has a directory, called
CVSROOT
, which contains various admin files. Users will generally only need to change these files when a module is added or deleted. The file which contains the mapping between modules and files/directories is calledCVSROOT/modules
.There other files will only need to be modified by the Configuration Manager to enforce any requirements on activities within the repository. For example, our test project may enforce the rule that a tag which looks like a version number (e.g. v1_2_7_0) must match the version number in
version.h
, it must be unique, and it may not be deleted or moved.There's a complete guide to the admin files here.