The build script from the last post turns your source code into something runnable. But it does not tell you which version of the source code you are building. That is where version control comes in.

I am going to use Subversion for this post because that is what most of the projects I have worked on have used, and it is still the most common version control system in enterprise Java development. Git is fine for other kinds of projects, but the concepts here apply regardless of which system you choose.

The basic workflow

Every developer on the team works on a local copy of the code. When they are ready to share their changes, they commit them to the central repository. When they start work in the morning, they update their local copy to get the latest changes from everyone else.

svn update
svn commit -m "Fixed the login page layout"

That is the entire workflow in two commands. Simple, right? It is simple until five people commit at the same time and two of them changed the same file. Then you get a conflict, and now you have to figure out whose version is correct, merge the changes, and hope nothing broke.

This is why version control is not optional. Without it, you are relying on email attachments, shared network drives, or “final_final_v3.jar” — and none of those scale beyond two people.

Revision numbers

Every commit in Subversion gets a revision number. The first commit is revision 1. The next is 2. The latest commit on your repository today might be revision 487. These numbers are useful because they give you a way to point at a specific state of the codebase.

If a bug appears in production and you need to know what the code looked like at the time of the last release, you can check out that exact revision:

svn checkout svn://repository/project/trunk@312 /tmp/old-build

This gives you the code as it existed at revision 312, including every file, every change, every commit message. You can build it, test it, compare it against the current version. Revision numbers are the simplest form of version tracking, and they work fine if you only need to refer to things occasionally.

But they are not human-friendly. Nobody remembers that “revision 312” was the login fix. They remember “the build from Tuesday” or “the release before the holiday”. For that, you need tags.

Tags

A tag is a snapshot of your code at a specific point in time. You create one by copying your trunk (the main development branch) into a tags directory with a descriptive name:

svn copy svn://repository/project/trunk \
         svn://repository/project/tags/release-1.0 \
         -m "Tagged release 1.0"

Now you have a permanent reference point. tags/release-1.0 will always contain the code from that moment, even if the trunk continues to change. You can check it out, build it, deploy it, compare it against other tags.

Naming matters. A tag like tags/build-Jan11_3PM tells you when it was made but not what is in it. A tag like tags/release-1.0 tells you what it is but not when. A good naming convention balances both:

tags/release-1.0        — a release version
tags/build-2011-05-11   — a daily build
tags/fix-login-312      — a specific fix, referencing the revision

Pick a convention and stick with it. Your future self will thank you when you need to find a tag six months later and have no idea what “build-May-11” refers to.

Branches

Tags are read-only snapshots. Branches are where you do work.

The most common branching strategy is to keep your main development on the trunk and create branches for specific purposes: a release branch, a feature branch, a hotfix branch. The release branch is a copy of the trunk at the point where you froze the code for testing. Developers keep working on the trunk while QA tests the release branch. When the release is ready, you merge the release branch back into the trunk.

# Create a release branch
svn copy svn://repository/project/trunk \
         svn://repository/project/branches/release-1.0 \
         -m "Branching for release 1.0"

# Later, merge fixes back to trunk
svn merge svn://repository/project/branches/release-1.0
svn commit -m "Merged release-1.0 fixes back to trunk"

Branches are powerful but dangerous. If you branch too often, you end up with a tangle of branches that nobody merges back. If you branch too little, you cannot isolate work. The rule of thumb is: create a branch when you need to do something that would disrupt the main line of development, and merge it back as soon as you are done.

How this fits with the build script

The build script from the previous post fetches the latest code from Subversion before building. That is the basic integration. But for release engineering, you want more control. You want the build script to know which tag or revision to build, not just “the latest”.

You can pass the revision as a property:

ant build -Dsvn.revision=312

And use it in your build file:

<target name="update">
    <svn username="${svn.user}" password="${svn.password}">
        <checkout url="${svn.url}" revision="${svn.revision}"
                  destPath="${src.dir}"/>
    </svn>
</target>

Now you can build any tag, any revision, any branch, without changing the build script. The tag name or revision number becomes the only thing that changes between a development build, a QA build, and a production build.

That is the connection between version control and release engineering: the build script produces the artefact, and version control determines which artefact gets produced.

A note on Git

If you are using Git instead of Subversion, the concepts are the same but the commands are different. Git uses tags and branches in much the same way, though Git’s branching model is lighter because branches are just pointers rather than copies. The idea of freezing a snapshot for a release, tracking it with a descriptive name, and building from that snapshot is universal.

Subversion and Git are different tools with different strengths. Subversion is centralised, which some teams prefer for its simplicity. Git is distributed, which suits open source and remote teams. But both solve the same problem: keeping track of what changed, who changed it, and when.

The next step

Version control gives you a record of what happened. The build script gives you a way to reproduce it. The next post in this series will cover build servers — the automated systems that run your build script on a schedule or in response to a commit, so you do not have to trigger it manually.