Version Control - CVS Procedures, Database Upgrades, Etc.

by Andrew Piskorski Last Revised: 18 Aug 2001

Articles and Tutorials

Books and Reference Manuals

Other Useful CVS Links

Companion Pages

This is intended to be a policy document and cheatsheet for doing version control and release management on a website, principally via use of CVS. While it contains extensive "howto" information, it is not intended to be either a reference manual or general tutorial on CVS - read the above articles first.

We will also try to make this document generally applicable, by placing any information highly specific to our own project in a separate companion document.

Overview

For web development, project version and configuration control falls roughtly into three main bins: CVS, Database Upgrades, and Other. This document originally dealt only with the CVS piece of the puzzle (hence the filename), but will now touch on all three.

CVS changes:

If you've read Ron's article, above, you know there are three basic ways to use CVS for Web Development version control: One Branch, Two Branches, and Many Branches.

So the first question is which method of using CVS do we use? My past experience says to use either One Branch or Many Branches. For simple projects, One Branch will often be simplest, fastest, and best. For some large, complicated, or long-term projects - particularly after they already have a Production server launched and in regular use - the Many Branches method may be useful.

As for the the Two Branches method, I don't recomend it. While I have not run a project using it, it strikes me as just as much work as Many Branches, without all the benefits.

Also, if you do use the Many Branches you'll probably want to avoid mixing in the Two Branches method as well. I have mixed the two, and it can work fine, but the extra complexity usually isn't worth dealing with. Keep things simple if you can.

Note that nothing about CVS locks you into any of the above methods. We can and will use the "other methods" should circumstances warrant it.

Non-CVS changes:

Any and all changes not handled via CVS go in the upgrade.sql file. This is usually database changes but may be anything at all. In other words, if making the change happen involves anything other than just doing a cvs update, it should probably be in the upgrade.sql file.

Admitedly, shoving things like "Go to /acs-admin/apm/ and use the web UI, install ACS package X, mount it on URL Y, and also do Z, because I was to lazy to figure out how to have a script set that up automatically" into upgrade.sql is something of a kludge. But at least that way all needed non-cvs changes are noted in one place.

You will probably see a natural change in your use of the upgrade.sql file as your site moves from developing something brand new, to launching and then maintaining a production site. When you start a brand new project and do not have any Production site launched, whenever you make changes to your data model you will want to simply drop and re-create your package, so you won't have much genuine data model upgrade stuff in there. Later on, necessity may force you to make all data model changes, even on Dev, via this upgrade script, as that's what you'll need to do on Production. And in fact, I first came up with this "put all upgrades into upgrade.sql" scheme in exactly that situation (the Muniversal project, which I worked on for roughly 10 months, roughly 8 months of which were after we'd launched a Production site).

Whatever you do, don't make changes to your data model one way on Dev, and then expect to come up with a whole different way of implementing those changes on your existing Production site! That's really asking for trouble.

In general, when making all data model changes as alterations to the live database via upgrade.sql on Muniversal, I found that it was not unusual to run into some problems when upgrading Staging, but most of the time the upgrade to Production went quite smoothly. In other words, one pass of testing was usually enough to work the bugs out.

