Apple Developer Connection
Advanced Search
Member Login Log In | Not a Member? Contact ADC

Version Control with CVS on Mac OS X

The Concurrent Versions System (CVS) is a powerful open-source tool for source code maintenance. It is provided on the the Xcode Tools CD that accompanies Mac OS X or as a part of the Xcode development environment online. This article covers some of the most commonly used features of CVS, with emphasis on using CVS with static and interpreted web files (HTML, PHP, Perl, etc).

CVS Overview and Terminology

Before you get started, take a moment to review a few common CVS terms. A repository is a place where CVS keeps master copies of all the files it knows about, along with information about the histories of those files. A project or module is a collection of files that belong together, such as all the files that make up a particular web site. To checkout a project is to make a local copy of all the related files so you can make and test changes on your own without affecting what others see. To commit is to save your changes back to the repository, where they are available to others (or to yourself, in case you ever want to roll back to a previous version).

Getting Started

CVS comes with Mac OS X; so you don’t need to install anything new if you’ve installed the programs on the Xcode Tools CD or downloaded Xcode from http://developer.apple.com/tools/.

To get started, create a new directory for your CVS repository as the root user:

liz@localhost:~> sudo sh
Password:
root@localhost:~> mkdir /usr/local/cvsrep

Now look at the default owner and group for the directory you just created:

root@localhost:~> ls -ld /usr/local/cvsrep
drwxr-xr-x  3 root  wheel  58 Dec 9 21:30 /usr/local/cvsrep

As you see above, the new directory is owned by the root user and belongs to the wheel group. You want to make sure you can write to the directory even when you aren’t working as the root user. You probably want to make your CVS directory available to your system’s primary user. In my Mac OS X installion, liz was the first account created, and by default Mac OS X made liz a member of wheel and other administrative groups:

root@localhost:~> groups liz
staff wheel admin

The instructions that follow work on most single-user Mac OS X installations. You can also read more about multi-user CVS below.

First you need to change the permissions to the newly created directory so that the system’s defualt user can read and write to the directory.

root@localhost:~> chmod g+w /usr/local/cvsrep
root@localhost:~> exit
liz@localhost:~>

Now create the CVSROOT shell environment variable. This variable tells CVS where its repository lives. (Note: if you don’t know what shell you’re using, it’s probably tcsh.)

In bash or sh, the command is:

liz@localhost:~> export CVSROOT=/usr/local/cvsrep

Or, in tcsh:

liz@localhost:~> setenv CVSROOT "/usr/local/cvsrep"

I also like to change the CVSEDITOR environment variable to be my favorite text editor, emacs. If you’re happy with vi (the default), you can ignore this variable. If you’re new to the Unix world, you might want to go with the simple editing program, pico. Later in this article you’ll see how to use CVS with BBedit.

liz@localhost:~> export CVSEDITOR=emacs

Or, in tcsh:

liz@localhost:~> setenv CVSEDITOR "emacs"

Now you’re ready to set up your CVS repository with the cvs init command:

liz@localhost:~> cvs init
liz@localhost:~>

Once you log out of the current terminal window, you’ll lose the CVSROOT environment variable. That means the next time you try to use CVS, you’ll see an error message much like this:

