I've been using git for a few years now and I don't see how I could ever go back to TFVC or *shudder* Visual Source Safe. Git makes managing branches and work significantly easier than other source control technologies I've used in the past, but there seems to be some concern or confusion regarding a good branching strategy to use along with Scrum.
I was pointed to this article by a colleague and while I agreed with some of it, some of it just seemed flat out wrong to me. We've worked really hard through several iterations to land on our branching strategy in my current role and it works really well. I figured I'd share it and our reasons for going this route.
Before I start, it's important to point out that we use Azure DevOps for our CI/CD pipelines so we use their Pull Request tool to merge between some branches. If you're using a different tool then you may follow slightly different processes that make sense for that tool. I've done my best to lay out the way we do it using git terminology, but there are definitely parts that are specific to Azure DevOps.
The Basic Premise
The Branches
- main - this branch matches our production environment nearly all the time
- staging - this branch is created only when necessary
- release/<release number> - this branch is our iteration branch and contains all changes that are expected to be available as a result of our iteration
- <task branches> - these branches are transient, abundant, and created as necessary to complete individual pieces of the iteration (i.e. stories or bugs), but could also be created as sub-task branches to work on tasks within stories or bugs
The Strategy
- Create a branch from main called release/<release number> where <release number> represents the next version number
- If 1.0 is in production you'd create a branch called release/1.1
- When a new story is started in the sprint, create a branch for it, named something meaningful to the story (I'll refer to this as the story branch the rest of the way)
- If the story is about accepting online orders you might name the branch online-orders
- Every task in the story could have a separate task branch named whatever you want (I'll refer to these as task branches the rest of the way)
- If the story to accept online orders has separate pieces to accept credit card payments and invoices you might create a branch called accept-credit-cards when you start working on that task and another developer might create a branch called accept-invoices when they start working on the other task
- As the work is completed on each task, the task branch is merged into the story branch
- Depending on team dynamics, you could use a pull request to do this, but on our team we just review our own changes and make sure there are no merge conflicts
- It doesn't matter what merge type you use (fast-forward, rebase, etc.) because these commits will be squashed in the next step
- When a story is complete, the story branch can be deployed to the dev environment to validate everything is working together as expected
- When the story is ready to be tested (this varies by team and project, but on our team a story is ready to be tested when the developers feel confident that it could safely go live right at that moment; in other words, we don't "throw it over the wall" and wait to see what the tester finds), we merge the story branch into the release/<release number> branch and deploy that branch to the QA environment
- We do this using a pull request in Azure DevOps
- The important part here is to do a squash commit and provide a useful commit message for the work that was done on the story branch (e.g. "Made changes to allow online orders to be placed via credit card and invoice")
- If the tester finds something, create a new branch based on the release/<release number> branch, fix the bug, and repeat step 6
- At the end of the sprint, only completed stories are in the release/<release number> branch (that's important)
- Create a staging branch from main
- Perform a squash commit from release/<release number> into staging and make the commit message the release number
- We do this via pull request in Azure DevOps and it allows us to use the release number as the title of the pull request and then we fill in the individual commit message from the story branches as the description of the pull request; that way we have the detailed information from each story as well as the release number in the commit log
- This is also the time to associate work items with the commit if your team does that
- Once the staging branch has been updated, the release/<release number> branch can safely be deleted and the staging branch can be deployed to the staging environment
- When the next sprint starts, create a new release/<release number> branch based on the next release (so if our previous release was 1.1 this branch would be named release/1.2) from the staging branch (that's important)
- When it's time to deploy the code to production, rebase the changes from the staging branch onto the main branch
- Rebasing one branch onto another applies each commit from the source onto the destination, but there's only one commit to staging that gets rebased onto master at this point
- Since we create the new release/<release number> branch from the staging branch we already have the same exact commit (with the same SHA1) in the release/<release number> branch that is now in main
- Delete the staging branch and repeat this process starting at step 2 for every sprint until the project is complete