A Step-by-Step Guide to the RubyGarage Git and Release Management Workflow
- 4482 views
- 1 comment
Most people don’t like processes and regulations, but one thing is for sure: no team can create a successful software product without following a solid software development workflow.
Having learned the hard way, our RubyGarage team has come up with a clear, transparent, and well-structured Git and release management workflow that helps us build high-quality applications as quickly and efficiently as possible. Our workflow encompasses the best practices of the Extreme Programming practices of the agile software development methodology, so it’s iterative and helps our team deliver products that meet the evolving business goals of our clients.
In this guide, we showcase our RubyGarage Git/GitHub and release management workflow so that you have a complete understanding of the steps we take to roll out prime software products.
Step-by-step guide to the RubyGarage Git workflow
Software development is a multi-stage process that contains lots of small steps. However, making the workflow overly complicated makes no sense, as it might get too difficult for developers to understand, adopt, and adapt. That’s why we’ve made our Git workflow as simple as possible and divided it into the following major steps:
Step #1 Taking the task
To make this guide easier to understand and more illustrative, let’s imagine we need to implement sign-up with email and password functionality in a web application. We’re going to take a close look at how a RubyGarage developer fulfils this task from beginning to end.
Before we start, however, you should clearly understand how we manage tasks at RubyGarage. We use a task management system (Jira) for managing our software product development workflow in compliance with the agile software development methodology.
Each project our team works on has a respective board with several lists:
- Backlog − for tasks that haven’t been included in the current sprint and will be fulfilled during future sprints.
- To Do − for all tasks that must be completed during the current sprint. In other words, this is the sprint backlog. The tasks are added to the To Do list by the product owner and estimated in story points by the whole development team during planning meetings. All tasks in the To Do list should be validated by all members of the development team and need to have detailed descriptions (design, scenarios, and acceptance criteria) before developers can start implementing functionality.
- In Progress − for all tasks that developers are working on at the moment; each developer has to work on a single task from this list.
- In Review − for tasks that are ready to be reviewed by the members of the development team and the team lead.
- In Testing − for completed tasks that are ready for testing by a QA specialist.
- Release − for tasks that have been tested and approved by a QA specialist and are ready for the current release.
Let’s go back to the tasks. To take a new task, a developer needs to complete several sub-steps:
#1: Select the task from the To Do list
Tasks in the To Do list in Jira are prioritized by the product owner, so a developer should select a task with a higher priority. At RubyGarage, we strictly follow the one developer − one task rule, so team members aren’t allowed to work on several tasks at a time.
#2: Assign the task to themselves
The developer needs to assign the task to themselves so that team members can see that this task is already taken.
#3: Move the task to the In Progress list
Once the developer has assigned the task to themselves, they move it to the In Progress list on the board. This step is crucial so all team members can track which task each developer is working on at the moment.
#4: Discuss the proposed implementation of the task
Prior to coding, the developer should carefully think through how they’re going to implement the sign-up with email and password functionality and discuss their potential solution with the team lead or fellow developers. This helps ensure that the developer knows the best way to complete the task and won’t waste time on implementing an incorrect solution.
Step #2 Implementing the task
Before we move to concrete steps the developer takes to fulfil the task and deliver functional code, you need to understand how we work with Git and what branches RubyGarage developers use.
Gitflow as a Git branching model at RubyGarage
At RubyGarage, we use Git for version control and Gitflow (check out the RubyGarage Gitflow guide) for a branching model. Gitflow provides a set of rules and conventions for working with Git, thus minimizing potential merge conflicts and making Git much easier to use.
To facilitate collaboration between developers, we use GitHub − the world’s leading development platform. GitHub helps developers manage, discuss, and review code by means of pull requests. However, BitBucket supports similar functionality, so you can set up the same Git workflow with it too.
According to Gitflow, we use two main branches:
- master, which contains production-ready code that can be deployed to the production server
- develop, which contains code for the upcoming release and which is deployed to the development server
RubyGarage developers use four types of supporting branches to facilitate parallel development between team members, make it easier to track features, prepare releases, and fix bugs in production. The supporting branches have limited lifespans and are removed after being merged into the main branches.
Here are the supporting branches we use:
These branches are created from develop; each feature branch is used to implement a single task. After completing the task, the feature branch must be merged into develop.
To avoid ambiguity, we use a specific naming convention for feature branches − they always begin with feature/ followed by a description based on the functionality implemented by the feature. For example: feature/sign-up-with-email-and-password. Thanks to this naming, team members can easily tell what code each branch contains.
This branch is created every iteration (i.e. sprint) from develop, and when the team rolls out a release it’s deployed to the staging server for testing. A stable release is merged first into the master branch and then into develop.
The naming convention for this branch starts with release/ followed by its version. For example, release/v1.0.1.
This branch is created for handling emergency situations − it allows developers to quickly fix something in production. This branch uses master as the parent branch and merges into both master and develop.
The name of this branch starts with hotfix/ followed by its version. For example, hotfix/v0.1.1.
These branches are created in case the release requires bug fixes. The parent branch depends on the circumstances: it can be either release or develop.
The name of this branch starts with bugfix/ followed by the description of what exactly is to be fixed: for example, bugfix/fix-small-bug.
Version naming rules at RubyGarage
At RubyGarage, we follow semantic versioning for giving versions to the release and hotfix branches. Each version contains three version numbers − Major.Minor.Patch. Here’s what each of them means:
- Major − code is incompatible or contains significant changes
- Minor − code has been changed but the changes are backwards compatible
- Patch − bug fixes have been made
Main Gitflow rules to follow
- Never make commits to the master branch
- One task, one feature branch
- Every pull request must be tested before merging into the develop branch
- New functions must be added only to the develop branch
Fulfilling the task and opening a pull request
Now that you know what branching model we use at RubyGarage, let’s go back to our sample task. Coding the necessary functionality involves lots of steps, so let’s go over each of them in detail:
#1: Creating the necessary branches
The first thing to do is to install Gitflow (here are detailed instructions) and then initialize it with this Git command:
This command initializes Gitflow, creates a new develop branch, and switches to it.
It’s important to select develop as the default branch on GitHub:
Next, the developer creates a new feature branch from develop and gives it a relevant name so that other team members can easily understand what functionality is going to be implemented in it. In our case, the name for the branch can be feature/sign-up-with-email-and-password:
Under the hood, this Gitflow command executes the following Git command:
Note: in this article, we show Gitflow commands and the corresponding commands in pure Git.
#2: Making commits to the feature branch
Having created the feature branch, the developer starts coding. The developer makes lots of commits during the day. It’s important to provide a short message for each commit to describe what it does so that other developers know how to apply this commit afterward. The best practice for writing commit messages is to complete the following sentence: If applied, this commit will…
For our task, a sample commit might look like this:
#3: Pushing the feature branch to the remote repository
At the end of each work day, the developer needs to publish the feature branch and push everything they’ve done to the remote repository with the help of the following Gitflow command:
Likewise, the same command in Git will be:
Pushing commits to the remote repository allows other developers on the team to access them and, therefore, suggest changes if the developer has gone the wrong way. In case the developer hasn’t finished the task during one day of work, they still need to push new commits to the remote repository every day.
When the developer pushes the feature branch to the remote repository for the first time, they need to open a new pull request (PR) on GitHub. This is done by clicking the Compare & pull request button in the notification on GitHub:
#4: Opening a new pull request
When opening and managing a pull request, the developer needs to apply relevant labels. Though there are default GitHub labels, software development teams can create their own labels and apply them to their pull requests. To avoid misunderstandings, our RubyGarage team uses a specific set of labels in our GitHub workflow:
- work in progress (color: fbca04) − A developer is working on the PR.
- needs review (color: 1d76db) − A developer has made the final commit and the PR needs to be checked by reviewers.
- reviewed (color: 0e8a16) − The PR has already been reviewed.
- broken tests (color: d93f0b) − The PR has failed to pass automated tests on the continuous integration server.
- conflicts (color: d93f0b) − There are conflicts in the PR.
- deprecated (color: 666666) − The PR contains code which is already deprecated or no longer necessary.
- breaking changes (color: d93f0b) − The PR contains significant changes to the API interface architecture.
- paused (color: 666666) − The PR is put on hold, for example in case there are more urgent things to do.
- needs discuss (color: c9208b) − The PR requires discussion with the product owner or the whole team.
- needs rework (color: 006b75) − The PR requires changes.
- release (color: c9208b) − The PR belongs to the release branch.
- :warning: DO NOT MERGE (color: b60205) − The PR is correct but for some reason it mustn’t be merged into the respective branch.
- :attention: HIGH PRIORITY (color: b60205) − Denotes high-priority pull requests that must be reviewed and merged first.
RubyGarage developers also use additional labels that describe the reviewer and status of the pull request:
- ✓ XY (color: 0e8a16) − Tells that a reviewer has approved the PR.
- ✕ XY (color: d93f0b) − Indicates that a reviewer hasn’t approved the PR.
In these labels, X and Y are the reviewer’s initials. For example, if a developer’s name is John Smith, his additional labels will be ✓ JS and ✕ JS.
Now that you know about the labels used by RubyGarage, let’s go back to our example. The developer needs to apply the work in progress label to the pull request and provide a relevant name and description. As far as the name of the pull request is concerned, it has to include the following information:
- Type of the task (feature, bugfix, hotfix, etc.)
- Task number in Jira
- Task name
This is how it looks for our sample task: [feature] 123 - Sign Up with Email and Password.
In the task description, the developer will see a checklist template that by default appears on the Open a pull request page for all RubyGarage projects. The checklist itself has to be completed in the course of working on the pull request, so at this stage the developer needs only to add the task name, description, and URL to Jira:
A detailed pull request description provides members of the development team with complete information about each pull request, which is particularly important for reviewers as they need to fully understand what the code does to know if it’s written correctly.
The RubyGarage pull request checklist includes two main sections: for developers and for reviewers. The checklist is designed to be intuitive so that everyone can quickly go through the most important aspects of code quality:
REPLACE THIS COMMENT BY THE TASK NAME
REPLACE THIS COMMENT BY THE DESCRIPTION OF THE TASK FROM THE TASK MANAGEMENT TOOL
REPLACE THIS COMMENT BY A LINK TO THE TASK
Checklist for the author of the Pull-Request
- The commit message follows guidelines
- I tested the changes in this pull-request myself
- I have performed a self-review of my own code and all items from Checklist for the Reviewers was passed
Checklist for the Reviewers
- The code follows the style guidelines
- All the linters are passed
- The implementation corresponds to the use case which described in the task
- All the gems and libraries are used correctly
- Design patterns are used correctly
- All the new features have documentation and an additional information for README.md was provided
- There is no redundant or commented code
- The code is readable and understandable
The code follows these principles
- DRY (Don't Repeat Yourself)
- KISS (Keep It Short and Simple)
- YAGNI (You Ain't Gonna Need It)
- SOLID (SRP, OCP, LSP, ISP, DIP)
- The database structure corresponds to normal forms
- The migrations have DDL (Data Definition Language) and no DML (Data Manipulation Language)
- There are Unit tests
- There are Acceptance or Request tests
- All the tests have test-cases for positive and negative cases
- Test Coverage is greater than 90% in general and 100% for the critical functionality
- All the tests are passed successfully
- There is verification of the data entered by the user regarding to use case
- There is user authentication for the feature
- There is user authorization for the feature
- The passwords are encrypted
- The tokens extracted to the environment variables or encrypted file
- There are no SQL-injections
- The controllers use only allowed parameters
Once the pull request has been created, the checklist will be displayed as bullet points to tick off:
Every time the developer pushes new commits to the remote repository, the pull request is automatically uploaded to the continuous integration server (such as CircleCI), which runs:
- Unit and functional tests that the developer writes for every feature they implement; the continuous integration service also shows the test coverage in the current pull request.
- Linters (such as Rubocop, Bundler-audit, Rubycritic, Brakeman, and Rails Best Practices), which perform static code analysis to detect code quality issues.
Step #3 Validating the pull request
As soon as the developer has finished working on the pull request, they need to have it validated by the team lead and other developers on the project. This step is subdivided into several processes:
#1: Preparing the pull request for review
Before the pull request can be sent for review, the developer has to run through the checklist and make sure that it meets all requirements. If something is wrong, for example if automated tests fail or linters reveal bugs, the developer has to fix all these issues. Sending pull requests that don’t meet the requirements in the checklist is strictly forbidden.
If everything is correct, the developer replaces the work in progress label with needs review on GitHub and, in parallel, moves the task card from the In Progress to the In Review list in Jira.
#2: Reviewing the pull request
Reviewers go through the checklist and examine the code; in the end, they have to decide whether to approve the pull request. If a reviewer doesn’t approve the pull request, they should request changes (or ask a question) on GitHub and add their personal label (✕ XY). In our case, it looks like this:
If the developer agrees with the requested changes, they carry out fixes in the pull request. To do that, the developer creates separate commits and pushes them to the remote repository. The use of separate commits allows other developers to check only the latest changes without wasting time checking the code they’ve already reviewed.
When the developer has made fixes, they add the needs review label on GitHub again so that other developers on the team know they can take a look at the pull request and check what changes (if any) have been made.
If developers review the pull request and approve it, they put their personal ✓ XY labels. The last reviewer should remove the needs review label and apply the reviewed label.
Now the developer can see that their pull request has been validated by other developers on the team:
#3: Merging the pull request into the develop branch
Now that the pull request has been reviewed and approved, the developer needs to merge the feature branch into the develop branch.
To do that, they first need to squash all commits into one; there are plenty of ways to do this, but our team leaves this to GitHub, which can automatically squash commits and merge them into the develop branch:
The developer simply clicks the Squash and merge button and edits the description of the squashed commit. In the first input field, they need to review the correctness of the commit message:
In the second input field, the developer replaces all commit messages with a new relevant task description:
The rest is simple − the developer clicks the Confirm squash and merge button and GitHub automatically squashes all commits into one and merges this squashed commit into the develop branch.
The final step is to remove the feature branch:
After that, the following record will appear in the commits log:
Also, it’s important to deploy the develop branch to the development server.
In the end, the developer moves the task to the In Testing list in Jira so that a QA specialist can see that the task has been finished and can start checking it for bugs and errors.
Step #4 Testing the develop branch
A QA specialist takes the task from the In Testing list in Jira and tests the implemented functionality in the develop branch on the development server. The QA performs the following tests:
- Smoke tests to find out whether critical functionality works as expected
- Sanity tests to check if the implemented feature works as expected
- Regression tests to make sure that the previously implemented functionality works correctly after the new feature has been added
If the QA specialist doesn’t find errors or bugs, they move the task forward to the Release list, thus indicating that the feature is ready for inclusion in an upcoming release.
If, however, the QA specialist does find bugs, they write a bug report and add a new bug fixing task to the To Do list in Jira. Needless to say, the bug fixing task has to be fulfilled by the developer who implemented the feature.
The bug fixing task must be handled just like any other task, so the sequence is the same as we’ve described above.
Step #5 Creating a release branch
Since the RubyGarage team follows the Scrum framework, each team has to release production-ready code at the end of each sprint.
To do that, the team creates a release branch from the develop branch (which already contains all PRs merged throughout the sprint, including our sample task of implementing sign-up with email and password):
The same command in pure Git looks like this:
Also, the release branch needs to be published to allow developers on the team to make commits to it (for bug fixing or updating):
Here’s the same command in pure Git:
To make sure the release performs correctly and contains no bugs or errors, it has to be deployed to the staging server and checked by the QA specialist, who runs smoke, sanity, and regression tests.
The QA specialist reports if they’ve found any bugs in the release, and the whole team decides which of them can be fixed quickly. Suppose there’s a bug in our sign-up with email and password feature; to fix it, the developer creates a new bugfix branch:
Here’s the command in pure Git:
Now the developer can make new commits to the bugfix branch and then publish the branch:
Let’s take a look at the same command with pure Git:
When the issue has been resolved, the developer needs to create a pull request and merge the bugfix branch back into release:
Here are the same commands in pure Git:
After that, an updated release branch must be re-deployed to the staging server and tested by the QA specialist one more time to make sure everything works correctly.
Step #6 Finishing up a release
After building a stable release and holding a demo meeting, the development team can finally deploy the release to the production server. To do that, the developer uses this command:
This single command replaces the following commands in pure Git:
It merges the release branch into the master (tagged with a relevant version number) and develop branches, while the release branch is automatically removed. Then the developer pushes the master and develop branches and tags to the remote repository:
Step #7 Fixing the master branch
When the team has deployed the release to the production server, the QA specialist needs to make sure the code runs correctly and there are no errors. It’s also possible that end users or support managers report errors in the live production version of an application. Needless to say, such errors must be fixed immediately.
At RubyGarage, the task of fixing bugs in production is assigned to the most experienced developer on the team or the team lead. They create a hotfix branch and add its version:
This command in pure Git looks like this:
When the work is done, the developer merges the hotfix branch back into the master (tagging it with the hotfix version) and develop branches; after that, the hotfix branch is removed. To do this, the developer uses the following command:
This single line in Gitflow replaces several commands in pure Git:
After that, the developer has to push the changes to the remote repository with these two commands:
Finally, the master branch must be re-deployed to the production server.
Our product development workflow is simple, clear, and focused on quality. It perfectly fits the Extreme Programming methodology that RubyGarage follows and helps us deliver secure and functional code after each iteration. At the same time, our team keeps up with the latest advances in the world of software development and project management, so we’re always eager to adopt new efficient practices.
To stay updated on how RubyGarage developers work and what technologies our team uses, subscribe to our newsletter.
Subscribe via email and know it all first!