Someone recently asked me about how I organised releases in our Subversion repository. So I decided to write up the response as a blog entry. This article describes how we have set up Continuous Integration and release management for a medium size web project for one of our clients. The approaches described here represent some typical practices that are (I think) fairly commonnly used for this size of project.
This project has a large legacy code base, apparently from the dark times before Struts, when it was fasionnable to roll your own MVC frameworks. There are some unit tests, but the code coverage is only around 20%. The original developers have moved on, and the new developers tread with caution when modifying the code, for fear of inadvertently breaking things elsewhere. Development is done by developers from several different companies, on different sites.
For this project, we use a central Subversion repository. However, local development work can be done using Git, as is the case on my machine. This allows fast local branching and merging, and intermediate commits without confusing other developers. We use FishEye to keep track of changes, and Crucible for online code reviews. For this reason in particular, Subversion is likely to remain as the central repository for the time being.
Development work is done on the main trunk. A release branch is used for release preparation. During the release preparation stage, change sets are merged from the trunk into the release branch. This requires more work than releasing directly from the trunk, but gives more control over what is actually released. It also allows certain high-priority changes to be "fast-tracked" directly into the release branch before other features have been completed or stabilized. However, it introduces the risk of errors if the features are not correctly migrated to the release branch. So regression testing is vital.
A Hudson Continuous Integration server runs unit, integration and web tests whenever the code on the trunk is modified. If the unit and integration tests succeed, the application is automatically built and deployed to a test server.
This sort of application is a good example of where web tests can be really useful. Starting from scratch, we are progressively building up a suite of Selenium regression tests that are run against every new release. These tests are set up in their own separate Maven module, and can be configured to run against any server via a command line parameter.
The release branch has its own set of Continuous Integration build jobs, which run the full tests against every new version, and automatically deploys the application to a "pre-release" integration server. The release branch contains "release candidate" versions. When we are happy with the version on the release branch, we use the Maven Release plugin (run as a manual Hudson build job) to release an official version that is deployed to the Nexus server.
This released version can then be deployed to staging and production. This is done using parameterized Hudson build jobs - when you run the build job, Hudson prompts you for the release version to be deployed. Under the hood, this version number is passed as a command-line property to a special deployment project, which fetches the appropriate WAR file from Nexus and patches the configuration files for the target environment.