by Andrew Piskorski | Last Revised: 07 May 2005 |
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, listed above.
In short, CVS is neither the perfect tool for software development nor for versioning UNIX system administration files, but while several potentially superior alternatives exists, currently (early 2003) none of them are a clear and compelling alternative to CVS. CVS is the de-facto standard. So unless you already know why you should use something other than CVS, you can safely ignore this section and go on to the rest of this document.The Better SCM Initiative has some good info comparing the different not-CVS systems that might actually be or become viable alternatives to CVS. Rick Moen's list of version control systems on Linux, has useful descriptions, and Martin Pool (developer of rsync and distcc, etc.) has extensive thoughtful commentary on version control systems. Many others systems are listed at Google.
Technically, Bitkeeper is probably an excellent (perhaps the best) alternative, but it is closed source. Features and user reports sound very positive. And among other things, Linus Torvalds chose it for the Linux kernel, so it has clearly been proven on at least one very large distributed software project. I have not used it, but it sounds better than at least any of the other closed source version control systems I've read about.
Eventually, Subversion, OpenCM, Arch, Darcs, or Monotone might be able to replace CVS as the de-facto open source version control tool of choice, but I'm not really familiar with any of them. Caveat Hacktor. (For more related version control links, see also these posts, as well as some others in that thread.)
TODO: I really should get around to experimenting with at least the more interesting (distributed, change set oriented, Open Source, relatively stable and ready to use) systems, Real Soon Now.
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 briefly touch on the other two as well.
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. (And in fact, if you have released one or more Production versions of your code to outside users, you are pretty much forced to use the Many Branches method in some form.)
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 theupgrade.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 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 intoupgrade.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!)
For the one branch method, typically, 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.The very simplest way to do this is with no tags or any other form of explicit control at all. Just commit things on Dev, and when you're ready Flow of code is controlled solely by when you do "cvs update" commands in the Staging or Production working copy. This almost isn't worth mentioning here as it is the simplest and most obvious way to use CVS. However, you run the risk of accidentally pushing out code to Staging or Production when it isn't ready, and then of not even knowing what version was actually in use on Production before your mistake. So from now on we will assume from here on that you want at least a bit more insurance than that.
The cvs-stable.tcl script helps us do this with three features. Of these features, two, the
-r
and-u
switches are simply convenience features which we could do with any simple script, or even manually. The third, the-n
switch, fills a glaring hole in the CVS API. Here's a quick look at this script's current command-line options:$ ~/bin/cvs-stable.tcl -h Usage: cvs-stable.tcl [-h] {-n|-t|-u} [-p] [-r TAG] [ file ... ] Description: Does one of three things, all related to the 'one branch' method of using CVS version control - see also: "http://www.piskorski.com/cvs-conventions.html" You MUST specify either the -n, -t, or -u switch, as the FIRST option after the command name. Here's what each does: -n : Displays all files which do NOT have the tag TAG on the current working revision of the file. Use to answer the question, which files on Dev are committed but NOT yet marked as ready to go to Staging? -t : Tags files as 'stable-staging' or 'stable-production', ready to go to Staging or Production, respectively. Also keeps a short rolling history of where the cvs tag was before, to help you in case you mess up. -u : Runs a cvs update command which updates the files in a working updates files to the version currently tagged with 'stable-staging' (or 'stable-production'). It also sets that as a sticky tag, as is normal for the 'cvs update -r TAG' command. Options: -h : Display this help message. -r TAG : Use cvs tag TAG. Defaults to 'stable-staging'. -p : Use the Production default tag 'stable-production' rather than 'stable-staging'. Note: The command-line option parsing currently isn't very sophisticated so keep the options in the exact same order as shown in the pattern above, and do NOT try to do unix-style combined flags like '-htp'.When a developer has a stable version of a file, ready to be pushed to Staging, he can do:
and the file is tagged as ready for the staging server. (Note that this simply tags the last committed version, it does not commit your current changes.)$ cvs-stable.tcl -t myfile
However, it is usually simpler and better to simply tag everything, like so:
Why? Well, the script keeps a short rolling history in CVS of where the "stable-staging" tag was before. Do a "cvs stat -v" and you will see tags like "stable-staging-1" and "stable-staging-2". If you run "cvs-stable.tcl -t" by accident, or if anything breaks after you push code out to Staging, those tags will help you figure out how to (manually) put things back the way they were before. This is simpler and more robust if the "stable-staging" tag is always moved for all files at once, as a single atomic "this stuff is ready to go to Staging" operation.$ cvs-stable.tcl -t
In other words, I recommend only running "cvs-stable.tcl -t" right before you push out code to Staging, once per code push-out to Staging. (Disaster is not likely to befall you if you ignore this guideline, but things will probably go more smoothly if you follow it.)
Now, go to the Staging checkout, and run this:
$ cvs-stable.tcl -u
That sets the stick tag "stable-staging" on all files in the Staging checkout, and updates them to that tagged revision. (Note, since the tag is sticky, you only have to use the
-u
switch once in the Staging checkout. From then on, you can just do a normal "cvs update -d" to update files to the new location of the stable-staging tag. It won't hurt anything to use "cvs-stable.tcl -u" again instead, but it is doing more work, and is overkill.)Now you're basically set, when Dev is ready to go to Staging you just tag it with "cvs-stable.tcl -t", then go update Staging, that's it. The problem though, is how do you check what files have been committed but not yet tagged as "stable-staging", so you can decide whether they're ready to be tagged as stable-staging?
That's where "
cvs-stable.tcl -n
" comes in - in fact it is the primary purpose of that script. Simply run it in your Dev working copy, and it will tell what files which committed files present in Dev are not the same version as that tagged stable-staging. E.g.:That's it as far as the cvs-stable.tcl script goes. Note that it displays all CVS commands and output, so you can see what's going on.$ ~/bin/cvs-stable.tcl -n Tag: stable-staging File: log.c Status: Up-to-date Working revision: 1.3 Sun Mar 13 08:32:14 2005 Repository revision: 1.3 /home/cvsroot/aol4-src/aolserver/nsd/log.c,v stable-staging (revision: 1.1) Working revision '>' tagged revision. File: pathname.c Status: Up-to-date Working revision: 1.2 Mon Mar 14 11:38:48 2005 Repository revision: 1.2 /home/cvsroot/aol4-src/aolserver/nsd/pathname.c,v stable-staging (revision: 1.1) Working revision '>' tagged revision.With this One Branch process, tagged code is moved continually from Dev to Staging (and never the reverse). 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 (see the docs on the
-p
switch above), and then update Production.Finally, if you want to be more conservative, instead of repeatedly moving the "stable-staging" tag forward, you could instead place a new unique tag (e.g., "stable-staging-2005-05-07") each time you roll code out to Staging or Production, either manually (not hard) or by enhancing the cvs-stable.tcl script to support it. (And you can of course delete really old tags later, to reduce clutter.) This more rigorous method is generally not desirable for Staging, but depending on your project, you may want to consider it for Production.
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.]
TODO:[Interpreted and Database-backed (argues for shared Dev) vs. compiled and non-db-backed (argues for individual Dev checkouts), etc.]
In the companion document, note both where the CVS repository located, and what each of the projects inside it are.
All code from external sources shall be imported onto a vendor branch.Note that vendor branches are per CVS project, just like everything else in CVS.
Originally, my thinking was that you should be sure to always use the same vendor branch numbers and names across all our CVS projects, and if for some reason you don't or can't, change the list of vendor branches project specific document to be per-cvs-project.)
Currently (2002/12/19), I suspect that doing so is pointless and possibly counter productive. Better to always import on the default 1.1.1 vendor branch, and only specify a differen branch number when you are truly have code from multiple vendors.
For example, take the example of OpenACS. ArsDigita released ACS 4.2. OpenACS took over all mainenance and evelopment of the codebase, and released OpenACS 4.5. Should OpenACS 4.5 be imported onto the different OpenACS branch? No, it is the next release of the same code, just import it onto the same 1.1.1 branch, which happened to previously be the ArsDigita vendor branch for this code - but not more.
That said, document which way you're doing it!
When naming tags please you will want to follow specific conventions, and record any additions, modifications, or exceptions. Here's a recomendation:Example Tags:
One Branch method: tag name, e.g. explanation stable-staging
Tag indicating the current stable version of a file, ready for release to Staging. The exact tag name is defined in the cvs-tag-staging.sh
andcvs-up-staging.sh
scripts, not here.release-2001-06-10
Release from Staging to Production. Every Production release has it's own tag, and the same tag will be on every file. Many Branches method: 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. release-2000-09-07
There's no _branch
suffix, so this means we're merging new stuff from Dev across onto a pre-existing Staging branch.Some Tagging Rules:
- Always tag the trunk with a "root-of" tag immediately before branching, with a
root_of_
prefix.- Separate prefixes and suffixes (like
root-of
andbranch
) from the main part of the tag with underscores (_). Separate words within each "part" of the tag with dashes (-). This should make tags easier to read.- Be consistant. Keep the examples above in mind.
- Document any other conventions here!
Keep in mind that CVS operations are not necessarily atomic, and tagging a whole source tree can be slow (e.g., 10+ minutes).There are roughly three (3) different ways to specify where to place a tag:
By default, the tag commands do NOT move a tag if it already exists. So if you're doing something funky, you can just tag all the special cases first on a file-by-file basis, then go back and tag all the rest of the files at once by just issuing a normal recursive tag command.
cvs tag
by default places the tag on the version of the file that is currently checked out in the working copy.cvs rtag
places the tag on the most recent version of the file in the repository.cvs tag -r
places a tag based on the position of another tag. This is often useful for achieving more atomic-like behavior from CVS.For example, I had a list of 8 or so files that had more recent revisions committed on Dev, but which I wanted to keep frozen at their older,
release-2000-08-30_fix-2
revision on Staging. (Don't ask...) So when preparing to merge some new Dev work across to the existing Staging branch, I did:$ cd /web/myproject-dev
$ cvs tag -r release-2000-08-30_fix-2 release-2000-09-07 tcl/myproj-defs.tcl tcl/myproj-scheduled-procs.tcl tcl/myproj-widgets.tcl www/admin/trading/trades.tcl www/pvt/trades-by-trader.tcl www/pvt/workspace.tcl
$ cvs tag -R release-2000-09-07Renaming Tags
There's no special command to rename a tag. Instead, you use the old tag to tell CVS where to attach the new tag, and then once you're sure you did that right, you delete the old tag. E.g., for non-branch tags:The above is fine for normal tags. However, I think think it may not properly maintain the "branchness" of branch tags - but I'm not sure - more experiment is required. So I recommend that you do not attempt to rename branch tags in this manner, unless you want to experiment.$ cvs tag -r release-2000-08-08-FIX01 release-2000-08-08_fix-1
$ cvs tag -d release-2000-08-08-FIX01
Environment Variables
They may already be set appropriately system-wide for your use, but you'll want to check that the environment variables
CVSROOT
andCVS_RSH
are set to your satisfaction. In particular, you'll want:You also probably want one of the following in yourCVS_RSH=ssh
~/.profile
or~/.bash_profile
:export EDITOR="emacs -nw"
export EDITOR=emacsclient
export EDITOR=gnuclient
Output Redirection
This has nothing to do with CVS per-se, but it is useful: When doing CVS commands that produce volumnuous output which you may actually want to inspect - e.g., merges - you may want to redirect both standard output and standard error to a file. With a Bourne-style shell (e.g., Bash), simply tack something like:
onto the end of your command line. E.g., when merging fixes from Staging back to Dev, you might do:>> ~/m.txt 2>> ~/m.txt
wherecvs up -R -kk -j release-2000-09-24_fix-2 -j release-2000-09-24_fix-3 >> ~/m.txt 2>> ~/m.txt
m.txt
is the file in your home directory that you want all output redirected to.
Generally, any code that you receive from an outside source rather than writing yourself should be imported onto a vendor branch, rather than simply added.Ask yourself these questions: Might I get a ever get code updates or fixes from that same source? Do I want to have a convenient, automatic way of diffing the code we're using vs. the code as we received it? If the answer to any of those questions is "yes", then you definitely want to use a vendor branch.
Initial install of ACS:
When you grab ACS out of the tarball, you might as well check it all into CVS first thing, before you run the install scripts or anything. Don't wait till later. It goes like this:
$ mkdir /web/mysite-dev Go and untar acs-4-2-beta.tar.gz into /web/mysite-dev $ cd /web/mysite-dev/ $ cvs -d ls.arsdigita.com:/cvsweb import -m "Importing ACS 4.2beta" mysite ArsDigita acs-4-2-beta-R20010307 $ cd /web/ $ cvs -d ls.arsdigita.com:/cvsweb checkout -d mysite-dev mysiteNote that we specified a repository to use with
-d ls.arsdigita.com:/cvsweb
, but we did not specify a vendor branch number, so CVS is going to use the default 1.1.1 branch. In this case that's fine - we'll be using 1.1.1 as ourArsDigita
- but you should probably make a habit of always specifying the vendor branch. See below.Importing a new ACS Package
Later on, as you download and install separate packages, you'll want to be sure to check each of these into CVS too. It's straightforward:
First, untar everything into an appropriate directory. You could do this anywhere, but I'll assume you used the ACS APM to do this, so all the package files are aleady installed in their normal ACS place, you just haven't check the code into CVS yet.
So, for example for the Monitoring package:
$ cd /web/mysite-dev/packages/monitoring/
$ cvs import -m "Importing Monitoring package v. 4.0.0a" mysite/packages/monitoring ArsDigita monitoring-4-0-0a-R20010201
$ cd ..
$ rm -r monitoring
$ cvs up -d monitoringIf you know the CVS (or other version control system) tag that the vendor used internally for his release, then that's probably what you want to use for the e.g. "
monitoring-4-0-0a-R20010201
" release tag above. If you don't know, just make up something reasonable.Note that we told CVS to import the files into the "
mysite/packages/monitoring
" subdirectory. Make sure you get that part correct, or you will successfully import your files into the wrong place in your CVS repository. (At which point you'll want to go and carefully delete them by hand...)
More Easy Import Examples:
Note that by default CVS ignores some files like
*.so
completely. So if you want to check those in, you'll want to reset the cvs ignore list with the-I!
switch:$ cvs import -I! -m "Importing stock AOLserver 3.2+ad12 Solaris binary distribution from ArsDigita." aol3 ArsDigita aolserver-3_2_ad12
Sometimes you'll do a normal import, but will also want to do a bit of remedial fix-up work if the software vendor screwed something up:
$ cvs import -b 1.1.17 -m "Importing TclODBC v. 2.2.1, from http://sourceforge.net/projects/tclodbc/" tcl-src/tclodbc SourceForge tclodbc-2-2-1
$ cd ..
$ cvs checkout tcl-src
$ cd tcl-src/tclodbc/config
$ cvs import -b 1.1.17 -m "Importing missing config/ subdirectory, which is now in the SourceForge CVS, but is not tagged with tclodbc-2-2-1 like it should be." tcl-src/tclodbc/config SourceForge tclodbc-2-2-1-missing-config
$ cvs tag tclodbc-2-2-1Importing AOLserver+ad12 source distribution:
Importing Tom Poindexter's Odbcisql, from http://sourceforge.net/projects/tclodbc/:$ cvs import aol3-src ArsDigita aolserver-3_2_ad12
Importing nsopenssl 1.1b source distribution, from http://scottg.net/:$ cvs import -b 1.1.19 tcl-src/odbcisql Tom-Poindexter odbcisql-1-0
Importing Monitoring package v. 4.0.0a:$ cvs import -b 1.1.7 aol3-src/nsopenssl scottg nsopenssl-1-1-b
Importing Developer Support package v. 4.0.1a:$ cvs import mysite/packages/monitoring ArsDigita monitoring-4-0-0a-R20010201
$ cvs import mysite/packages/acs-developer-support ArsDigita acs-devloper-support-release-4-0-1a
Upgrading Code - importing code that needs to be merged with existing code:
When importing a new version of code you already have, the process is exactly the same as importing brand new code, except you add a bunch of extra steps. Note that both Fogel and Cederqvist have some good instructions on this.
Remember, when you import code onto a "vendor branch" you really are putting code onto a branch, not the main trunk. When they code doesn't exist at all yet on the main trunk, 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 thefoo.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:
- Import new stuff onto vendor branch.
- Checkout a new working copy of the trunk with
-kk
.- Merge in changes from vendor branch, in that working copy.
- Resolve any conflicts.
- Commit.
- 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:
- It is important to use the -kk tag when you checkout the separate working copy. If you don't, you will get lots of bogus conflicts to resolve.
- When you import version 2 of some vendor code, the import command you use is exactly the same as when you imported version 1, except for your comment and the vendor release tag.
- For importing brand-new code, you simply skip steps 2 through 5.
- When you merge code from the vendor branch onto the trunk the second and all subsequent times, you have to use two -j switches rather than just one. See also below under Merging fixes from the Staging branch back to the Development trunk.
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 upImporting 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 -dImporting 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 -dImporting 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 -dOccasionally 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
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:
- 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.
- Place the
root-of
tag on the trunk.- Place the
branch
tag.- Checkout the new branch onto Staging.
- Do whatever non-CVS things you need to do - data model upgrades, restarting AOLserver, etc.
- 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_branchIf 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-moduleNow, checkout the new branch onto Staging:
$ cd /web/myproject-staging
$ cvs update -kk -d -r release-2000-08-30_branchFinally, 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
Steps to merge changes from Staging back to Dev:
- Make sure everything on Staging is committed.
- Try to make sure ever on Dev is committed too - it is less confusing this way (but see below).
- Tag Staging with an appropriate
fix-n
tag. This identifies your batch of fixes.- Optionally, check out a separate copy of the Dev trunk into your own temporary directory (see below).
- Use
cvs update
orcvs checkout
to merge the files from Staging to Dev.- Resolve any conflicts, and commit the modified files on Dev.
- 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.: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$ cd /web/myproject-dev
$ cvs up -R -kk -j release-2000-08-08_fix-1fix-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:does not show you everything that the command would do for real without the$ cd /web/myproject-dev
$ cvs -nq up -R -kk -j release-2000-08-08_fix-1 -j release-2000-08-08_fix-2-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.:
or:$ 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.)Then, fix any conflicts, commit as usual when you're done, and delete the extra working copy.$ 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[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:
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.)$ cd /web/myproject-dev
$ cvs up
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:
- Make really sure all fixes on Staging have been merged back to Dev!
- Tag Dev.
- Go to Staging and use
cvs update -j
to merge.- Verify that everything is right. (E.g., there should not be conflicts, if you had already merged fixes from Staging back to Dev.) If the two files should now be identical on Dev and Staging, do a diff to verify that they are.
- Commit on Staging.
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-dev
$ cvs tag -R release-2000-09-07
$ cd /web/myproject-staging
$ cvs -q up -d -R -kk -j release-2000-09-07found several conflicts in each of the following files:$ cd /web/myproject-staging
$ find . \( -name "*.tcl" -o -name "*.sql" \) -exec grep '<<<' {} /dev/null \;Beware of sticky tags! [TODO: Consider writing more about the dangers and uses of sticky tags.]./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.sqlThe
-A
will remove all sticky tags, but although this may fix your immediate problem, this can cause you big trouble on branch. I don't know yet how to add sticky tags back to a file, or if doing so is even plausible.Here's another real-life example of merging from Dev to an already existing branch on Staging. In this case, I only wanted to merge over certain specific files. I don't necessarily recomend this - think about the potential problems I might have had if I later had to do a second round of merges, this time with a larger group of files that partially overlappted this set. But, it worked well enough at the time:
$ cd /web/myproject-dev
$ cvs tag release-2000-09-08 tcl/myproj-scheduled-procs.tcl www/doc/sql/myproj-orders_pb.sql
$ cd /web/myproject-staging
$ cvs -nq up -j release-2000-09-07 -j release-2000-09-08 tcl/myproj-scheduled-procs.tcl www/doc/sql/myproj-orders_pb.sql
$ cvs commit tcl/myproj-scheduled-procs.tcl www/doc/sql/myproj-orders_pb.sqlOne more example. The only tricky part is to get the first tag which indicates the current state correct during the update command.
$ cd /web/myproject-dev
$ cvs -nq tag ulla-order-enter-2000-09-11 tcl/myproj-order-entry-widgets.tcl www/trading/order-enter-n.tcl
$ cd /web/myproject-staging
$ cvs up -j release-2000-09-07 -j ulla-order-enter-2000-09-11 tcl/myproj-order-entry-widgets.tcl www/trading/order-enter-n.tcl
$ cvs commit tcl/myproj-order-entry-widgets.tcl www/trading/order-enter-n.tcl
Nothing could be simpler. Just place a tag against certain files, rather than every file in the working copy or rrepository. E.g.:Then go merge the files as usual. E.g., continuing the above example (merging from Dev to Staging):$ cd /web/myproject-dev/tips/wrapper
$ cvs tag release-tips-holidays-2000-08-28 Makefile libtipswrap.so tipswrap.c tipswrap.h tipswrap.oFinally commit, etc. as usual.$ cd /web/myproject-staging/
$ cvs up -kk -j release-tips-holidays-2000-08-28
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 (old broken link), which links to how to do it.
Note: This section is also a BBoard article (old broken link).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:
In the above example, to make it work for you the only things you'd need to change are the name of the file (
$ 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.sqlstuff.sql
), the names of theroot-of_release-2000-08-30
andrelease-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 likemv
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 ofcvs tag
. But I findcvs 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: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
$ 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.2cvs log
commands shows this magic branch number, butcvs status -v
does not - which is why I usecvs log
throughout these examples.Let's try moving the the branch back to an older revision of the file:
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 -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
$ 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.2OK, the revision number that we need (1.1.1.1.0.2) has been created.
$ 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.2Now we'll actually move the branch tag to the older revision of the file:
Now we go to the Staging server where the
$ 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.2release-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:Success! At this point, we could simply delete more.tcl and then do
$ 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)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:
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
$ 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 existrelease-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 therelease-2000-08-30_branch
tag. But since I didn't, let's use the "create a branch revision of this file" trick again:But what revision number was acutally used? Let's find out:
$ cvs tag -l -r root-of_release-2000-08-30 -b test-cvs-2_branch more.tcl T more.tclAh, 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.
$ 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.2Now, 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.4Since we're done experimenting, let's get rid of the extra branch tags we created:
And we can again go to the branch checked out on Staging, and confirm that everything is back the way it started:
$ 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
$ 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.9 2005/09/09 21:44:45 andy Exp $ |