Using CVS on OSX with MacCVS Pro


Time to geek out.

I’m posting this mostly so that when I (inevitably) break this and need to reinstall it, I’ll remember how — because it’s tweaky and convoluted and I’m guaranteed to forget exactly half of how it’s done.

But maybe somebody else out there will find this useful; certainly there were enough contradictory howto’s out there to get me tied up in knots for most of a day; here’s one more.

Let’s start off by saying that if you’re not me, you probably don’t want to do what I’m doing here:

1) CVS is old and busted. All the cool kids switched to Perforce or Subversion years ago. But Subversion requires a full Apache2 installation, which I didn’t want to get into. And the only Perforce client I could find was butt-ugly, confusing, and looked like it would get in the way of my work. (One of the problems with software that’s only intended to be used by developers is that the user interfaces always suck.)

2) You already have it, anyway. OSX Tiger (version 10.4) already has CVS pre-installed for local, single-user access, which in theory is exactly what I want. But I’ve gotten used to a CVS GUI client which I really like — but it’s built with the assumption that you’ll be connecting to a remote server; it doesn’t know how to cope with an unprotected CVS installation.

3) pserver is insecure. The various forms of protected CVS connection are SSH, RSH, or pserver. SSH and RSH are secure goodness. pserver apparently does all sorts of dumb things like sending your password around in plaintext, mooning police vehicles while passing them at high speeds, and POSTING TO USENET IN ALL CAPS. It’s the wrong choice. But it’s the first one I was able to get working, and I’m a solo developer doing this on a local, dynamic-ip network, so I’m going to live dangerously.

So, bearing those disclaimers in mind, here’s how to do it anyway:

Setting up pserver

In order to use CVS with password authentication, we need to start the pserver service. Do this by creating a file in /etc/xinetd.d/ called “cvspserver”, containing this black magic:

service cvspserver
  disable = no
  protocol = tcp
  socket_type = stream
  wait = no
  user = root
  server = /usr/bin/cvs
  server_args = -f --allow-root=/usr/local/cvs pserver
  passenv =
 groups = yes

I won’t even pretend I understand why this does what it does, but it does. If you have a firewall running, you need to open port 2401. Restart xinetd using

sudo kill -HUP `cat /var/run/`

Now that pserver is (hopefully) running, edit your .tcshrc file (assuming you’re using the tcsh shell, which I am, so there) so you can use it from the command line without having to specify the repository every time:

setenv CVSROOT :pserver:daniel@

(substituting your own username, of course.) Start a new shell, so that setenv will take effect, then test it out using cvs login. If everything is working well so far, it should ask for your password and not spit out any error messages.

Setting up the repository

Now we need to set up the CVS repository, where the raw data for your various projects will be stored. This can be any directory on the machine; /usr/local/cvsroot or /usr/local/cvs seem to be popular choices; we’re using /usr/local/cvs/. If you want to be smart about security, you’d create a new user named “cvs” and create this directory with that user — but if you were worried about security you wouldn’t be following these instructions, so just use your own account for it. Then tell CVS it exists:

cvs -d /usr/local/cvs/ init
This’ll create a CVSROOT directory inside your repository, which needs some tweaking before you can use it. The most important thing is that you’ll need to create a password file there, so pserver can use it:

htpasswd -c passwd username
That’ll create a file named passwd containing your CVS username followed by your encrypted password. (I haven’t messed about with multiple usernames, but you can put more than one account in this password file, or even have multiple logins act as the same “real” account, e.g.


would allow both me and my dog to log in to CVS, but would then treat both of us as if we were really user “cvs”.)

While you’re here, edit the “cvswrappers” file to help CVS differentiate between binary and text files (the comments in that file explain how to do this better than I could). It’s also, apparently, necessary to edit the “config” file to turn on the LockDir option, because leaving it at its default value causes Bad Things Of Some Sort to happen. (I’m blindly trusting some randomly googled HOWTO files for this, so it may be voodoo chicken-bone waving, but it sounds like a good idea anyway.) If you’re worried about security, turn off SystemAuth here, so that CVS won’t try to use real system usernames and passwords, but only the pseudo-users in your passwd file. (But if you were worried about security, you wouldn’t be following these instructions.) I’ll worry more about this as soon as Frank learns how to type.

Other things I haven’t successfully tried yet, but want to note for later, is that you can create files called “readers” and “writers” in the CVSROOT directory to control who is allowed to read and write to the repository.

Creating a new module

For each project you want to manage with CVS, you’ll need to import a module into CVS, by cd’ing to the project’s directory and using

cvs import -m "Message" modulename vendorname start
modulename is apparently the only one of these options that is actually meaningful; “Message” will be the first log comment for each of the project files, start will be the first CVS tag, and vendorname will be, as far as I can tell, ignored completely (but must still be included.) When you later on check this module out of CVS somewhere else, it will be created in a subdirectory named whatever you used for modulename.

(Don’t bother trying to use the GUI for this. It doesn’t work. Create new modules from the command line only.)

Actually Using CVS

We can now retreat from scary command line territory to soft plushy GUIland.

Open up MacCVS, create a new session file, save it wherever you like, and edit the session settings:

Under “Checkout and Update”, your “Local Tree Directory” should be where you want the working copy to be stored. Your “default module” should be the module name you used earlier.

Under “Remote Host”, the server hostname should be “localhost” or or your local IP address or however else you feel like referring to your local machine. The CVS root should be /usr/local/cvs. Change the authentication method to Password, give it your username and password, and click OK.

You should now be able to use “checkout default module” to pull a copy of all its files into your working directory, make modifications, check files back in, and all that lovely CVS goodness.


  • Once you’ve checked out a module, don’t move any directories around; there are hidden CVS directories in every one of them that depend on staying where they’ve been put. If you accidentally move one of them, your checkins and checkouts will appear to work but will actually be put in the wrong places. Meaning, basically, you’re officially hosed. So don’t do that.

  • Either the client or CVS itself, not sure which, doesn’t cope well with spaces in file names. So don’t use them.