(Note: If there exists a tool that can version and diff live relational data models as well as CVS can version and diff text files, all without losing any data in the tables, I'd like to hear about it!)

More on the One Branch method

We use one or two UNIX machines, three AOLservers, and three database users. All development takes place on the Development server. Developers check their changes into CVS continually. The development machine also hosts the staging server, with its own AOLserver and Oracle user. The staging server has two uses: (1) as a testing area for new code, and (2) as a testbed for updating the production server.

When a developer has a stable version of a file, he does:

$ cvs-tag-staging.sh myfile.tcl

and the file is tagged as ready for the staging server. (Note: this tags the last committed version, but doesn't commit your current changes.) When any developer does:

$ cvs-up-staging.sh

all files tagged for Staging are moved to the Staging server. (The cvs-tag-staging.sh and cvs-up-staging.sh scripts are located in "/web/mysite/bin/".)

You can also use the cvs-not-tag.tcl script to tell you which files have not been tagged for Staging.

With this process, tagged code is moved continually from dev to staging. Any non-CVS changes that need to go to Staging along with the code will be done and noted in the upgrade.sql file by the person moving the code to Staging.

Watchdog is installed on the staging server, so any errors are sent via email to the development team.

When it is time to do a Production release, we tag all the files on Staging with a Production release tag, and then update Production.

So the process of releasing changes from Staging to Production is very similar to the process for releasing from Dev to Staging, but it differes in these important ways:

More on the Many Branches method

TODO: [Basically, there's not that much to it, as you use only CVS commands excluseively - I never found the need for any anciallary shell scripts as I did witht the One Branch method. See the articles and books listed above.]

Individual vs. Shared Checkouts

TODO:

[Interpreted and Database-backed (argues for share CVS also makes the stuff you just imported onto the vendor branch rev. 1.1.1 on the trunk. But if you already have a revision 1.1.23 of the foo.sql file, say, then importing a revised foo.sql onto the vendor branch doesn't do anything to the foo.sql on the trunk - you have to merge and commit first.

So to import code that needs to be merged in with existing code, the steps are:

  1. Import new stuff onto vendor branch.
  2. Checkout a new working copy of the trunk with -kk.
  3. Merge in changes from vendor branch, in that working copy.
  4. Resolve any conflicts.
  5. Commit.
  6. Go to any existing checkout that you normally use for your Development work, and update to get the new stuff you just committed.

Note that:

Here are some examples:

Upgrading from ACS 4.2beta to 4.2:

$ cvs import -b 1.1.1 -m "Importing ACS 4.2 (not beta)." mysite ArsDigita acs-4-2-R20010417
$ cd ~ ; cvs checkout -kk -d mysite-tmp mysite
$ cd mysite-tmp
$ cvs up -kk -j acs-4-2-R20010417
$ cvs commit
$ cd /web/mysite-dev/
$ cvs -q up

Importing Extop's (Joe Bank's <jbank@arsdigita.com>) enhanced ACS Developer Support package:

$ cvs import -b 1.1.3 mysite/packages/acs-developer-support Extop Extop-production-update-20010518
$ cd ~
$ cvs checkout -kk -d mysite-tmp mysite
$ cd mysite-tmp/packages/acs-developer-support
$ cvs up -kk -d -j Extop-production-update-20010518
$ cvs commit -m "Merged in changes from Extop-production-update-20010518"
$ cd /web/mysite-dev/packages/acs-developer-support
$ cvs up -d

Importing ACS-Notification tweaks from Proteome:

$ cvs import -b 1.1.5 mysite/packages/acs-notification Proteome proteome-final
$ cvs checkout -kk -d mysite-tmp mysite
$ cd mysite-tmp/packages/acs-notification
$ cvs up -kk -d -j proteome-final
$ cvs commit
$ cd /web/mysite-dev/packages/acs-notification
$ cvs up -d

Importing AOLserver nsodbc driver v. 1:

$ cvs import -b 1.1.11 aol3-src/aolserver/nsodbc AOLserver nsodbc_v1
$ cd ~/tmp
$ cvs checkout -kk aol3-src
$ cd aol3-src/aolserver/nsodbc/
$ cvs up -kk -d -j nsodbc_v1
$ cvs commit -m "Merged in nsodbc_v1 changes from www.aolserver.com."
$ cd /web/aol3-src/aolserver/nsodbc/
$ cvs up -d

Occasionally you may get just one or two extensively changed files from someone. Here's such an example. Below, I start with onle the single new version of the table-display-procs.tcl file in the current directory. Then:

$ cvs import -b 1.1.5 mysite/packages/acs-tcl/tcl Proteome proteome-final
$ cd ~ ; cvs checkout -kk -d mysite-tmp mysite
$ cd mysite-tmp/packages/acs-tcl/tcl
$ cvs up -kk -d -j proteome-final table-display-procs.tcl
$ cvs commit table-display-procs.tcl
$ cd /web/mysite-dev/packages/acs-tcl/ ; cvs up table-display-procs.tcl

Branching

In Ron's article, he describes three methods for managing releases for client projects - One Branch, Two Branches, and Multiple Branches. Multiple Branches is the most complex of the three, so this discussion is going to assume a project using primarily the Multiple Branches method, with a bit of the Two Branches method thrown in for flavor.

Remember that in CVS, branching is really just a special kind of tagging. The (only?) difference, is that a "branch tag" is placed against a special revision number of the file - see the Cederqvist manual about this

Steps to make a new branch to Staging/Production:

  1. Make sure all fixes on the current Staging branch are committed, and merged back into the Dev trunk. See the separate section below on this.
  2. Place the root-of tag on the trunk.
  3. Place the branch tag.
  4. Checkout the new branch onto Staging.
  5. Do whatever non-CVS things you need to do - data model upgrades, restarting AOLserver, etc.
  6. Repeat the previous two steps on Production.

Steps 2 - 4 are the meat of the braching work. We give examples of them here:

Let's place the two tags. Since we're always taging the trunk with a root-of tag, we should always use this root-of tag to tell CVS where to put the branch tag. E.g.:

$ cd /web/myproject-dev
$ cvs tag root-of_release-2000-08-30
$ cvs tag -r root-of_release-2000-08-30 -b release-2000-08-30_branch

If you want to use rtag instead of tag, it's almost exactly the same, except that you also have to specify the module or repository root, in this case 'cvs-module':

$ cd /web/myproject-dev
$ cvs rtag root-of_release-2000-08-30 cvs-module
$ cvs rtag -r root-of_release-2000-08-30 -b release-2000-08-30_branch cvs-module

Now, checkout the new branch onto Staging:

$ cd /web/myproject-staging
$ cvs update -kk -d -r release-2000-08-30_branch

Finally, when you're ready to upgrade Production to the new Staging branch, you use the exact same update command:

$ cd /web/myproject
$ cvs update -kk -d -r release-2000-08-30_branch

Merging fixes from the Staging branch back to the Development trunk

Steps to merge changes from Staging back to Dev:

  1. Make sure everything on Staging is committed.
  2. Try to make sure ever on Dev is committed too - it is less confusing this way (but see below).
  3. Tag Staging with an appropriate fix-n tag. This identifies your batch of fixes.
  4. Optionally, check out a separate copy of the Dev trunk into your own temporary directory (see below).
  5. Use cvs update or cvs checkout to merge the files from Staging to Dev.
  6. Resolve any conflicts, and commit the modified files on Dev.
  7. Do whatever non-CVS things you need to do - data model upgrades, restarting AOLserver, etc.

The basics of merging

You use the -j (join) switch with a tag name to actually make the merge happen. When you merge the first set of fixes on a Staging branch back to Dev, you only need one -j tag, to tell CVS what to join into the current working directory. E.g.:
$ cd /web/myproject-dev
$ cvs up -R -kk -j release-2000-08-08_fix-1
When you are merging your second set of fixes, you need to use two -j tags, to tell CVS "merge changes from between these two tags". If you don't, CVS will try to merge the fix-1 changes in again, causing lots of bogus conflicts. So for the second and subsequent batches of fixes, do e.g.:
$ cd /web/myproject-dev
$ cvs up -R -kk -j release-2000-08-08_fix-1 -j release-2000-08-08_fix-2

Checking out a separate working copy of Dev instead

Now, when you want to merge fixes back from the Staging branch to the Dev trunk, there will often be un-committed files on the trunk. And since the -n switch in a command like:
$ cd /web/myproject-dev
$ cvs -nq up -R -kk -j release-2000-08-08_fix-1 -j release-2000-08-08_fix-2
does not show you everything that the command would do for real without the -n switch to stop it, you can't get a list of the files that will have conflicts prior to actually doing it.

It might be nice to have a script which uses 'cvs status -v' to generate a list of all files which DON'T have the same revision number on two tags.

Now, if you don't mind not knowing what files are going to be affected by the merge, you can simply go ahead and use the above command without the -nq, resolve all conflicts, and commit.

But, if you're not sure, you probably don't want to merge directly from Staging back to Dev like the above. Instead, checkout a new working copy of the Dev trunk and do the merge there. (This means that you will be merging the fixes from Staging back into the version in the repository on Dev, not the - possibly modified - files in the Dev working copy.)

You can do the checkout of a new Dev working copy and the merge from Staging all in one step. E.g.:

$ cd ~/
$ cvs -d ls.arsdigita.com:/cvsweb co -kk -j root-of_release-2000-08-30 -j release-2000-08-30_fix-1 cvs-module > cvs-co.log

(Note that the first -j tag is probably unnecesary.)

or:
$ cd ~/
$ cvs -d ls.arsdigita.com:/cvsweb co -kk -j release-2000-08-08_fix-1 -j release-2000-08-08_fix-2 cvs-module
Then, fix any conflicts, commit as usual when you're done, and delete the extra working copy.

[It might be nice to have a script to conveniently parse out all the conflict messages from the output of the above merge command, to give a concise list of the conflicts that need to be addressed. But in practice, I just do 'cvs -nq up' to find the files with conflicts, write down the list, and then resolve them one at a time.]

Finally, remember, your fixes are now all in the CVS repository for the Dev trunk now, but you still need to check them out into the primary Dev working area. E.g., do something like:

$ cd /web/myproject-dev
$ cvs up
And if necessary, then resolve any conflicts and commit. (You should never have conflicts at this stage unless you had un-checked in changes in your Dev tree.)

Merging from Dev to Staging - without creating a new branch

Note that it can be confusing to be doing merges back and forth from Dev to a Staging branch. I recomend attempting to avoid having to merge from Dev to Staging. Instead, try to only create a new branch from Dev to Staging, and then merge fixes back from Staging to Dev. This will make your life simpler.

Steps:

For example:
$ cd /web/myproject-dev
$ cvs tag -R release-2000-09-07
$ cd /web/myproject-staging
$ cvs -q up -d -R -kk -j release-2000-09-07
TODO: Note that there may be something wrong or non-optimal with the above, because when I did it, I got lots of totally bogus but rather complicated conflicts. E.g., doing:
$ cd /web/myproject-staging
$ find . \( -name "*.tcl" -o -name "*.sql" \) -exec grep '<<<' {} /dev/null \;
found several conflicts in each of the following files:
./www/trading/order-enter-n-4.tcl
./www/doc/sql/myproj-upgrade-db.sql
./www/doc/sql/myproj-orders_pb.sql
./www/doc/sql/myproj-orders_ph.sql
Beware of sticky tags! [TODO: Consider writing more about the dangers and uses of sticky tags.]

The -A will remove all sticky tags, but although this may fix your im And we can again go to the branch checked out on Staging, and confirm that everything is back the way it started:

$ cd /web/staging/www/bannerideas
$ cvs stat more.tcl 
===================================================================
File: more.tcl          Status: Up-to-date
   Working revision:    1.1.1.2
   Repository revision: 1.1.1.2 /cvsweb/myproj/www/bannerideas/more.tcl,v
   Sticky Tag:          release-2000-08-30_branch (branch: 1.1.1.2.4)
   Sticky Date:         (none)
   Sticky Options:      (none)


atp@piskorski.com
$Id: cvs-conventions.html,v 1.4 2001/08/29 07:08:54 andy Exp $
cross branches Nothing could be simpler. Just place a tag against certain files, rather than every file in the working copy or rrepository. E.g.:
$ cd /web/myproject-dev/tips/wrapper
$ cvs tag release-tips-holidays-2000-08-28 Makefile libtipswrap.so tipswrap.c tipswrap.h tipswrap.o
Then go merge the files as usual. E.g., continuing the above example (merging from Dev to Staging):
$ cd /web/myproject-staging/
$ cvs up -kk -j release-tips-holidays-2000-08-28
Finally commit, etc. as usual.

Repository Surgery - or, renaming a file

In CVS, the only normal way to rename or move a file is to use cvs to remove and add it. This is annoying, as CVS can now longer retrieve the full history of the file for you automatically. The useful stopgap to avoid, involving "repository surgery", is discussed in this BBoard article, which links to how to do it.

How do you move a branch tag to a different revision of a file?

Note: This section is also a BBoard article.

After branching from our Development trunk, I discovered that I'd tagged the wrong revision of a bunch of files. Now, maybe I could have ditched the whole branch and started over, but what if I'd modified a bunch of files on the branch? Discarding the branch then could have been problematic. To fix this, what I really wanted to do was simply take those 6 or 7 files, and move the branch tag to the correct, older revision. So how to do that? Here's how:

First some background: In the project I work on, before branching we always tag the trunk with a root-of_ tag, and when we branch we attach a suffix of _branch to all branch tags. Bug-fix merges from the Staging/Production branch back to the Development trunk are given tags with _fix-n in the tag name. So in the following examples, you'll see tags that look like this:

tag name, e.g. explanation
root-of_release-2000-08-30 Root of tag, placed on the trunk, immediately before doing the branch
release-2000-08-30_branch Branch tag for release-2000-08-30
release-2000-08-30_fix-1 First batch of fixes merged back from the Staging branch to the Dev trunk
release-2000-08-30_fix-2 Second batch of fixes merged back from the Staging branch to the Dev trunk

OK, so you want to move a branch tag from one revision of a file to another. How to do it?

Short answer:

$ cd /web/dev
$ cvs admin -Nroot-of_release-2000-08-30:1.13            www/doc/sql/stuff.sql
$ cvs tag -l -r root-of_release-2000-08-30 -b tmp_branch www/doc/sql/stuff.sql
$ cvs admin -Nrelease-2000-08-30_branch:tmp_branch       www/doc/sql/stuff.sql
$ cvs tag -d tmp_branch                                  www/doc/sql/stuff.sql
$ cd /web/staging
$ rm www/doc/sql/stuff.sql
$ cvs up www/doc/sql/stuff.sql
In the above example, to make it work for you the only things you'd need to change are the name of the file (stuff.sql), the names of the root-of_release-2000-08-30 and release-2000-08-30_branch tags, and the revision number of the file (1.13) which you want to move the branch to. The rest is boilerplate.

Before trying the above, you'll want to read up on the cvs admin -N command, but basically, it moves tags, much like mv does for unix files.

Also note that you probably don't need to use cvs admin -N at all - you should be able to achieve the same effect with multiple uses of cvs tag. But I find cvs admin -N clearer and more concise.

Long answer:

Here I'm going to walk through the testing I did in order to figure out the above Short Answer. Also, note that I've removed the irrelevent portions of the output from some commands.

First thing, is to dig around my CVS tree and find an unused file to experiment with. /web/dev/www/bannerideas/more.tcl looks perfect. Here is its initial status:

$ cd /web/dev/www/bannerideas
$ cvs log more.tcl 
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
Working file: more.tcl
symbolic names:
        release-2000-08-30_branch:  1.1.1.2.0.4
        root-of_release-2000-08-30: 1.1.1.2
        acs-3-2-0: 1.1.1.2
Notice the magic branch number of 1.1.1.2.0.4 - the extra zero in the revision number is how CVS actually keeps track of branches. The cvs log commands shows this magic branch number, but cvs status -v does not - which is why I use cvs log throughout these examples.

Let's try moving the the branch back to an older revision of the file:

$ cvs admin -Nrelease-2000-08-30_branch:1.1.1.1.0.2 more.tcl
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
cvs [server aborted]: revision `1.1.1.1.0.2' does not exist
OK, that didn't work, because apparently we have to "create" the branch revision of this file first. If this was a file we cared about, we'd want to make sure we keep our root-of and branch tags in sync, so let's do it that way:
$ cvs admin -Nroot-of_release-2000-08-30:1.1.1.1 more.tcl
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
done
$ cvs log more.tcl 
symbolic names:
      release-2000-08-30_branch:  1.1.1.2.0.4
      root-of_release-2000-08-30: 1.1.1.1
      acs-3-2-0: 1.1.1.2
$ cvs tag -l -r root-of_release-2000-08-30 -b test-cvs-2000-08-30_branch more.tcl
T more.tcl
$ cvs log more.tcl 
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
symbolic names:
      test-cvs-2000-08-30_branch: 1.1.1.1.0.2
      release-2000-08-30_branch:  1.1.1.2.0.4
      root-of_release-2000-08-30: 1.1.1.1
      acs-3-2-0: 1.1.1.2
OK, the revision number that we need (1.1.1.1.0.2) has been created.

Now we'll actually move the branch tag to the older revision of the file:

$ cvs admin -Nrelease-2000-08-30_branch:1.1.1.1.0.2 more.tcl
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
done
$ cvs log more.tcl 
symbolic names:
      test-cvs-2000-08-30_branch: 1.1.1.1.0.2
      release-2000-08-30_branch:  1.1.1.1.0.2
      root-of_release-2000-08-30: 1.1.1.1
      acs-3-2-0: 1.1.1.2
Now we go to the Staging server where the release-2000-08-30_branch branch was previously checked out, and we see that the older 1.1.1.1 version of the file has magically become the initial revision on the branch:
$ cd /web/staging/www/bannerideas
$ cvs status more.tcl 
===================================================================
File: more.tcl          Status: Needs Patch
   Working revision:    1.1.1.2
   Repository revision: 1.1.1.1 /cvsweb/myproj/www/bannerideas/more.tcl,v
   Sticky Tag:          release-2000-08-30_branch (branch: 1.1.1.1.2)
   Sticky Date:         (none)
   Sticky Options:      (none)
Success! At this point, we could simply delete more.tcl and then do cvs update more.tcl to get the 1.1.1.1 rev that we want. But since this is just a test file, we don't want to do that.

Instead, let's put things back the way they were before we started:

$ cd /web/dev/www/bannerideas
$ cvs admin -Nroot-of_release-2000-08-30:1.1.1.2 more.tcl
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
done
$ cvs admin -Nrelease-2000-08-30_branch:1.1.1.2.0.4 more.tcl
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
cvs [server aborted]: revision `1.1.1.2.0.4' does not exist
Whoops! Moving the root-of tag back where it started worked, but moving the branch tag back failed. Apparently, branch revision numbers have no existance separate from tags placed against them. So, when I moved the release-2000-08-30_branch tag from rev. 1.1.1.2.0.4 to rev. 1.1.1.1.1.2, rev. 1.1.1.2.0.4 simply ceased to exist.

Probably, I should have put a placeholder tag on rev. 1.1.1.2.0.4 to mark my spot, before I moved the release-2000-08-30_branch tag. But since I didn't, let's use the "create a branch revision of this file" trick again:

$ cvs tag -l -r root-of_release-2000-08-30 -b test-cvs-2_branch more.tcl
T more.tcl
But what revision number was acutally used? Let's find out:
$ cvs log more.tcl 
symbolic names:
      test-cvs-2_branch:         1.1.1.2.0.4
      release-2000-08-30_branch: 1.1.1.1.0.2
Ah, we see that the original 1.1.1.2.0.4 rev. number was indeed re-used, further confirming our guess that the magic branch revision numbers have no existance independent of branch tags.

Now, we move the branch back to the revision of the file where it originally started:

$ cvs admin -Nrelease-2000-08-30_branch:1.1.1.2.0.4 more.tcl
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
done
$ cvs log more.tcl 
symbolic names:
      test-cvs-2_branch:         1.1.1.2.0.4
      release-2000-08-30_branch: 1.1.1.2.0.4

Since we're done experimenting, let's get rid of the extra branch tags we created:

$ cvs tag -d test-cvs-2_branch          more.tcl
D more.tcl
$ cvs tag -d test-cvs-2000-08-30_branch more.tcl
D more.tcl
And we can again go to the branch checked out on Staging, and confirm that everything is back the way it started:
$ cd /web/staging/www/bannerideas
$ cvs stat more.tcl 
===================================================================
File: more.tcl          Status: Up-to-date
   Working revision:    1.1.1.2
   Repository revision: 1.1.1.2 /cvsweb/myproj/www/bannerideas/more.tcl,v
   Sticky Tag:          release-2000-08-30_branch (branch: 1.1.1.2.4)
   Sticky Date:         (none)
   Sticky Options:      (none)


atp@piskorski.com
$Id: cvs-conventions.html,v 1.4 2001/08/29 07:08:54 andy Exp $
cross branches Nothing could be simpler. Just place a tag against certain files, rather than every file in the working copy or rrepository. E.g.:
$ cd /web/myproject-dev/tips/wrapper
$ cvs tag release-tips-holidays-2000-08-28 Makefile libtipswrap.so tipswrap.c tipswrap.h tipswrap.o
Then go merge the files as usual. E.g., continuing the above example (merging from Dev to Staging):
$ cd /web/myproject-staging/
$ cvs up -kk -j release-tips-holidays-2000-08-28
Finally commit, etc. as usual.

Repository Surgery - or, renaming a file

In CVS, the only normal way to rename or move a file is to use cvs to remove and add it. This is annoying, as CVS can now longer retrieve the full history of the file for you automatically. The useful stopgap to avoid, involving "repository surgery", is discussed in this BBoard article, which links to how to do it.

How do you move a branch tag to a different revision of a file?

Note: This section is also a BBoard article.

After branching from our Development trunk, I discovered that I'd tagged the wrong revision of a bunch of files. Now, maybe I could have ditched the whole branch and started over, but what if I'd modified a bunch of files on the branch? Discarding the branch then could have been problematic. To fix this, what I really wanted to do was simply take those 6 or 7 files, and move the branch tag to the correct, older revision. So how to do that? Here's how:

First some background: In the project I work on, before branching we always tag the trunk with a root-of_ tag, and when we branch we attach a suffix of _branch to all branch tags. Bug-fix merges from the Staging/Production branch back to the Development trunk are given tags with _fix-n in the tag name. So in the following examples, you'll see tags that look like this:

tag name, e.g. explanation
root-of_release-2000-08-30 Root of tag, placed on the trunk, immediately before doing the branch
release-2000-08-30_branch Branch tag for release-2000-08-30
release-2000-08-30_fix-1 First batch of fixes merged back from the Staging branch to the Dev trunk
release-2000-08-30_fix-2 Second batch of fixes merged back from the Staging branch to the Dev trunk

OK, so you want to move a branch tag from one revision of a file to another. How to do it?

Short answer:

$ cd /web/dev
$ cvs admin -Nroot-of_release-2000-08-30:1.13            www/doc/sql/stuff.sql
$ cvs tag -l -r root-of_release-2000-08-30 -b tmp_branch www/doc/sql/stuff.sql
$ cvs admin -Nrelease-2000-08-30_branch:tmp_branch       www/doc/sql/stuff.sql
$ cvs tag -d tmp_branch                                  www/doc/sql/stuff.sql
$ cd /web/staging
$ rm www/doc/sql/stuff.sql
$ cvs up www/doc/sql/stuff.sql
In the above example, to make it work for you the only things you'd need to change are the name of the file (stuff.sql), the names of the root-of_release-2000-08-30 and release-2000-08-30_branch tags, and the revision number of the file (1.13) which you want to move the branch to. The rest is boilerplate.

Before trying the above, you'll want to read up on the cvs admin -N command, but basically, it moves tags, much like mv does for unix files.

Also note that you probably don't need to use cvs admin -N at all - you should be able to achieve the same effect with multiple uses of cvs tag. But I find cvs admin -N clearer and more concise.

Long answer:

Here I'm going to walk through the testing I did in order to figure out the above Short Answer. Also, note that I've removed the irrelevent portions of the output from some commands.

First thing, is to dig around my CVS tree and find an unused file to experiment with. /web/dev/www/bannerideas/more.tcl looks perfect. Here is its initial status:

$ cd /web/dev/www/bannerideas
$ cvs log more.tcl 
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
Working file: more.tcl
symbolic names:
        release-2000-08-30_branch:  1.1.1.2.0.4
        root-of_release-2000-08-30: 1.1.1.2
        acs-3-2-0: 1.1.1.2
Notice the magic branch number of 1.1.1.2.0.4 - the extra zero in the revision number is how CVS actually keeps track of branches. The cvs log commands shows this magic branch number, but cvs status -v does not - which is why I use cvs log throughout these examples.

Let's try moving the the branch back to an older revision of the file:

$ cvs admin -Nrelease-2000-08-30_branch:1.1.1.1.0.2 more.tcl
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
cvs [server aborted]: revision `1.1.1.1.0.2' does not exist
OK, that didn't work, because apparently we have to "create" the branch revision of this file first. If this was a file we cared about, we'd want to make sure we keep our root-of and branch tags in sync, so let's do it that way:
$ cvs admin -Nroot-of_release-2000-08-30:1.1.1.1 more.tcl
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
done
$ cvs log more.tcl 
symbolic names:
      release-2000-08-30_branch:  1.1.1.2.0.4
      root-of_release-2000-08-30: 1.1.1.1
      acs-3-2-0: 1.1.1.2
$ cvs tag -l -r root-of_release-2000-08-30 -b test-cvs-2000-08-30_branch more.tcl
T more.tcl
$ cvs log more.tcl 
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
symbolic names:
      test-cvs-2000-08-30_branch: 1.1.1.1.0.2
      release-2000-08-30_branch:  1.1.1.2.0.4
      root-of_release-2000-08-30: 1.1.1.1
      acs-3-2-0: 1.1.1.2
OK, the revision number that we need (1.1.1.1.0.2) has been created.

Now we'll actually move the branch tag to the older revision of the file:

$ cvs admin -Nrelease-2000-08-30_branch:1.1.1.1.0.2 more.tcl
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
done
$ cvs log more.tcl 
symbolic names:
      test-cvs-2000-08-30_branch: 1.1.1.1.0.2
      release-2000-08-30_branch:  1.1.1.1.0.2
      root-of_release-2000-08-30: 1.1.1.1
      acs-3-2-0: 1.1.1.2
Now we go to the Staging server where the release-2000-08-30_branch branch was previously checked out, and we see that the older 1.1.1.1 version of the file has magically become the initial revision on the branch:
$ cd /web/staging/www/bannerideas
$ cvs status more.tcl 
===================================================================
File: more.tcl          Status: Needs Patch
   Working revision:    1.1.1.2
   Repository revision: 1.1.1.1 /cvsweb/myproj/www/bannerideas/more.tcl,v
   Sticky Tag:          release-2000-08-30_branch (branch: 1.1.1.1.2)
   Sticky Date:         (none)
   Sticky Options:      (none)
Success! At this point, we could simply delete more.tcl and then do cvs update more.tcl to get the 1.1.1.1 rev that we want. But since this is just a test file, we don't want to do that.

Instead, let's put things back the way they were before we started:

$ cd /web/dev/www/bannerideas
$ cvs admin -Nroot-of_release-2000-08-30:1.1.1.2 more.tcl
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
done
$ cvs admin -Nrelease-2000-08-30_branch:1.1.1.2.0.4 more.tcl
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
cvs [server aborted]: revision `1.1.1.2.0.4' does not exist
Whoops! Moving the root-of tag back where it started worked, but moving the branch tag back failed. Apparently, branch revision numbers have no existance separate from tags placed against them. So, when I moved the release-2000-08-30_branch tag from rev. 1.1.1.2.0.4 to rev. 1.1.1.1.1.2, rev. 1.1.1.2.0.4 simply ceased to exist.

Probably, I should have put a placeholder tag on rev. 1.1.1.2.0.4 to mark my spot, before I moved the release-2000-08-30_branch tag. But since I didn't, let's use the "create a branch revision of this file" trick again:

$ cvs tag -l -r root-of_release-2000-08-30 -b test-cvs-2_branch more.tcl
T more.tcl
But what revision number was acutally used? Let's find out:
$ cvs log more.tcl 
symbolic names:
      test-cvs-2_branch:         1.1.1.2.0.4
      release-2000-08-30_branch: 1.1.1.1.0.2
Ah, we see that the original 1.1.1.2.0.4 rev. number was indeed re-used, further confirming our guess that the magic branch revision numbers have no existance independent of branch tags.

Now, we move the branch back to the revision of the file where it originally started:

$ cvs admin -Nrelease-2000-08-30_branch:1.1.1.2.0.4 more.tcl
RCS file: /cvsweb/myproj/www/bannerideas/more.tcl,v
done
$ cvs log more.tcl 
symbolic names:
      test-cvs-2_branch:         1.1.1.2.0.4
      release-2000-08-30_branch: 1.1.1.2.0.4

Since we're done experimenting, let's get rid of the extra branch tags we created:

$ cvs tag -d test-cvs-2_branch          more.tcl
D more.tcl
$ cvs tag -d test-cvs-2000-08-30_branch more.tcl
D more.tcl
And we can again go to the branch checked out on Staging, and confirm that everything is back the way it started:
$ cd /web/staging/www/bannerideas
$ cvs stat more.tcl 
===================================================================
File: more.tcl          Status: Up-to-date
   Working revision:    1.1.1.2
   Repository revision: 1.1.1.2 /cvsweb/myproj/www/bannerideas/more.tcl,v
   Sticky Tag:          release-2000-08-30_branch (branch: 1.1.1.2.4)
   Sticky Date:         (none)
   Sticky Options:      (none)


atp@piskorski.com
$Id: cvs-conventions.html,v 1.4 2001/08/29 07:08:54 andy Exp $