Browse in : |
All
> Topics
> Management
All > Journals > CVu > 136 Any of these categories - All of these categories |
Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.
Title: Using CVS (A Beginner's Guide)
Author: Administrator
Date: 03 December 2001 13:15:48 +00:00 or Mon, 03 December 2001 13:15:48 +00:00
Summary:
This is a pretty short introduction to using CVS on a small-ish project. The scope covers basic versioning tasks (adding, removing, check-in, check-out) and goes up to very basic multi-user access tasks (branching and merging).
Body:
This is a pretty short introduction to using CVS on a small-ish project. The scope covers basic versioning tasks (adding, removing, check-in, check-out) and goes up to very basic multi-user access tasks (branching and merging).
It's not intended to be a user guide; it's more the Blind leading the Blind. That's why the apostrophe is not misplaced in the sub-title - I am a beginner as a write this, hoping to pass on what I've learned to you.
Start as you mean to go on. If this is your first exploration into CVS territory, this bit is for you.
The first thing to do is to create a new repository: (I am assuming a projects directory throughout this article)
cvs -d :local:/projects/cvs init
The -d argument tells CVS where to find the repository. The first part of what follows is the server type - in this case, the local machine. This will be different for remote repositories, which I won't be covering here. After that, the path to where you want the repository to live.
I have used the Unix path convention here. I recommend getting Cygwin from RedHat[1]. This allows you to use the same convention throughout as is used here. CVS is ported to Win32 as well, and that version will accept Win32 path names, too:
cvs -d :local:c:\projects\cvs init
Having done this, you should notice a new directory called cvs in your projects directory. This entry in turn has a CVSROOT directory, which contains CVS's administrative files.
In fact we will start a new project; when I say start as you mean to go on, here's where it means the most: when you start a project, the very first thing is to add the empty project to CVS. That way, you can track all of your changes, including the first check in. Let's start a project called helloworld. (If that sounds familiar, you're in the right place!)
Create a new directory. It doesn't actually matter what it's called, as long as it contains no files. Make it the current directory.
Now we'll add a new project to CVS:
cvs import -m "Initial Project" helloworld snl start
This creates a new project in CVS called helloworld, containing all files in the current directory (yes - none at all). The import has a log message "Initial Project". The last two items are a vendor tag and a release tag. These are useful when you are importing sources from a third party (like a library), but are required on the import command line. I use these items consistently; snl is the vendor (me) and start seems a suitable tag for the project head.
This command doesn't actually set up the current directory as a working directory. For that, you need to check out the project to the Working Directory.
Note that there is one other element to creating modules in CVS, and that's defining them in the modules file. I recommend you look in cvshome.org[2]for that - it's really out of scope here.
Whenever you want to work on a project - whether you've just imported it, or have worked on it previously - you need to check out the module. In this case, we check out the module so we can add files to it. Note that the default path for the module is the repository name - in this case helloworld - and CVS will create a directory called this as a child of the current directory if it doesn't exist.
cvs co helloworld
This command will create the helloworld directory, within which there will be a CVS directory. This is where CVS keeps local information about the working project. Don't mess with files in here!
Now we need to add files to the project. Here's where it gets interesting.
Now that you have a working directory, you can add files, and put them under version control from the outset. First off, create a new file called hello.cpp in the helloworld directory.
When you do anything in CVS that changes the repository, a log entry is added to the history for that file. This log can be echoed in your text files using keywords that CVS replaces with the log entry. For example:
$Author$ $Date$ $Log$
Will get replaced by your login name, the current date and the current history for the file. Put this in the top of the new file now, and save it.
Now we'll add it to the repository.
cvs add hello.cpp
You don't need to put a log message to add because it doesn't change the repository. As with all commands which potentially change the repository, you must commit your changes.
This is the opposite of the cvs co command:
cvs ci -m "Added file to the project" hello.cpp
If you don't specify a -m argument to ci, CVS will bring up your favourite text editor for you to add a log message. The history of a file can be a very useful thing to track, and leaving this blank would render it useless.
Another feature of CVS is the ability to query the current project to see what needs to be done. This is performed by the update function, and this is just an overview of what it can do.
Create a new file called hello.h. Leave it blank for the moment.
Open up hello.cpp, and write the hello, world! Program. For the purposes of this exercise, make it this one (it is deliberately in error - it doesn't have to compile yet!):
#include iostream.h void main() { cout << "Hello, World\n"; }
Now type: cvs -n update
Note the order of arguments: -n is an option to CVS itself, not to the update function of CVS (which has a number of its own options). This command checks the current project for things that need to be done to bring the repository (or the project itself) up to date. In this case, you should see something like:
cvs update: Updating . ? hello.h M hello.cpp
This output is CVS code for "I don't know what hello.h is. hello.cpp has been Modified". So, to bring the repository in line, we need to add hello.h and check in both files. Firstly, add the new file. Now run the same update command again. This time, it should look like:
cvs update: Updating . A hello.h M hello.cpp
Which means "hello.h has been added locally, and needs to be committed".
We can check in the entire project at once by specifying no files (CVS will by default recurse into any subdirectories you may have, and check in any modified files):
cvs ci -m "Added new header file. Added hello, world program to the main unit"
Having done this, running the same update function again should report no files out of line.
The update function is also used to replace the local working copy with a repository copy. For example, if you've made changes you want/need to lose, or you want a specific (old) version of a file.
cvs update -C
will retrieve the latest clean copy of a file from the repository, overwriting locally modified files.
The update function is also used to merge two revisions of a file. This is useful when you operate more than one branch (which we discuss briefly later).
CVS does not check to see what kind of file you are adding. What it may do, regardless of file name, extension etc. is change the line endings to match some local convention. CVS always stores files with linefeed only (UNIX style). This conversion is almost guaranteed to corrupt any binary file such as an image or database file.
Secondly, keyword substitution takes place, unless you explicitly switch it off. This one is less likely to occur in most cases, but it can be the cause of corrupted files.
Because CVS doesn't check for file types, you need to tell it the difference between different files. This is done at add time.
cvs add -k b filename
This specifies two things, that the file is binary, and that no keyword substitution is to take place. There are other options; see the manual[2].
When it's time to release the program, you need to be able to mark the current sources. This is so that at any time, you can go back and get the project as it was at that point in time. This is very important in software development. What we want to do is to name the files in the current project as a release. To do that, we use a tag.
Have a look first at the status of the helloworld project:
cvs status
If we've both followed the instructions so far, it should report that hello.cpp is revision 1.2 and hello.h is revision 1.1. These numbers really only mean something to CVS - they don't refer to a release number or anything - they are the revision numbers used by CVS to manage the file revisions.
What we want is to mark this project now to be a particular version.
Tagging the current project is simple. Deciding on tag names is less easy - a bit like naming variables in a program. The reason is that a tag can be anything that starts with an alpha character (upper or lowercase letter) and doesn't contain certain characters like '.'.
Deciding on a naming convention early is a good goal. I use the following:
v1_1ft2 v1_2rc2 v1_3
Where ft means Field Test and rc means Release Candidate. I haven't patented it ;-)
To tag a project, use the tag function:
cvs tag v0_1ft1 .
Note the trailing '.' to indicate the current directory.
The main reason for tagging is so that you can go back to a specific revision - a known working copy - of a project. If you're familiar with Microsoft's Visual Source Safe, you may recognise this as labelling.
Imagine you've been working on the helloworld project for a little while, and a bug is reported in version 0.1 Release Candiate 1. Imagine also you're currently in the middle of work on 0.1rc2, and you can't reproduce the bug in the current version.
You need to check out the code for 0.1rc1 and work on that. The check-out option (see Getting The Project Out) is used with the -r flag:
cvs co -r v0_1ft1 helloworld
will get the project. You may also wan to check it out to a different directory than the module name; CVS uses the module name as the parent directory by default, but it doesn't care if you want a different name. Try:
cvs co -r v0_1ft1 -d helloworld_0_1ft1
To see what tags a project has, you need to check out the HEAD revision (the current revision), and look at the status with the verbose option:
cvs status -v
Now if you can fix the bug, but it changes code in the current release (rc2), you have a conflict. You may need to branch the project and work in a clean-room.
Even on a so-called single-user project, it can be useful to branch the project, so you can work on a "clean" copy.
For example, say you have written a program, and released it for a field test (where users get to break the program). At the same time, you want to work on a new version - the next full release - but you know that the field-test will produce bugs and problems with this one. You have three options:
-
Hold off on the new release until after this full release has gone ahead. Spin your wheels until the field tests are complete...
-
Start a new project in the repository (a new working directory, the whole thing). A bit of a waste of effort, unless the new release is an entire re-write.
-
Branch your project under CVS and work on both in tandem. When field testing is complete, you can merge changes occurring back to the main project.
If you've already decided on (1) or (2), you can skip the next bit...
Using the example given above, and our helloworld project, let's examine what a branch is, and how it works.
I assume you've performed the actions in the last section - Release Time - and you have a tag on the current project of v0_1ft1. A branch is a bit like a tag, it's a named version of the project. What it gives you over a normal tag is the ability to perform changes on the branch without polluting the main trunk. Merging code back into the main trunk is a controlled process, generally seen as the most complex and error-prone aspect of branching.
To create the branch, check out the (possibly tagged at an earlier stage - see above) project. Now create the branch tag:
cvs tag -b v0_1ft1_fix1
Now look at the output from:
cvs status -v
You'll see a new tag, and that it is a branch, and what version it represents. The new version will (should) have a .2 appended to the old revision (the one from which the branch was created).
As with newly imported projects, the current directory still represents the main trunk working copy. To work on the branch, you must check it out. Since we're still working on the main trunk in tandem, we'll check out the branch to its own directory:
cd .. cvs co -r v0_1ft1_fix1 -d helloworld_0_1ft1_fix1 helloworld
Change to the helloworld_0_1ft1_fix1 directory and look at the status. Note the section labelled "Sticky Tag". It has the current branch tag next to it. What this means is that any changes you make to the repository here (for this working copy) will happen only to the revision indicated by that tag.
Change the program to look like this:
#include iostream int main() { std::cout << "Hello, World\n"; return 0; }
Check it in. Now go back to the main trunk directory (../helloworld) and change the program to look like this:
#include iostream.h #include string void main() { std::string message = "Hello, World!"; cout << message << "\n"; }
and check it in. We now have two independent branches of development, and the changes in each overlap.
CVS is pretty handy at merging branches. If for example you made changes on the branch to one file, and there were changes on the main trunk but not to that file, CVS will merge all the changes for you automatically. Similarly, if changes in a single file are in different portions, CVS will try to merge automatically.
In our case, however, the changes conflict with each other. Let's see what CVS does with them:
cvs update -j v0_1ft1_fix1
The -j option to the update function tells it the branch name from which to merge. CVS tells you it's retrieving the branch and the revision the branch was made from, and merging the differences into the current revision. It should also tell you there are conflicts.
Look at the status of hello.cpp. Now open up hello.cpp in your favourite editor.
Ignoring for now any conflicts in the keyword expansions at the top of the file (assuming you used them), here are the two files side by side:
// main truck #include <iostream> #include <string> void main () { std::string message = "Hello World!" cout << message << "\n"; } // fix1 branch #include <iostream> void main () { cout << "Hello World\n"; return 0; }
Here's what the merged file should now look like:
#include <iostream> int main() { hello.cpp std::string message = "Hello, World!"; cout message "\n"; ======= std::cout << "Hello, World!\n"; return 0; 1.2.2.1 }
CVS successfully auto-merged the first three lines. If only those changes had been made on the branch, we would not have a conflict.
However, CVS was unable to merge the main body of the program. We have to do this manually. We have arrows pointing left, and the file name. This represents the main trunk file (the file being merged into), followed by a horizontal line, and then the text of the right hand file (the branch), with arrows pointing right and the tag name of the branch.
This should be enough information to determine what the merged file should look like; we need some text from both sides of the merge:
#include iostream int main() { std::string message = "Hello, World!"; std::cout << message << "\n"; return 0; }
We can now save this file.
During the merge, CVS saved our existing working copy of any files changed as a result of the merge. In this case hello.cpp was changed. The existing copy will be called something like .#hello.cpp.1.3. If needed you can always replace the new merged file with the old one, and re-merge if necessary.
However, in this case, we're happy with the merge, and so can commit the changes to the repository.
cvs ci -m "Merged Fix1 branch into the development trunk"
Merging branches is generally not as easy as that! It requires care and patience, but is ultimately more efficient than other methods of handling the same problems.
You can't use CVS effectively without access to the manual[2]. It is a pretty complicated program, but all full-featured tools are. With luck, this guide will help get you started.
personally started using CVS with WinCVS[3] on Windows NT and 98. Many people disparage it, because it tries to put a Visual Source Safe-like GUI on top of CVS. (I actually think it's more like WinMKS, but there you go!). It's still a useful tool, and you can use the command line access to perform tasks it won't or can't do for you. When you run an interactive command, WinCVS also tells you what CVS command it has issued, and the direct output from it, which can be very useful in understanding the CVS command line.
Now go ahead and play with it. Use the repository we created here as a play-area, a kind of sandbox you can muck about it without fear of getting in too much of a mess.
When you're confident, start using it to manage your own projects.
[CVS] CVS--Concurrent Versions System v1.11.1p1 AKA "The Cederqvist"
Notes:
More fields may be available via dynamicdata ..