Version control, release management, and continuous integration

In this post i’ll describe how we’re doing version control, release management, and continuous integration on my current project. I’ve noticed that for people who have never worked with version control, the whole system can be pretty daunting at first. I hope this post will explain the concepts.

A new project starts with a developer importing source code for a new application into CVS. Importing source code into CVS means that you’ve created some files on your development pc, and you’re putting these files into the version control repository. Once these files are imported into CVS, other developers can check these files out of the repository and edit them on their own development pc.

We’re using CVS and in CVS every file gets its own revision (version) number. In the illustration bellow the new project starts with 2 files, file A and file B, both with revision number 1.0 (top left green box).

[Version control][]

Now the continuous integration tools kicks in. We use cruisecontrol. Continuous integration basically means that after every change in CVS the whole project is automatically compiled to see if no incorrect changes were made to the files in CVS. Usually a developer will just compile and test the part of the system he’s working on, but the continuous integration tool will verify that the whole system compiles. And preferably it will also run tests on the system.

After the first files are imported into CVS, cruisecontrol builds the application for the first time. If the build succeeds, this will result in build-1. If the build fails, an email will be sent to the developer requesting him to fix the build. A label, or tag, is put on all the files which we’ve used to create build-1, so later you know exactly what you we’re testing when testing build-1.

How does cruisecontrol create build-1? It simply starts an ant build file. This build file is part of the files imported into CVS for the application, so in the illustration it could be file A or file B. This build file contains all the steps to build and test the application. Now the same developer, or another developer can continue working on the file and commit new changes to the repository. In the illustration you see that file B was changed and these changes were commited to CVS. This results in a new revision of file B, revision 1.1. And again cruisecontrol builds the application, resulting in build-2. The green box labelled build-2 show you which files are part of build-2, revision 1.0 of file A (unchanged), and revision 1.1 of file B.

You may ask: when does a developer commit his changes to CVS? We prefer task level commit, meaning that a developer commits changes for one new feature or one bug fix at a time.

Now things get interesting, the project is almost ready to release it’s first production version, which can be used by the end-users. So a developer makes some changes to files A and B and commits these changes to CVS. This results in revisions 1.1 (file A) and 1.2 (file B). Cruisecontrol creates a new build, build-3. After some testing, the decision is made that build-3 is correct and ready for the end-users. A developer tags all the files part of build-3 with a label indicating that these files comprise the first release. This tag is called RELEASE_1_0.

After the first release the developers continue to make changes for the following release, but some bug fixes also need to be commited the CVS for the release 1.0. This is done by creating a branch in CVS. This branch is called MAINT_1_0, as in maintenance on release 1.0.

Changes that will lead to the following release are commited on the HEAD, also called mainline, main trunc, or active development line. The active development line is illustrated in the diagram by the green boxes in the upper half of the diagram. We configured cruisecontrol to only create builds for changes on this active development line. Changes on branches aren’t automatically built. In my opinion it would too complex to configure cruisecontrol for all branches.

For builds on branches and for building release versions, e.g., RELEASE_1_0, we have a release script. It’s a unix script which you can call with 2 parameters, project name, and tag (the labels in the yellow boxes). This script will get the correct sources out of CVS and build the requested deliverable. More info about the reasons for a build script on Joel on software: 2. Can you make a build in one step?.

Release 1.0 has been patched twice in the illustration, first a patch is made to file A, resulting in revision 1.1.1.1. Then a patch is made to file B, revision 1.2.1.1. Every time a patched version of the software needs to be released, the software is tagged in CVS, e.g., RELEASE_1_0_1, and later RELEASE_1_0_2, and using the release script the software is build.

To continue the scenario in the illustration, after the first release development continues and a developer adds another file to the project, file C. Some changes to all the files are made, resulting in 2 new builds, build-4, and build-5. It is then decided that build-5 should be released to the end-users, and it is tagged RELEASE_2_0. Using the release script the release is build, and a maintenance branch is created.

In the example, builds on the main branch are released for production. Usually you don’t do this. What you normally see is that first a release candidate is branched, patched, another release candidate is created and then at some point the release candidate branch is tagged with a production tag.