BOLTS : Click to return to MacEdition homepage

All your versions are belong to you: An introduction to CVS

By Mark Dalrymple (, October 16, 2002

Feedback Farm

Have something to say about this article? Let us know below and your post might be the Post of the Month! Please read our Official Rules and Sponsor List.


Want to dig even deeper? Post to the new MacEdition Forums!

Now stick this in your repository!

OK, now that we have a repository, we need something to put into it. As a contrived example, how about keeping to-do lists under version control. So, first make a directory to hold the lists, and move into it.

% mkdir to-do
% cd to-do

Then put a couple of files in there. I’m gonna create them on the command line, but you can use any editor (such as TextEdit) to make them.

% cat > personal.txt
	* vacuum pool
	* feed cats
	* catch Beanie-n-Cecil marathon on cartoon network

% cat > job.txt
	* upgrade G3 tower to Jaguar
	* get next article to Poruka
	* finish Networking course notes

(For both of the above files, enter CTRL-d to exit them when finished.)

Now to get this to-do project into CVS. We use cvs import on the directory (note: you should still be in the to-do directory).

% cvs -d ~/cvsroot import -m "initial revision" to-do bork start

	N to-do/job.txt
	N to-do/personal.txt
	No conflicts created by this import

Taking apart this command:

cvs : run the cvs command

-d ~/cvsroot : use ~/cvsroot as the repository to store this project

import : import a directory structure into the repository as a new project

-m "initial revision" : the comment put on the first revision of the file

to-do : the name of the project

bork : this is known as the “vendor tag” when importing a big bolus of someone else’s code; just put your name here

start : this is the “release-tag” – since this really isn’t a new release of a product, but the start of a new project

CVS doesn’t modify the original directory, so we’ll need to check out this project so we can use CVS’ features.

Go up a level, to to-do’s parent directory–

% cd ..

–and move aside to-do so we can check out a fresh copy. Don’t nuke this yet, just in case something went wrong. I’ve used computers long enough to be more than a little paranoid.

% mv to-do to-do-save

And now check out the project:

% cvs -d ~/cvsroot checkout to-do

	cvs checkout: Updating to-do
	U to-do/job.txt
	U to-do/personal.txt

The “U” indicates that it’s updating the file, which just means the file is being copied out of the repository. Go into the to-do directory and look around.

% cd to-do
% ls

	CVS/	job.txt	personal.txt

There’s our two text files – and a directory called CVS. That’s where CVS puts bookkeeping information. You’ll see a CVS directory in every directory that’s “under CVS control,” where CVS is maintaining histories of files.

So, say I actually do something on my to-do list, like I finish upgrading my G3 tower to Jaguar. I’d edit the file job.txt with whatever editor is handy (TextEdit, BBEdit, ProjectBuilder, Emacs, etc). I would then delete the first line and get:

	* get next article to Poruka
	* finish Networking course notes

And then I discover I misspelled the Editor-in-Chief’s name. I’d better fix that – and I’ll add a new item to the list. My job.txt now looks like this:

	* get next article to Porruka
	* finish Networking course notes
	* start outlining Rendezvous chapter

Now let’s assume I walk out and go to dinner. I come back and think, “I was editing one of these files. I should check it in and save my changes, but what file was it?” CVS will tell me what files are changed, and also what files are out of date with respect to the repository (say if someone checked in a newer version of a file). Here’s the command to do that:

% cvs -n update

	cvs update: Updating
	M job.txt

Some things to note about the command. First is we don’t need to specify -d ~/cvsroot anymore. CVS is smart enough to look in the CVS directory for that information. The other is the -n flag. That is a global flag meaning “don’t change anything on the disk.” The cvs update command, if run without -n, will go to the repository and fetch files that are newer. That can lead to bad consequences if you’re testing against one set of files, do a cvs update to see what changed, and you suddenly have a new set of identical files to test against.

So, now take a look at the output again:

	M job.txt

The “M” means that the file has been modified locally. This tells me that job.txt is the file of interest.

Next question, “What did I do to that file?” You can ask CVS to take the difference between this file and the version it’s based on from the repository:

% cvs diff job.txt

	Index: job.txt
	RCS file: /Users/bork/cvsroot/to-do/job.txt,v
	retrieving revision
	diff -r1.1.1.1 job.txt
	< * upgrade G3 tower to Jaguar
	< * get next article to Poruka
	< * get next article to Porruka
	> * start outlining Rendezvous chapter

That’s kinda cryptic, but once you read a couple of diff reports like this you can figure out what’s going on (or use a CVS GUI Tool). The angle bracket pointing to the left means a line that was removed. The angle bracket pointing to the right means a line that was added. A changed line is shown as being both removed and added. The first number before each block is the line number where the change takes place. Here it is, annotated:

1,2c1on or about line 1
< * upgrade G3 tower to Jaguarremove this line
< * get next article to Porukaremove this line
---but also
> * get next article to Porrukaadd this line
3a3on or about line 3
> * start outlining Rendezvous chapteradd this line

Making it stick

OK, I can live with that. Time to make the change permanent.

% cvs commit -m "finished G3 tower. Added rendezvous task" job.txt

	Checking in job.txt;
	/Users/bork/cvsroot/to-do/job.txt,v <-- job.txt
	new revision: 1.2; previous revision: 1.1

cvs commit takes your changes and puts them in the repository. Notice the two version numbers listed above. The older revision was 1.1. The one with my new changes is revision 1.2. You can come back later and look at any particular revision you want.

One of the nice things about using a version control system is that it provides a safety net. Let’s say that we accidentally wiped out the files in our to-do directory:

% rm *.txt
% ls


Oops! All gone. Since the files are in CVS, we can ask for them back:

%% cvs update

	cvs update: Updating . cvs update: warning: job.txt was lost
	U job.txt
	cvs update: warning: personal.txt was lost
	U personal.txt

If you had any uncommitted changes, they of course would have been lost, but it’s nice knowing you don’t have to start from scratch on a file or try to figure out how to restore from backups.

We can also take a look at a file’s history. cvs log shows us a ton of information:

% cvs log job.txt

	RCS file: /Users/bork/cvsroot/to-do/job.txt,v
	Working file: job.txt
	head: 1.2
	locks: strict
	access list:
	symbolic names:
	bork: 1.1.1
	keyword substitution: kv
	total revisions: 3; selected revisions: 3
	revision 1.2
	date: 2002/08/30 01:44:19; author: bork; state: Exp; lines: +2 -2
	finished G3 tower. Added rendezvous task
	revision 1.1
	date: 2002/08/29 21:01:42; author: bork; state: Exp;
	branches: 1.1.1;
	Initial revision
	date: 2002/08/29 21:01:42; author: bork; state: Exp; lines: +0 -0
	initial revision

The most interesting parts are the highlighted lines – the information about the changes made. You can see when the change was made, by whom, how many lines were added and removed, and what the commit message is. Always be sure to use good commit messages so that folks who come after you know what happened.

OK, I’ve gotten some to-dos in my project. The spousal overunit reminds me it’s time to go to the grocery store. I think I’ll add my grocery list.

% cat > grocery-list.txt
	* cat food
	* cat litter
	* chocolate soy milk
	* salsa
	* waffles

(enter a CTRL-d to close the file)

And now to tell CVS to start taking care of the file:

% cvs add grocery-list.txt

	cvs add: scheduling file `grocery-list.txt' for addition
	cvs add: use 'cvs commit' to add this file permanently

After we add, we need to commit the file too.

% cvs commit -m "initial revision" grocery-list.txt

	RCS file: /Users/markd/cvsroot/to-do/grocery-list.txt,v
	Checking in grocery-list.txt;
	/Users/markd/cvsroot/to-do/grocery-list.txt,v <-- grocery-list.txt
	initial revision: 1.1

Alternatives to CVS


Some of the original CVS developers are taking a shot at making a better CVS, addressing some of the shortcomings that CVS has and adding some new ideas. It's still under development, but it looks like they have something usable.


This is the version control system the Linux kernel team uses, geared for very distributed teams. It uses change sets that let you roll in or out changes (say edits to a dozen files) in one atomic operation rather than a dozen different operations. Currently only the command-line tools (not the GUI) work on Mac OS X.

One last bit of information. You can store binary files (such as images and sounds) in CVS. There are two gotchas associated with binary files. CVS can store the changes to text files in a very compact form so dozens of revisions to text files won’t take up too much disk space. CVS doesn’t do that with binary files. If you have a 20 megabyte sound you’re storing, and you commit 5 changes, that will be 100 megabytes of space consumed. The other is that CVS has some magic keywords that it can expand in text files to add some additional information for someone reading the file (like the last check-in time). If those keywords (like $Id: $) happen to appear in your binary file, CVS will happily expand those, most likely corrupting the file. The way to work around this is to add the -kb flag when you cvs add the image files:

% cvs add -kb badger-frolic.jpg

Here ends the brief overview of what you can do with CVS. Luckily there’s a huge user community out there, lots of documentation and lots more tutorials if you find this stuff interesting and/or useful.

Mark Dalrymple ( has been wrangling Macs and Unix systems for entirely too many years. He’s currently building the Core Mac OS X Programming course for the Big Nerd Ranch, covering many topics in Unix and Mac OS X programming, including CVS.

E-mail this story to a friend

Talkback on this story!