CVS FAQ

Here are a few things I've learned working with CVS that I haven't found documented anywhere on the net.

These FAQs assume you have a working understanding of CVS. If you do not, you may refer to Open Source Development With CVS or CVS--Concurrent Versions System v1.11.6 to familiarize yourself first.

Branching and Merging in CVS

Let's say we want to import the excellent database abstraction layer ADOdb version 3.60 into our CVS repository:


$ wget http://phplens.com/lens/dl/adodb360.tgz
$ tar xvzf adodb360.tgz
$ cd adodb
$ cvs import -m 'Imported ADOdb 3.60' adodb PHPLENS_COM R3_60

Now that we've imported it, we can remove the directory and the download file:

$ cd ..
$ rm -fr adodb adodb360.tgz

Now, we'll check it out from CVS and fix a bug we found:

$ cvs checkout adodb
$ cd adodb
$ vi adodb.inc.php
$ cvs commit -m "Fixed bug #12345: Replace doesn't use native REPLACE command, if available"
$ cd ..
$ rm -fr adodb

Now, version 3.72 has been released, and we want to merge it in to our repository:

$ wget http://phplens.com/lens/dl/adodb372.tgz
$ tar xvzf adodb372.tgz
$ cd adodb
$ cvs import -m 'Imported ADOdb 3.72' adodb PHPLENS_COM R3_72

This command completed successfully, but reported the following:

1 conflicts created by this import.
Use the following command to help the merge:

cvs checkout -j -jR3_72 adodb

Again, we can now delete the imported directory, and download file:

$ cd ..
$ rm -fr adodb adodb372.tgz

And checkout as instructed above.

$ cvs checkout -jR3_60 -jR3_72 adodb

Let's manually resolve any conflicts that were found, if any:

$ vi adodb.inc.php

Now, let's commit our 3.60 changes into 3.72:

$ cvs commit -m 'Resolved conflicts after upgrading from ADOdb 3.60 to 3.72'

And finally, if we want, we can remove our directory:

$ rm -fr adodb

Including third party code in CVS

Let's say we want to start a project, and include the excellent database abstraction layer ADOdb into our CVS repository.

First, let's import our new project, and name the CVS module _myproject:

$ cd original_code
$ cvs import -m 'Initial import' _myproject INTERNAL R0_01
$ cd ..

The leading underscore is not required. It's just a convention I use to indicate a module that is included within another module.

Next, we'll import ADOdb and name the CVS module _adodb:

$ wget http://phplens.com/lens/dl/adodb372.tgz
$ tar xvzf adodb372.tgz
$ cd adodb
$ cvs import -m 'Imported ADOdb 3.72' _adodb PHPLENS_COM R3_72
$ cd ..

Now, we want to associate these two modules, such that when we check out the _myproject module, we get the _adodb module as well. We do that thru the CVSROOT/modules file, which we'll now check out:

$ cvs commit CVSROOT
$ cd CVSROOT

We'll add the following lines to the modules file:

adodb -d adodb _adodb
myproject &_myproject &adodb

Next, we'll commit these changes:

$ cvs commit -m 'Added myproject and adodb to modules file'
$ cd ..

The first line tells CVS that adodb is an alias for the _adodb module. But when we check out adodb, the _adodb module's files will be created in a directory named adodb.

The second line tells CVS that myproject is an alias for both the _myproject module and the adodb alias. When we check out myproject, the _myproject module's files will be created in a directory named myproject, and the _adodb module's files will be created in a directory named adodb, within the myproject directory.

Here's an example:

$ cvs checkout myproject
$ cd myproject
$ ls -l
total 11688
-rw-rw-r-x 13 ross cvsusers 4096 Jul 29 23:11 myproject.php
drwxrwxr-x  7 ross cvsusers 4096 Aug  9 11:14 adodb

Why not just include the ADOdb code directly in the myproject module? There are two reasons:

1. You want to share the _adodb module between two or more modules, yet have only one instance of the ADOdb code in CVS. We can easily add a second project by adding the following to our CVSROOT/modules file:

project2 &_project2 &adodb

If we want to store ADODb in project2's lib/adodb directory, we would instead add:

libadodb -d lib/adodb _adodb
project2 &_project2 &libadodb

2. You can easily upgrade the ADOdb code when a new version is released. See Upgrading third party code in CVS for details.

3. You cleanly define what is internal code, and what is external code. While this may not matter on smaller projects, it's importance will increase as projects get bigger.

References:

Open Source Development With CVS: The modules File
CVS--Concurrent Versions System v1.11.6: Administrative files

Upgrading third party code in CVS

Let's say we want to import the excellent database abstraction layer ADOdb version 3.60 into our CVS repository:


$ wget http://phplens.com/lens/dl/adodb360.tgz
$ tar xvzf adodb360.tgz
$ cd adodb
$ cvs import -m 'Imported ADOdb 3.60' adodb PHPLENS_COM R3_60

Now that we've imported it, we can remove the directory and the download file:

