As a follow up to my last post http://jake.ginnivan.net/release-nuget-semver-packages-from-teamcity I have been investigating more into different ways to achieve semantic versioning and being able to release in an easy way.
Next stop on my trip was looking into Git-Flow and how it manages releases, initially it seemed very waterfally and too heavy to use on an open source project, but I thought it may fit at different clients. I was instantly drawn to the fact that releases have an explicit step to version at the start of the release process, this is great, I can manage my project, merge pull requests, add features then when I am ready, I can decide to release, put together release notes and figure out if this is a major, minor or patch release.
Much to my surpise, it is actually very light-weight and you can drop much of it if your process is simpler (say for open source).
https://github.com/TestStack/ConventionTests is my guinea pig!
To get started, I decided to try and implement git-flow manually to really understand what is going on. There are plenty of explainations out there, so I will just be running through what I did, and how it works from my point of view. I always have two remotes setup for my projects, ‘upstream’ is the main repository, ‘origin’ is my fork.
1. Convert the repo over to git-flow
From your git command line
git checkout master git fetch upstream git merge upstream/master git checkout develop git push upstream develop
This pushes the
develop branch into your repo, then head to the project settings in github and change the default branch to
2. Install GitFlowVersion
Basically GitFlowVersion uses the conventions in place in git-flow to make it really easy to version your software.
Once up and running, tags off
master get a normal version, say
v2.1.0 (assume this is the LAST tagged release), develop CI builds get the version
v2.2.0-unstable20 where the minor is LASTMINOR + 1 and there has been 20 commits since the last tag on master. Or
Release branches have a version of
v2.2.0-beta where 2.2.0 is the version you have put in the release branch name (in this case
You can also tag a release branch as
rc2 and the version will become
If this sounds confusing, its not, and it is all pretty automatic, read on to see what it actually means for you maintaining a project.
3. Contributing/Pull Requests
I always use feature branches, but not for an entire feature. I use very short lived branches, which I put up as a pull request as soon as I am done. I am known to submit 3+ pull requests all within the space of an hour when I work on a project because I just fix a bunch of small things.
Nothing much changes here, except you take you branch from
git checkout develop git fetch upstream git merge upstream/develop git checkout -b FixingSomething
Pull requests now target
develop, so I do my commits, push to origin like normal and submit my pull request targeting
Your CI build (assuming you are building pull requests) will trigger with build number
v2.2.0-PullRequest50, where the version is the last release, with the minor bumped, just like develop version numbers, except the semver tag is PullRequest<PR#>.
I think that is pretty neat.
4. Releasing a new version
When you decide you want to release, or start preparing a release you can either take a release branch, or merge develop straight into master and tag master with the release (afaik this doesn’t cause any issues :P)
In my mind there are two styles to run a project,
- every checkin builds, then if tests pass autodeploys. These projects cannot adhere to semantic versioning, but the changes are often so tiny that upgrades pose little risk.
- At some point in time, the decision to release is made, this could be as soon as a pull request is merged, or after a particular milestone is reached. All the projects I am currently contributing to work in this way.
4.1 Using a release branch
Using a release branch allows you to make the decision I am going to release, there may be a few things I know I have to do before releasing (DbUp is a great example of this). But I don’t want to stop being able to merge pull requests. To release using a branch (assume develop is up to date), and I am only fixing a bug in the current v2.1.0 release
git checkout -b release-2.1.1
git flow release start 2.1.1
I can now leave this branch open for a bit, do the work I need to do to release 2.1.1 or send it to someone else on the team to OK. I could even release the 2.1.1 build as a pre-release package on NuGet. It would have version 2.1.1-beta1 remember.
Once I am happy, either the pre-release package has got the feedback I wanted, or I have made the additional changes I need I simply merge the branch to master (with the –no-ff option), tag, then delete the remote branchs. Remember this is if you are doing it manually, you can also just go
git flow release finish if you have the git-flow extensions installed, or use source tree and click a button :P The first two commands are not needed if you haven’t committed anything to the release branch
git checkout develop git merge release-2.1.1 --no-ff git checkout master git merge release-2.1.1 --no-ff git tag 2.1.1
git flow release finish 2.1.1
git push upstream master git push --tags git branch -d release-2.1.1 git push upstream :release-2.1.1
git flow release publish 2.1.1
4.2 Skip release branch
If you don’t want to bother with the release branch, you could also just go
git checkout master git merge develop --no-ff git tag 2.1.1 git push upstream master git push --tags
5. Publish the build
Now you have the release build from your CI you can progress through your build pipeline and release to NuGet or publish to a test environment or whatever the first step is in your Continuous Delivery pipeline.
I think Git-Flow is actually not too bad for releasing projects using semantic versioning, most of the time in this is writing the release notes, seeing what has changed since the last release (which is really easy due to the conventions in place!) and checking to see if you have any breaking changes which would mean a major version bump.
As a summary. Here is me fixing and releasing a feature as a pre-release package. The current version on NuGet is currently v2.2.0
git fetch upstream git checkout develop git merge upstream/develop git checkout -b SomeFeature # Work.. git commit -am "Some experimental feature" git push origin SomeFeature # Submit pull request, so NOTHING has changed yet, this PR is merged git fetch upstream git checkout develop git merge upstream/develop # to get my changes into my local develop branch # So now I am starting the release, nothing before here is new if you are using GitHub flow git checkout -b release-2.3.0 git push upstream release-2.3.0
Now my CI builds, and I click the button to promote the last CI build as a pre-release NuGet package. Once I am happy, I have got my feedback
git checkout master git merge release-2.3.0 --no-ff git tag 2.3.0 git push upstream master git push upstream --tags
My CI will build, and I can click the publish button on teamcity to release to NuGet.
Thoughts? Do you know of an easier way. I am REALLY open to suggestions and willing to try stuff out at the moment :)