How to develop an update without breaking production
The Branches Iceberg
Have you ever found yourself in the need of “hiding” a feature with some boolean flags because these features are not for this delivery / patch? Or even worse, you broke a build because you left a half-baked feature for the next delivery in the current one?
What’s the solution? Twiddle your thumbs while you wait for the current patch to be delivered before starting work on what’s coming for the next one?
If we only had a way to split work and continue working on unstable features that will be needed in the future while some other part of your team works on the final changes for the stable release coming soon…
Introducing: Stability Branches (or “Spicy Gitflow”)
This is a part of my “git gud or git rekt” series.
Stability Branches
Back in 2020, Epic released a white paper called “Workflow on Fortnite: Collaboration on Large Teams with UnrealGameSync”, and among a lot of things, they dropped the concept of “Stability Branches”.
A stability branch is a branch you make from your current development branch when a new release approaches. The idea (and name) for that branch is to make sure that your game must always be buildable (no build/compilation errors) and it should be crash-free (or as crash-free as possible).
On this branch you should only fix bugs and make balance patches without introducing new features. This way you build towards a very stable release, and you release a very stable and bug-free update.
But… this sounds all too familiar.
Spicy Gitflow
If you are a hardcore, old-school, silverback developer, you might have already noticed that what I describe as Stability branches are just release/*
branches in a Gitflow… flow, and you would be correct!
Let me just bring people up to speed on Gitflow:
- You have a
develop
branch where you integrate all changes. - Your changes happen on
feature/*
branches that then integrate intodevelop
. - You branch from
develop
withrelease/*
when you startstabilizingpolishing a release. - You release by pushing the
release/*
intomain
orproduction
. - If any critical bug happens, you hotfix it into
main
(or sometimes ahotfix/*
branch immediately merged into main). - All changes from a more
stablereleas-y branch are merged down immediately for history’s sake.
With this approach, thinking of live games (updates every two~three months), we can use our release/*
branches as stability branches to stage the next immediate update.
This assumes you branch from your unstable develop
branch when all the features are already in and you are ready for a feature freeze, leaving this release/*
branch to be only for QA, bugfixing and mayyyyybe some polish and juice.
In an ideal world, hell yeah! In reality… we will have to bend that rule.
Bending the law, bending the law!
Unless you are exactly staffed, where you finish the update the day before release and only then you start with the next update (in which case I would say you might be understaffed), you can find yourself in a position where you can’t collaborate on the upcoming update because there aren’t any open tasks you can grab, but you know what is coming for the update-after-this-one. How can you start working on that without risking the current, upcoming update?
Well, feature/*
could be your choice, making yourself an unstable branch to work with and waiting until the update was delivered before integrating your changes into the develop
branch, but if we bend the rules of stability branches, instead of keeping development
stable, we make it inherently unsafe, and everything that needs to become more stable branches away from it. This means the part of your team that is getting the update ready branches away from develop
into their own release/*
branch where they are still adding features but hopefully can hit the feature freeze ASAP and stabilize that branch so QA can come in and get ready for the final push to production
.
We bend the law of “stability branches are always feature frozen,” and we start seeing our branching strategy more like a gradient.
End of life of a release/*
This is an easy one. Once a release/*
is merged into production
(released to the public), that branch is officially dead. No further changes can happen on it. Any change happens directly on production
and is considered a hotfix.
Always merge down, Merge up only on milestones
Always down
If the feature for this update was finished in release/*
, when the hell does it get back into develop
? What if we had an emergency hotfix? When does that get back to develop
?
Well, the idea is that all changes that happen in a more stable branch trickle down immediately to their more unstable brethren.
- Hotfixes directly on
production
must be merged back to any active (unreleased)release/*
and back todevelop
.- Even if the hotfix is an ugly hack that
develop
will fix in a better way, merge the fix back for the sake of keeping history, then fix it properly.
- Even if the hotfix is an ugly hack that
- Features, fixes, content, etc. that gets added to any
release/*
should be merged back intodevelop
at the earliest convenience.- In case it wasn’t obvious, when a
release/*
branch dies (a.k.a. is merged intoproduction
and released to the masses), that final state must also be merged back intodevelop
.
- In case it wasn’t obvious, when a
Almost never up
The only moment when you merge up, is when you reach the end of the life of a release/*
branch and you merge it into production
and release the update.
However, I know that no plan survives contact with the enemy, so it might happen that a very critical bug was fixed in develop
. In this case, a surgical cherry-pick could be performed to try to get this very important fix to the active release/*
branch or even to production
.
Auxiliary branches
Adding to these concepts, I add two more branch families to this mess:
-
content/*
branches: The stable cousin offeature/*
. These branch offdevelop
at a point where the game was stable enough to open the editor (and maybe even build the game). These are made and used by level designers, artists, and other “non-programmers” to add content to the game without ever compromising the “buildability” of the game. This allows less techy people to work in a somewhat stable environment. While these branches are usually more stable thandevelop
, they are not considered to be more stable for other purposes. -
patch/*
branches: The less crashy cousin ofhotfix/*
. These branches, the same ashotfix/*
split off ofproduction
instead of the unstabledevelop
. These are changes that will only tweak values or adjust the game very minimally. If your game has some sort of A/B testing and feature delivery, these branches don’t need to exist since that system should handle those changes.
Summary of branches
In stability order, with children underneath them
production
: What your players can see. This is what gets built and delivered.hotfix/*
: “oh f*ck” branches. Branch fromproduction
, fix something, test it, merge back toproduction
. Then merge into everything below.patch/*
: For balance patches/gameplay patches. If you have a feature delivery system, A/B Testing or something like that, you won’t need these.
release/*
: Born fromdevelop
once you start stabilizing a release, they “die” once they get merged intoproduction
and released to the public. Merge down frequently.develop
: The unstable core where everything that is being developed gets merged.feature/*
: The very unstable changes. If you have a small team with very good communication, you can avoid these branches and just commit everything intodevelop
content/*
: The stable unstable changes. Mostly useful in engines where a half-baked piece of code can keep you from opening the editor (ejehm… Unreal Engine… ehjem). It’s useful for non-programmers to have access to a non-crashing build where they can experiment and test.
Finally, a very spooky graph of how stuff can look with this system.
(Arrows point forward in time, this is not a proper git graph! In a git graph, arrows point to the parent)
Now, listen here to the important part:
You don’t have to use them all!
I know that I speak (and thus, I write) like I am laying truth granted onto me from the gods of project management above and that thou should’st do as I say or ill things shall befall thee
But that ain’t how this works.
You know your team better than I do (and if you don’t, you probably should start trying to get to know ‘em).
Steal what you find useful; don’t steal what you don’t find useful. Bend, twist, break, and adapt the system to work for you, not against you.
Thank you for your time <3