A branching strategy in adequacy with the Scrum model.
Scrum is a great framework. If you do not know it, you should check it out and then come back here ! Defining a branching strategy suitable for Scrum is not so simple, here is a proposal.
Scrum is designed to develop product among iterations (called sprints). The sprint backlog contains stories which represents the work to do in the scope of the sprint. At the end of the sprint the Product Owner will decide if the proposed product increment is accepted or not.
It is common for Scrum teams to have strong difficulties with the branching strategy to apply to the product they are building. As a matter of fact, the expected level of collaboration on each story, the need to propose a shippable product increment, the different stories that may or not be sent in the end, all these specificities of Scrum make it challenging to find the proper way to stay clean and consistent with the code repository. That’s the reason why the branching strategy must be in adequacy with the Scrum model.
In this article we will present a branching model for Scrum using Git, oriented towards team collaboration and continuous integration. If you doubt this is worth investing some time, you may be interested in why software engineering matters.
The master branch
The development will start from your reference branch. You call it the master branch, the develop branch or by any other name. It is the one that reflects the level of code approved by the Product Owner as being ready for the production. In the scope of this article we will call it master branch.
The sprint branch
The sprint branch aims at containing commits of completed stories, the ones that will be presented to the Product Owner during the sprint review. This branch can be seen as the “ready for demo” branch.
At the beginning of each new sprint, its sprint branch is created from the master branch (i.e.: sprint/S01). This operation can be managed by the Scrum Master for instance. It is also helpful to tag the branch at this point (you will understand why in ‘failed stories’).
The story branch (and the relevant-atomic-clean approach)
As soon as the sprint starts, a dedicated branch for each story will be created from the sprint, following a naming convention that has to be decided. It can be of the form story/S01S01, story/S01S02… Which would be read sprint 01/story 01, sprint 01/story 02, …
The important point here is that the story branch is fully part of the model. It can be seen as a reference branch for the development of a specific feature. Even if it is a short-lived branch (like described in the popular Trunk Based Development model), it should not be considered as a "garbage" branch. More precisely, commits that will be pushed to the remote story branch should be relevant, atomic and clean. For instance, the remote story branch should likely not contain commits description like ‘working on story 2, WIP’, or even ‘fixed typo in MyClass.java’.
The cleaner the commits are in the story branch, the better the code history will be.
Branching strategy for Scrum, the big picture
Commits and interactive rebase
Of course, in the day-to-day work, the developer working on a story wants to commit his ongoing work, like a checkpoint so he knows he can restart from this point if anything goes wrong after. While implementing the story, this can end up with several commits.
Before pushing the commit(s) to the remote story branch, he should ensure that these commits are atomic and relevant. Performing an interactive rebase ("rebase -i") will allow to squash relevant commits between them and write a meaningful commit message if needed. The dev team can agree on the commit message format, like : “Story 654 – Switching from log4j to logback”, where 654 would be the story id in the product backlog, followed by a meaningful description. Only when this operation is done, the commit(s) can be pushed.
In a story branch, a pushed commit is a commit ready for code-review.
Working together, and merge-request for code review
Once a commit has been pushed, the dev team member should create a pull-request (also called merge-request) to the sprint branch in order to ease with the code review. There is no need to wait for the story to be completed to perform the merge-request: the code-review may start before the other parts of the story are implemented. But of course the story will not be merged in the sprint branch before the implementation is complete (i.e. the story is put in the column DONE in the sprint backlog).
TIP: GitLab (for instance) will prevent the branch from being merged as long as the merge-request’s title starts with “WIP:”
The code-review phase can generate one or more additional commits that cannot be squashed to the code being reviewed. That’s an acceptable state.
If several developers are working on the very same topic that requires sharing portion of code being developed, it might not be a good idea to work directly on the story branch because it would break the relevant-atomic-clean approach. Instead, they can consider branching from the original story branch or even forking the project. That way others can contribute to this temporary branch which can be interactively rebased at the end, creating new nice and beautiful squashed commits. 🙂
When the story finally meets the done criteria and the code-review is completed, it can then be merged in the sprint branch. That means the story is “ready to be shipped” from the development team point of view. All tests (unit tests and integration tests) should be rerun at this stage.
The code level in the sprint branch is the one that will be presented to the product owner during the sprint review ceremony. Uncompleted stories should not be merged into the sprint branch.
It is important to keep in mind that stories in a sprint branch (i.e. in the “done” status) has the purpose of being accepted by the Product Owner. To maximize chances, it is essential to work as closely as possible with the Product Owner. The situation of a story seen as done by the dev team and finally failed at review time should be extremely rare.
The review during the demo
During the sprint review ceremony, the product owner will review all stories of the sprint backlog. He will decide whether each story passes or fails. If you are running Scrum in a strict mode, one failed story will fail the sprint! The consequence is that none of the stories will reach the master branch at this point. The reason is that the product owner acknowledges the stories being ready for production on the very code level being presented during the review. If a failed story is removed from the sprint branch afterwards, the resulting level of code has never been reviewed and thus should not be accepted.
If all stories have passed, the sprint can be merged back to the master branch. This operation is called the delivery.
The Git tree representation of the branching model for Scrum (general case)
If the Product Owner has rejected at least one story, the whole sprint is failed. The way to deal with failed stories may vary a lot depending of the failure type and the Product Owner’s point of view on this. There is actually no unique answer, but here is some global leads:
1st case: the failed stories can be fixed easily.
If the failed story can be fixed easily and quickly, the Product Owner can agree on keeping the story for the delivery. The dev team will fix the sprint branch and will rerun all tests. The Product Owner should accept the fix based on the evidence usually required (test results) or a short informal demo.
2nd case: the failed story has to be removed from the delivery.
That’s the touchy case that should be handled carefuly. Here are three different kinds of approaches:
1/ Commit(s) of failed stories are reverted in the sprint branch.
This is an easy operation if story’s commits are atomic. But if this kind of operation is often done, this will lead to a dirty code base.
2/ A new sprint branch is created from the original one (like ‘sprint/01-take2’), identified by the tag created at the beginning of the sprint. All story branches of approved stories are merged into this new branch.
This is a tedious operation but the code history will be cleaner. This is a safe operation as long as the sprint branch does not contains special commits (last minute fixes for instance). That’s easy to forget those commits, be careful!
3/ A new branch is created locally from the actual sprint branch (like ‘sprint/01-rebase1’) and interactively rebased upon the original sprint branch (identified by the tag created at the beginning of the sprint). That way, only accepted stories are picked and there is no chance to forget any relevant commit.
This is the safer method, but this will generate new commits due to the rebase nature, losing the merge information of the story branch.
In all cases, the strategy should be proposed to and accepted by the Product Owner. He can decide that this is too risky and reject the product increment as a whole. The stories will then be integrated into a new sprint.
Continuous integration: some keypoints
This branching strategy works well for continuous integration.
On story branches
- Every single commit pushed to the story branches triggers at least the run of the unit tests. That’s one of the reason behind the idea of not pushing partial/work-in-progress code to the story branch. If it can technically trigger more tests, it should. The earlier the tests are run, the better
- When the story development is complete, the integration tests should run. The trigger to this operation can be defined in several ways (a tag on the commit can be an option for instance).
On sprint branch
- Once the story is merged in the sprint branch, the whole pipeline should be run (including the integration tests). This will ensure that no story broke another one.
I have implemented this Scrum branching strategy successfully in several companies. Of course this is not the only possible solution, but from my point of view it takes a good compromise between respecting Scrum interactions and maintaining a clean code base. It can cope correctly with the different scenarios a Scrum team can encounter.
That is however not the end of the story: not all code changes are necessarily done in the context of Scrum. Maintenance operations and urgent fixes may follow an alternative (and complementary) process. More on this in a future article.