cvs import: No CVSROOT specified!  Please use the `-d' option
cvs [import aborted]: or set the CVSROOT environment variable.

There are three ways to work around this error. You can manually set the CVSROOT (and the optional CVSEDITOR) variables when you want to use CVS. Or you can add the -d /usr/local/cvsrep switch to your CVS commands. The easiest option in the long run, however, is to insert the export or setenv command above into your shell startup file. See the manpage for your default shell with, for example, man sh or man tcsh if you don’t know how to set up a startup file.

Creating Your First Project

Once you have initialized CVS, you can create a new project. First, create a directory called “myproj” under your home directory:

liz@localhost:wherever> cd ~
liz@localhost:~> mkdir myproj
liz@localhost:~> cd myproj

Now you can create a new file as part of your project. For test purposes, create an HTML file called ~/myproj/index.html with some basic text:

<html><head><title>First</title></head>
<body bgcolor="white">

<h1>First File in First CVS Project</h1>

</body>
</html>

Now you can add this new project to your CVS repository using the import command:

liz@localhost:myproj>cvs import -m "My First Project"
  myproj vendor-tag start
  
N myproj/index.html

No conflicts created by this import
liz@localhost:myproj>

Creating Working Copies of a Project

Now that you have created a new CVS project containing a single file, you (and others) can use the checkout command to make working copies of the project. You can make changes to this project and test them. Then, once you are satisfied, commit the changes to the main repository.

A sensible place to check out copies of web projects is into your individual web directory. By default, this directory is /home/username/Sites/. Assuming you have Apache running on your Mac OS X machine, files placed in this directory can be viewed in a web browser via http://localhost/~username.

This is how I check out a copy of a new project to my ~/Sites directory:

liz@localhost:~> cd ~/Sites
liz@localhost:Sites> cvs checkout myproj
cvs checkout: Updating myproj
U myproj/index.html

liz@localhost:Sites> cd myproj

You can now view this page from your browser:

Browsesr Screen Shot

At this point, you can delete the original project file from your home directory (~/myproj/index.html)if you like. The file is now in the CVS repository, and you can check it out whenever you want.

Making Changes

Now you can make some changes to your local copy of myproj/index.html, the one in the ~/Sites directory. For example, you might add a line of text:

<html><head><title>First</title></head>
<body bgcolor="white">

<h1>First File in First CVS Project</h1>

I have added some text to the file.

</body>
</html>

Once you’ve made the changes, commit the updated file to the repository. CVS expects you to supply a comment with your commit that summarized the work you’ve done. If you do not supply a comment using the -m flag, CVS will dump you into a text editor (see CVSEDITOR above) and expect you to type and save a comment there before proceeding.

liz@localhost:myproj> cvs commit -m "updated index.html" index.html 
Checking in index.html;
/usr/local/cvsrep/myproj/index.html,v  <--  index.html
new revision: 1.2; previous revision: 1.1
done

Adding and Deleting Files

When it’s time to add new files to your project, you can create the files in the the ~/Sites/myproj/ directory and import them using the CVS add command:

liz@localhost:myproj> cvs add another.html
cvs add: scheduling file `another.html' for addition
cvs add: use 'cvs commit' to add this file permanently
liz@localhost:myproj>

liz@localhost:myproj> cvs commit -m "added another.html"
cvs commit: Examining .
RCS file: /usr/local/cvsrep/myproj/another.html,v
done
Checking in another.html;
/usr/local/cvsrep/myproj/another.html,v  <--  another.html
initial revision: 1.1
done

Want to remove a file from a project? First delete the file itself, then tell CVS that it’s gone:

liz@localhost:myproj> rm another.html
liz@localhost:myproj> cvs remove another.html
cvs remove: scheduling `another.html' for removal
cvs remove: use 'cvs commit' to remove this file permanently

liz@localhost:myproj> cvs commit -m "removed another.html"
cvs commit: Examining .
Removing another.html;
/usr/local/cvsrep/myproj/another.html,v  <--  another.html
new revision: delete; previous revision: 1.1
done
liz@localhost:myproj>

Restoring a Previous Version

If you’d like to check out a project as it existed some time in the past, you can use the -D flag. For example, to get the project as it existed at a certain date and time, create a fresh directory (or move or delete your existing project files), and run cvs checkout -D date project. For example:

liz@localhost:Sites> cvs checkout -D "2001-11-29 18:00" myproj
cvs checkout: Updating myproj
U myproj/index.html

Working with Multiple Projects

Your CVS repository can contain as many projects as your hard disk’s capacity will allow. Just create new projects with the cvs import command. When you check out a version of a project, cd into your local project directory and run your CVS commit, add, remove, etc. commands from there. Actually, there’s no reason you can’t have more than one CVS repository as well. You can specify different repositories using the -d flag or by changing the CVSROOT environment variable. All the examples in this article, however, assume a single local repository.

Using CVS with BBEdit 6.5

Traditionally, Mac internet developers have used BBEdit to develop files locally, then FTP-ed them to a remote server. But BBEdit 6.5 runs natively on Mac OS X and it supports integrated Unix scripting. This means you can write simple shell scripts to access CVS without leaving BBEdit.

To run a Unix shell script from within BBEdit 6.5 or greater, you will use the menu option labeled #! (often pronounced “shebang”). First, create a directory to hold your scripts.

liz@localhost:~> mkdir scripts

Now you can create a file in ~/scripts called, for example, “commit”. If you already know you’ll be working with multiple projects, you might want to give the file a more specific name, like “myproj_commit”. The file would have the following contents:

#!/bin/sh
cd ~/Sites/myproj
cvs -d /usr/local/cvsrep commit

To run your new commit script, select Run File from the shebang menu in BBEdit. The first time you run it, you’ll have to tell BBEdit where the script is. In the future, BBEdit will remember recently executed scripts and display them for you.

Using CVS Branches

Branches can be useful for even the simplest web applications. For example, you may want to release one version of a site while you’re working on the next version. With CVS branches, you cannot only keep track of multiple releases, you can easily reference your various versions by name, making bug fixes and code maintenance much simpler. You can create branches by using the -b option to the cvs tag command.

liz@localhost:myproj> cvs tag -b phase_one 
cvs tag: Tagging myproj
T myproj/index.html

Now that you’ve tagged the project for the first time, you need to take one extra step: release your current working directory and checkout a new copy of the project, specifying the new branch name (the -d flag deletes the files currrently in use):

liz@localhost:Sites> cvs release -d myproj
You have [0] altered files in this repository.
Are you sure you want to release (and delete) directory `myproj': y

