Yes, this is all over again the old discussion about what’s the best branching model for projects using Git as their version control system. I know, there are countless blog posts (e. g. 1, 2, or 3) about that topic out there… yet, I feel most of the discussion is focused on projects with continuous deployment (i. e. mostly web applications), whereas classical desktop software with classical release cycles are rather underrepresented.
First, some thoughts on what I actually need, before creating any new ideas others put already in the bin due to being unfit… When hacking on a desktop software or an operating system (i. e. something that will be deployed on many systems, by many people, without me as developer being aware), it all comes down to releases. People are used to releases, updates etc. being clearly labelled with a semantic version designator. This is no different from what you should do for SAAS web applications – what’s really different here is that for desktop applications, several supported versions can exist in parallel. So for me, a good branching model should
- limit to one eternal branch (have most recent working code in
- use feature branches to change the code in
- support maintenance of several active releases in parallel
So, why not have a look at what are other open source developers doing? Well, that proved to be easier said than done. I’m quite familiar with FreeBSD’s branching model, but it is rather old-school, was ported from CVS to Subversion and seems to have some misfits with Git (of course the linked article blames Git, but that means vice versa if Git is otherwise considered good or is mandatory to be used, the FreeBSD branching model doesn’t fit well with Git).
But what about more recent projects, that were not ported from older version control systems? Well, there are some, like Atom and Electron. Both would be perfect samples, as they use semantic versions, have no continuous deployment and are managed by GitHub – they could be great role models about how Git in general and GitHub in particular are meant to be used for this type of project. I wrote “would” and “could” since both projects didn’t document their branching model anywhere. Of course that didn’t prevent me from reviewing both projects’ repository structure in order to explore what branches I would find there. For Electron, I couldn’t really figure a structure, but for Atom, there seems to be one:
Seemingly, Atom uses dedicated branches for releases, and tags on these branches to mark published releases. This would be quite in line with what Marcus Geelnard proposes in his stable mainline branching model for Git. At a first glance, this seems to be the only acceptable model also satisfying my third requirement, i. e. the maintenance of several active releases in parallel, that are not nice sequences. This feels quite close from what I know from FreeBSD –
master corresponds to
head, and the release branches correspond to FreeBSD’s
However, there’s another branching model that looks quite appealing to me: Adam Ruka’s OneFlow model (the post is quite recent, but the idea of that model already dates back to his first article on the subject, GitFlow considered harmful). True, in the preamble it already says:
The main condition that needs to be satisfied in order to use OneFlow for a project is that every new production release is based on the previous release
If your project needs to maintain multiple simultaneous yet incompatible release versions that way, then OneFlow won’t work for you out of the box.
But is that really true for my projects? For FreeBSD and Python it certainly is – both projects work with two major releases, from which they continue to derive also new minor releases. For my projects however, old versions would still be supported with patch releases (e. g. in case of a security issue detected), but new versions would only be derived from the current development stage, i. e. from what can be found in
master. So would the OneFlow model work for me?
Honestly speaking, I can’t tell right now. There is one aspect about OneFlow that intrigues me and at the same raises some questions: It’s about merging back from release and hotfix branches. While I can mostly understand the idea of merging hotfix stuff back into
master (you probably want to fix the same issue also on
master that you just fixed on a release), I don’t understand the concept of release branches, if they should be merged back. Let’s look at several scenarios:
The trivial one: in preparation for a release, you need to bump the version number of your project. Fine, but what does in this trivial scenario distinguish a release branch from a mere feature branch? What is the value added for doing it that way? I can only imagine it’s more convenient for projects with a bad version information maintainability (e. g. maintaining version information for several submodules like Electron has to do, or maintaining version information in several files scattered across the project’s source tree). It also would mean you have to stick to a certain sequence:
- create the release branch
- do your stuff on the release branch (bump version & tag release)
- you can create new feature branches off
masterin the meantime
- merge the release branch back into
masterbefore any new feature branch is allowed to be merged (i. e. apply a code freeze rule for
- bump the version on
masterto the next RELENG working release
- rebase all feature branches to the bumped
- start merging back feature branches
For me it would feel more natural to have a sequence like this one:
- create a release branch
- bump version on
masterto the next development release
- rebase any feature branch to the bumped
- do whatever has to be done on the release branch (version bump, release candidates, …)
- tag the final version on the release branch
The ugly aspect of this cactus model (that’s what Jussi Judin calls it) is the merging effort between the different branches. It allows the
master branch to strongly evolve while the release is being finished, which makes it hard to keep fixes in sync between potentially several release branches,
master and feature branches. OneFlow solves this problem, but at the price of a necessary code freeze in
master during the release preparation period.
So it all comes down to one question: What happens in my repositories in preparation for a release, and how strong can the master branch evolve during that time? To be honest, when preparing a release, applying a code freeze on master wouldn’t be that terrible a price to pay. Contrariwise, this could help keeping the discipline to finalize a release before turning attention towards other ventures, or consequently abandon a release attempt if too much has to be fixed upfront. So indeed, OneFlow could generally fit my needs.
There’s just one point I would handle differently, and that is patch level releases. For sure, a hotfix or patch release has to be based on the latest previous release, but that could as well be a hotfix release itself (e. g. 2.3.2 would be based on 2.3.1). OneFlow is not really taking this into consideration, so for me, I’m more or less back on Marcus Geelnard’s stable mainline branching model for Git, but with one amendment taken from OneFlow:
So my personal branching model is a mix of the OneFlow and Cactus models:
- New features are developed in feature branches, leaving master as single eternal branch (in accordance with OneFlow).
masteris a protected branch, so features have to be merged by pull requests with all applicable code checks (CI, quality reviews) to be passed prior merge (my own rule enhancement).
- Alpha and beta pre-releases are managed via tags in
master. Version bumps and tags can be applied either as an override to the protected branch rule by a repository administrator, or by using quick & dirty pull requests (idea shamelessly stolen from FreeBSD).
- Release branches are branched off
masterafter the beta phase has been completed (this helps avoiding too many merges and rebasing between the different branches).
- Release candidates and the final release are managed as tags in the respective release branch.
- While a release branch is going through the pre-release phase,
masteris under code freeze (i. e. no pull requests from feature branches shall be merged during that time – that’s again my own conclusion).
- After tagging a release branch as final (zero patch release), the release branch shall merge back into
master(introducing fixes made during the release candidate phase – in line with OneFlow).
- Once this merge has happened, the version in
mastershall be bumped to the next upcoming release (suffixed with
-dev— again shamelessly copied from FreeBSD).
- Once this has happened, all active feature branches need to be rebased onto the most recent version of
master. This implicitly also lifts the code freeze previously imposed on master (again in line with OneFlow).
- From the moment of the final release on, the release branch becomes a residual maintenance branch, managed as protected branch (to avoid incidental development in the wrong place – this is my own enhancement to OneFlow).
- Post release patches (i. e. hot fixes) are managed within the residual release branch (which becomes a maintenance branch after the final release).
With this set of rules, it should be fairly straight forward to manage projects with sequential releases, but a need for hotfix releases also for older releases otherwise not being developed any further.