Imagine you’re working on the latest feature of your app. You get an alert from your monitoring service and your app is crashing in production. You find the cause of the bug, fix it. Now you just have to deploy it. So what pull request should you open? Which branch will hold the bug fix?
All those answers will help you create your git workflow. The git workflow is the way you organize your git branches, commits, tags, … It should reflect your development process and be as easy and maintainable as possible.
I’ll explain the workflows I’ve worked with and the ones I recommend. Usually, we work with two environments: a staging app for testing purposes and the production app. In this first part, I’ll focus on the staging and production workflows.
One thing to keep in mind: I build mobile applications with React Native. That means my needs won’t be necessary the same as yours.
One main difference in mobile app development vs web is the number of versions of your product that can be live.
Let me explain, imagine you deploy a new release of your website. All users will use this new release from now on. On a mobile app, it’s a bit different. Each user can decide to download or not the latest release from the store. Although a lot of users usually let their device automatically update their apps, it’s a possibility. If a user encounters a bug on an old version, we should be able to reproduce it locally (ie have the same code as the user). Our workflow has to reflect that.
The second thing to take into account is CodePush. For those not familiar, CodePush is a mechanism that allows React Native developers to push OTA updates to a specific version of their app. It’s not allowed to push entire new features but it can be useful to fix a critical bug. Let’s take another example: Your app release 1.0.0 was so successful that you decided to release a new version 1.1.0. Unfortunately, some users did not upgrade and are encountering a bug where they can’t log in anymore on version 1.0.0. You could tell each user to upgrade their app but it’s not user-friendly. So we want to be able to send an update to the 1.0.0 version of the app. Our workflow should also allow that.
Let’s recap what our workflow should allow us to do:
Those are our decision drivers.
The first workflow I encountered was as follows:
Your staging environment is always the more advanced one. Every time you want to create a new release, you will “transfer” your work from the staging environment into the production one. So you create a pull request from develop into main.
At first, we liked to have a separate branch for production. It seemed clean. We could easily have the same code as the one live on our local environment (a simple git checkout main). But we quickly realized that for each release (one every 2 weeks), the pull request was huge. It was a pain for the reviewer and did not bring a lot of value.
We realized that the staging app is identical to the production app, but on a different environment. Why should it be on 2 different branches then?
All in all, we did not see a real pro to having two separate branches. The main pain point was the pull request. The reviewer was doing all the work from the previous reviewers.
For me, just for the sake of ease of use, this is a no go on a two-branch workflow.
So now we have a single branch (main) for staging and production. Let’s take our 3 decision drivers and see if it fits.
Now we have only one main branch. I mean, it will be difficult to have it more simple than that.
We need to be able to checkout the code from any past release. Our workflow should then have a mark in time when a release is going live. Fortunately, git has something called tags. When building our production app we can tag the current commit on main with the corresponding tag.
Let’s look at our main branch. First, we just have some commits
On the first release, we’ll tag the last commit to be released.
And again on the next release.
That way even if the current live version is 1.1.0, we can still checkout the code from 1.0.0 and debug any issue.
What about the second decision driver?
We now need to be able to send an OTA update to our 1.0.0. For that, a simple solution can be to create a branch from the tagged commit with the release name.
We now can keep track of which versions have been hotfixed.
That’s the workflow I currently use on all my projects and the one I recommend. It’s easy to maintain and allows us to do what’s needed. Nothing more, nothing less.
There is a small caveat though. That workflow assumes that the main branch is always ready to go live. There are multiple solutions to make sure it’s the case. I’ll go over this in a future article but let’s just say I really like feature flags.