$ cd ..
$ rm -fr adodb adodb360.tgz

Now, we'll check it out from CVS and fix a bug we found:

$ cvs checkout adodb
$ cd adodb
$ vi adodb.inc.php
$ cvs commit -m "Fixed bug #12345: Replace doesn't use native REPLACE command, if available"
$ cd ..
$ rm -fr adodb

Now, version 3.72 has been released, and we want to merge it in to our repository:

$ wget http://phplens.com/lens/dl/adodb372.tgz
$ tar xvzf adodb372.tgz
$ cd adodb
$ cvs import -m 'Imported ADOdb 3.72' adodb PHPLENS_COM R3_72

This command completed successfully, but reported the following:

1 conflicts created by this import.
Use the following command to help the merge:

cvs checkout -j -jR3_72 adodb

Again, we can now delete the imported directory, and download file:

$ cd ..
$ rm -fr adodb adodb372.tgz

And checkout as instructed above.

$ cvs checkout -jR3_60 -jR3_72 adodb

Let's manually resolve any conflicts that were found, if any:

$ vi adodb.inc.php

Now, let's commit our 3.60 changes into 3.70:

$ cvs commit -m 'Resolved conflicts after importing ADOdb 3.72'

And finally, if we want, we can remove our directory:

$ rm -fr adodb

References:

Open Source Development With CVS: Tracking Third-Party Sources (Vendor Branches)

Putting /etc in CVS

Here's how I put my /etc directory under CVS:

First, since some files in /etc are readable only by root, I need to login as root:

$ su -

Next, I set my $CVSROOT environment variable to be /var/lib/cvs:

# export CVSROOT=/vvar/lib/cvs

Next, I check out my CVSROOT directory:

# cd ~
# cvs co CVSROOT
# cd CVSROOT

Next, I'll add an option to instruct CVS to treat all *.gz files as binary:

# echo "*.gz -k 'b'" >>cvswrappers

Next, I'll add an option to instruct CVS to ignore all standard CVS keywords ($Id$, $Header$, etc.), and only expand $Etc$:

# echo "tag=Etc=CVSHeader" >>options
# echo "tagexpand=iEtc" >>options
# echo options >>checkoutlist
# cvs add options
# cvs ci -m 'Instruct CVS to only expand $Etc$'

This previous step is optional, but I do it as many files in /etc already have the CVS keywords $Id$'s, and I don't want existing keywords expanded and the resulting changes checked in to CVS.

Note: These options are a relatively new addition to CVS and may not work in your version.

I change to the /etc directory and create a .cvsignore containing a list of files that change often by the system:

# cd /etc
# echo adjtime >.cvsignore
# echo ioctl.save >>.cvsignore
# echo ld.so.cache >>.cvsignore
# echo motd >>.cvsignore

As the name of the file suggests, this instructs CVS to ignore these files when importing or updating.

You can list any files you want to .cvsignore, including sensitive files such as /etc/passwd. Personally, I like to keep these files in CVS, so I secure the repository instead.

Next, I add non-text files to .cvswrappers. This instructs CVS to not perform keyword expansion ($Id$ to $Id: ... $) on these files:

echo "* -k 'b'" >/etc/alternatives/.cvswrappers
echo "*.p12 -k 'b'" >/etc/ipsec.d/.cvswrappers
echo "*.dat -k 'b'" >/etc/pcmcia/cis/.cvswrappers
cd /etc/terminfo
for i in * ; do echo "* -k 'b'" >$i/.cvswrappers ; done

I then import the /etc directory into a new CVS module, named etc:

# cvs import -m 'Initial import for the /etc directory' etc hostname release-dd-mmm-yyyy

If you want to have several systems' /etc directories in a single CVS repository, you could name the modules hostname_etc, or anything you want.


Next, I restrict the $CVSROOT/etc directory so only the root user has access:

# cd /var/lib/cvs/etc
# chown root.root .
# chmod go-rwx .

This is because there are sensitive files, such as /etc/passwd, in the /etc directory and CVS will add them to the repository with different rights then the rights they have in /etc.

Next, I check out a copy of the new etc module:

# cd /root
# cvs checkout etc

Then, I set the date on all the files to 1/1/1970:

# find etc | xargs touch -t 197001010000

Next, I backed up the /etc directory, just in case:

# tar czf /root/etc.tgz /etc

Then, I copy over just the CVS directories from /root/etc directory to the /etc directory:

# cd /root/etc
# cp --interactive -pRuv . /etc

The -u option instructs cp to only copy files that are new or have changed. As we set the date of all the files to 1/1/1970, the only files copied over will be the CVS directories.

I added the --interactive option to make sure that nothing is overwritten without asking me first. In truth, nothing was overwritten.

Lastly, I check in any changes to CVS. The only changes at this point will be from files containing CVS keywords (i.e., $Etc$):

# cd /etc
# cvs ci -m 'Keyword substitutions (i.e., $Etc$ after initial checkout)'

And finally, I removed the temporary directory, as it's no longer needed.

# rm -fr /root/etc