liz@localhost:Sites> cvs checkout -r phase_one myproj
cvs checkout: Updating myproj
U myproj/index.html

Publishing Code with CVS

If you look at your project directory under ~/Sites/myproj, you’ll see that your source files aren’t the only things there. There are also CVS administrative files and directories:

liz@localhost:myproj>ls -l
total 8
drwxr-xr-x  5 liz  staff  264 Dec 11 23:23 CVS
-rw-r--r--  1 liz  staff  125 Dec 12 17:19 index.html

These are necessary while you’re working on a project, but you don’t really want them to be part of your published website. To obtain a copy of a project without any CVS extras, you can use the CVS export command. For example, here’s how I would export my web project to my Apache server’s main document root. Note: I’m specifying the date of now because export expects either a branch tag or a date.

liz@localhost:~> cd /Library/WebServer/Documents
liz@localhost:Documents> cvs export -D "now" myproj
cvs export: Updating myproj
U myproj/index.html

liz@localhost:Documents> cd myproj
liz@localhost:myproj>ls -l
total 8
-rw-r--r--  1 liz  staff  186 Dec 12 20:29 index.html

Using CVS with Multiple Developers

CVS will let more than one person work on the same file at the same time. In fact, if two people make changes to the same file and commit them, CVS will try to accommodate both changes. If CVS cannot accomodate your changes (for example, two people have both changed the same area of the same file), it will let you know that you need to hand-edit the file — and probably communicate with the other developer — in order to resolve the conflict.

If many people access the same CVS repository on the same machine, it helps to create a new CVS login group so that all developers can have write access to the repository.

To create a new group under Mac OS X, launch the NetInfo Manager application from /Applications/Utilities, and follow these steps:

  • Click the lock icon and enter your password in order to make changes.
  • Click the word “groups” in order to display groups.
  • You are going to make a duplicate copy of an existing group and re-name it. Start by clicking on “daemon”.
  • Click the “duplicate” icon (the image of two folders).
  • Click on the name of the newly created duplicate group and change it to “cvs-user”.
  • Change the group id (gid) to some number that hasn’t been used yet, like 501.
  • Highlight the “users” field in the lower pane of the window.
  • For each user you would like to associate with this group, select “insert value” from the directory menu. Double-click each newly inserted value and change the name to the short name of your desired user.

When you’re done, you should see a window something like this (”liz” and “jay” are the two users I have associated with the “cvs-user” group):

NetInfo Screen Shot

Once you’re done, you can go back to the shell to confirm that the users “liz” and “jay” are members of the cvs-user group:

liz@localhost:~groups liz
staff wheel admin cvs-user

liz@localhost:~>groups jay
staff cvs-user

Finally, change the group ownership of your CVS repository so that the members of your new cvs-user group can access it:

liz@localhost:~>sudo chgrp cvs-user /usr/local/cvsrep
Password:

CVS has several useful commands for multi-developer collaboration. These include cvs status, which shows information about files you’ve changed that have also been changed by others; cvs release, which tells CVS that you’re done working on a project; and cvs update, which updates your working copy of a project to reflect changes made by others. For more information on these and other commands see the suggestions for further reading.

