Gitflow and CI/CD at Pistil
A couple of weeks ago I joined Pistil Data, a cannabis data analytics startup. Coming into the engineering team, which is very much being formed, I noticed how every commit was being done directly to the master branch, and every deploy to dev, staging and prod, from the master branch as well. The team being small, this does make sense, but we’re looking into expanding it, and as we have more people working on the same codebase, we’ll need a better structure and flow around branches, pipelines and deployments, hence Gitflow.
Gitflow
You should read Atlassian’s post on gitflow (and the original post), but the basic idea is that a project using git should have the following branches:
masterrepresents production at all timesreleasecode coming fromdevelop, about to be merged intomasterto be released to productionhotfixbranched out offmaster, to fix production bugs. After it’s done and tested, it should be merged back intomasterso the fix goes out to production, and intodevelopso that the next release doesn’t lose itdevelopcontains all ofmaster’s code, and it’s usually ahead. It represents code/features that are going out in the next release. Ideally,developshould represent your staging environment at all times, just likemasterrepresents productionfeature-[\d+]feature branches, offdevelop. The number is a simple reference to your board system (Jira, GH issues, Zenhub, etc). Once the developer finishes working on the feature, they should open a pull request againstdevelop
From the branches laid out above, we decided to drop release, since it adds even more overhead to the release cycle.
Azure DevOps
At Pistil, we use Azure DevOps to host our repositories, our Agile board, and as you’ll see below, our CI and CD pipelines.
It’s very similar to any of the other tools out there, like Github, Gitlab or Bitbucket.
The Plan
All that said, when we set out to implement gitflow and fix the CI/CD pipelines around it, this was the plan:
- the
masteranddevelopbranches should be locked for commits, only accepting pull requests - any commit to
feature-[\d]+branches should trigger a CI pipeline that runs unit tests, and blocks existing pull requests againstdevelopif it fails - any merge into
developshould:- trigger a CI pipeline to build the project’s artifact, and also runs the unit tests. If this pipeline fails, any pull request against
mastershould be blocked - trigger a separate CI pipeline to run the project’s Integration Tests Our Integration Tests take too long to run, and currently we’re accepting the trade-off of having them in a separate pipeline that doesn’t block pull requests.
- trigger a release CD pipeline to automatically deploy the build to the
devenvironment - trigger a separate release CD pipeline to create a release to
staging. The actual deploy must still happen manually (sort of a business requirement for us at the moment, ideally it should deploy tostagingautomatically as well)
- trigger a CI pipeline to build the project’s artifact, and also runs the unit tests. If this pipeline fails, any pull request against
- any merge into
mastershould:- trigger a CI pipeline to build the project’s artifact
- trigger a separate CI pipeline to run the project’s Integration Tests
- trigger a release CD pipeline to deploy to
prodmanually
End result
All in all, the plan has been accomplished, except for the Integration Tests CI pipeline, which were set to run manually.
Below are our default branches develop and master. At the time of writing, we’ve merged and deleted every feature-[\d]+ branch into develop and then into master for the latest release, that’s why there’s no other branch:

Notice the blue badge, close to the develop branch, reading “Branch has policies”. It represents a policy that blocks every pull request against develop if the unit tests have failed. There’s no matching blue badge in the master branch because I still don’t have the necessary Azure DevOps permission to add one to a repository’s default branch, but I’ll add one soon.

Below, the relevant CI pipelines that run unit and integration tests, and build the project:
- 2 for the
developbranch: Integration Tests + (Build + Unit Tests) - 2 for the
masterbranch: Integration Tests + Build - 1 for any
feature-[\d]+branch: Unit Tests

The CD pipelines:
- one that releases the
developbranch automatically to thedevenvironment, and manually to thestagingenvironment - another that releases
masterto theproductionenvironment, manually

And finally, a quick look at how the develop CD release pipeline looks in Azure DevOps:

Next steps
A few things we have left to improve the process and pipelines:
- Azure DevOps has something called Environments which could, I think, replace our current stages on the release pipelines, but from my readings, I couldn’t clearly understand the benefits of going with Environments, so more reading on that is necessary
- A bit outside of AzureDevOps’ scope, but Azure App Services has a “Deployment” section with “Deployment Slots” and “Deployment Center” in there. It’s something worth exploring