The ins and outs of a good Git workflow
We’ve yet to give our readers a “peek behind the curtain” and discuss some of the ins-and-outs of our engineering practices at MojoTech. In this post, I’d like to begin to talk about some of our practices and the way we work from an engineering perspective.
There are many things we care deeply about that go a long way to making a software project run smoothly, and our usage of Git as a source code management tool is one of them.
The following is a section directly from our internal MojoTech engineering playbook and is presented in its entirety.
Git will be used as the source code management (SCM) tool here at MojoTech. Our projects are primarily hosted under the MojoTech Github organization, although some projects occasionally have other SCM hosting tools.
MojoTech has some specific usage patterns around Git that allow us to work as a team effectively and collaborate on projects in an efficient manner.
Git has no definitive rules on how branches must be named, and no branch name holds any special significance inherently. However, the community standard is to use a branch called
master as the default, and as such a
master branch is generated for you when you create a new Git repository via
For MojoTech projects, the
master branch should be considered the "always working" branch and will be the main place feature branches are merged into when they have been code reviewed and approved.
One of the nice benefits of Git is that creating, working with, merging, updating, and deleting branches is trivial, cheap and easy. As such, developers are encouraged to create branches at will as they work on various features, bugs, and tinkering. Feel free to create branches as often as desired, and work out of those feature branches in your normal daily workflow.
Developers should never directly commit to the
master branch if possible. Instead, always follow the normal process of:
- Create a feature branch off of
- Make changes to implement the feature
- Submit a new Pull Request for those changes
- Have those changes peer code reviewed
- After approval, merge the Pull Request into the master branch
New feature branches typically should be created from an up-to-date
$ git checkout master
$ git pull $ git checkout -b <new_branch_name>
There are occasions when you will create a new branch from a non-master branch as well. For instance, a developer may want to try several solutions for a problem by creating a new branch for each one from a common starting point.
To keep some form of consistency and legibility among open branches, developers should name their branches in the following format:
Some examples follow:
$ git checkout -b cpj/add_metrics_tracking
$ git checkout -b cpj/fix_remember_me_input
$ git checkout -b cpj/update_login_endpoint
This naming structure allows all developers to easily see which developer “owns” or created a particular branch, as well as gives general insight into what code changes live in that branch. Some developers also like to include the Pivotal Tracker ticket number in their branch name.
$ git checkout -b cpj/add_reset_password_feature_1234567
As engineers work on a feature branch, changes from other developers will most likely be merged into the
master branch during that time. Developers should get in the habit of semi-frequently pulling down the latest changes to
origin/master to their local
master branch, and then rebasing their feature branch off of the latest changes to stay up-to-date.
In simple terms, a
git rebase master from a feature branch, will unwind the local commits made on the feature branch, update the root of the feature branch with the commits pulled down from master, and then re-apply the local commits from the feature branch on top.
This may result in some conflicts as the commits are reapplied, and the
git rebase process will stop if conflicts occur, tell you which files had conflicts, and allow you to fix those conflicts and continue on the rebase process with
git rebase --continue.
If the changes from
master were to files or sections of code unrelated to your feature branch, the rebase should be able to automatically re-apply all the local commits without conflict.
There will be times, usually after a code review process or while “cleaning up” a branch before you submitting as a new Pull Request that you will need to do an “interactive” rebase. The first few times you need to do this process, it makes sense to sit with another Mojo and walk through the process. We also made a video describing the process: https://www.youtube.com/watch?v=8ZXExlHBPoY
Git commits, like branches, are also cheap and easy to make. As such, developers should get into the habit of creating frequent commits along the way, instead of making one huge “big-bang commit” at the end of a feature’s development cycle. Smaller commits are much easier to code review, rollback, and combine together later if needed, rather than the alternative of having to split a single large commit into multiple smaller commits.
Unlike Subversion commits which are immediately pushed to the repository, Git (which is a distributed system) commits are kept local to the user/machine who made them. Nothing is distributed to the central repository unless the developer specifically chooses to do so via
Git commits should be kept atomic, which means each commit should be self-contained, related, and fully-functional revolving around a single task or fix. Git makes this task easier by allowing you to save partial file changes in a commit. This means you don’t need to commit all changes to a single file at once. Instead, you can stage them in “hunks” that can contain many commits that reflect several changes to a single file.
“Self-contained, and related” means that commits should contain all changes and files related to the commit in question, and only those changes. A commit should not, for instance, contain code for adding a new input field to a form and also code for fixing a bug in a separate file tweaking a cache timeout setting. These should be two separate commits, not one single commit.
“Fully-functional” means that each commit along the way should be a fully buildable/passable stopping point. If anyone were to check out the Git history at a specific commit along the way, the application and code should still be fully functional, runnable, and passing all test suites.
Git commit messages should contain a shorter, succinct first line, followed by a single blank line, then any additional supporting descriptive paragraphs as desired.
Often times, this single short line is all that is needed to describe what is contained in a commit. Other times, more supporting information and documentation are required, and the developer can feel free to use as much additional space as desired in the commit body.
Here is an example of a single line commit message:
Update the ASSET_HOST environment variable
Here is an example of a more lengthy descriptive commit message:
Fix regression bug with Remember Me feature
There was a bug ticket filed here #TK134AFKR where a user was unable to "uncheck" the Remember Me checkbox on the login screen while.
After research, it appears that certain older mobile browsers were not able to use the native JS touch library code we were using so a polyfill method was added to make the feature work on any older mobile browser not supporting native touch calls.
Some developers also like to “tag” their first-line message as well for easier scanning the logs
[fix] Remove login timeout threshold`
[docs] Update deployment process documentation
This is optional unless your project’s team leader decides to require or exclude it as a project policy.
A lot of times, tagging commits makes it easier for peer reviewers to quickly know your intent behind each commit. For example, if a commit is tagged
[fix] but also introduces a new piece of functionality, then that would be a good indicator that the commit isn't' very atomic.
A “Pull Request” (PR) is the term used for a single or set of commits being marked as ready to review and merge into
master. When a PR is opened, a developer is signifying that they believe the feature being worked on is complete and ready to be peer reviewed and tested by other developers.
Before submitting a PR, the developer should rebase their feature branch against
master to get the current changes at the time. This makes merging the feature branch PR into
master easier when the PR code is approved.
Commits should be ordered sequentially for easy review and grouped logically. Any “general supporting” commits should be reordered to the beginning of the commit sequence, followed by the detailed feature commits.
Commits should be squashed together (via
fixup during an interactive rebase
git rebase -i) if required. This means if one commit adds a new line of code change, then a commit a few commits later changes how that first commit worked, the final changes should be combined into where the new code was introduced as a single commit.
All developers on the project should actively participate in peer code review of open Pull Requests. This keeps the whole engineering team “in the loop” and aware of what other developers are working on and what moving pieces are changing in the application. Code review also is beneficial to everyone as more eyes on the code base will allow more bugs to be caught ahead of time. Developers are encouraged to leave specific comments and questions on changes, logic flow, business requirements, etc… related to the PR’s code.
When code review is complete, and the developer team signs-off on approval of the code, and the code passes any required test suites, the PR request will be merged into the
master branch, and the PR will be formally "closed" at that time.
Guides & Links
Originally published at blog.mojotech.com on August 15, 2017.