Using CVS Across a Network

So far, you’ve learned how to deal with repositories and projects stored on your local Mac OS X machine. CVS will also let you to get repositories residing on other machines on the Internet, providing they’re running one of a few services.

CVS pservers

You can checkout files from a remote repository by accessing a public CVS pserver (password server). Many pservers, especially those for open-source projects, allow read-only access under the username “anonymous” with the password “anonymous”. You can access a remote server (specified with the -d flag) by first using the login command, followed by checkout.

liz@localhost:Sites> cvs -d :pserver:anonymous@
  remote_server:/path/to/repository login
CVS password: anonymous

liz@localhost:Sites> cvs -d :pserver:anonymous@remote_server:
  /path/to/repository checkout module_name

There is a significant disadvantage to using this method for anything but anonymous access, however. CVS pservers transmit the login name and password unencrypted. The module files are transmitted in the clear as well. This means that any unfriendly folks who may be listening in on your network traffic have full access to not only your login information, but your data.

There are ways to set up cvs-only usernames and passwords (see suggestions for further reading for directions), but it can be much nicer, in my view, to use CVS entirely through an encrypted connection. For this, you can use ssh (secure shell).

Secure CVS via ssh

It’s simple to use CVS entirely over ssh. If you set the environment variable CVS_RSH (CVS Remote Shell) to “ssh”, you can run CVS commands securely across the Internet, just as though you were running them locally. Mac OS X 10.1 users have ssh and scp (secure copy) available by default. If you’re using an earlier version of Mac OS X, you can visit http://www.openssh.com/ to obtain ssh.

If you’d like to use ssh without having to type your password every time (useful if you are accessing CVS via a shell script in BBEdit, for example), you’ll want to create a public/private key pair with the ssh-keygen command. The advantage of doing this is that your scripts will be able to run without human intervention. The disadvantage is that anyone who can access your account on your local Mac OS X box will also be able to access those remote servers which have stored your public key.

The exact commands you enter for the ssh-keygen command depend on whether you are using ssh version 1 or ssh version 2. If you are using version 1, enter the following at the shell. (Note: If you hit return when you are asked for a passphrase you will end up with an empty passphrase.)

liz@localhost:~> cd  ~/.ssh
liz@localhost:> ssh-keygen -t rsa1
Generating public/private rsa1 key pair.
Enter file in which to save the key (/Users/liz/.ssh/identity):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /Users/liz/.ssh/identity.
Your public key has been saved in /Users/liz/.ssh/identity.pub.
The key fingerprint is:

liz@localhost:.ssh>ls
identity     identity.pub

And for version 2, do the following

liz@localhost:~> ssh-keygen -t dsa
Generating public/private dsa key pair.
Enter file in which to save the key (/Users/liz/.ssh/id_dsa):
Created directory '/Users/liz/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /Users/liz/.ssh/id_dsa.
Your public key has been saved in /Users/liz/.ssh/id_dsa.pub.
The key fingerprint is:

liz@localhost:~>cd ~/.ssh
liz@localhost:~> ls
id_dsa     id_dsa.pub

Once you have created your private and public keys, you need to place your public key on the remote host in a place where ssh and scp can recognize it. Use ssh to connect to the remote host(s) on which you want to publish your projects. Then add the contents of your local ~/.ssh/identity.pub or id_dsa.pub file to a file in your remote ~/.ssh directory called “authorized_keys” (if you don’t have one already, you can create it).

Whether or not you’ve placed your public key on the remote server, you can run CVS securely through ssh:

liz@localhost:Sites> export CVSROOT=":ext:your_login@
your_remote_server:/path/to/repository"
liz@localhost:Sites> export CVS_RSH="ssh"
liz@localhost:Sites> cvs checkout remote_module_name

Conclusion and Suggestions for Further Reading

In this article you’ve seen how to create CVS repositories and projects, add and remove files, make changes and commit them, and access CVS on servers other than your own. There are many more features in CVS for you to explore, and I encourage you to follow the links below to learn more.

For more information about CVS, visit http://www.cvshome.org/. Of particular interest is the Official CVS Manual. On your local OS X system, you’ll also find the standard CVS documentation, when you type “man cvs”. Finally, there’s also the handy paperback CVS Pocket Reference, written by Gregor N. Purdy, and published by O’Reilly.