Git Best Practices for Branching and Commits
Git is a very flexible versioning tool created in 2005 by Linus Torvalds, the creator of Linux, to help manage versioning and contributions from many developers around the world. It can be a very powerful tool for you if you learn some best practices. In this article we will cover:
- Start with good coding processes
- How does git work?
- How to decide when to create a new branch?
- When should I make a commit?
- What makes a good pull request?
Note: It is always best to approach git from the standpoint of the goals and standard practices of your project and team. Git should be a tool that enhances your current processes, not makes them harder. If you don't have good processes, to begin with, you need to adopt some. Below is a brief overview of general coding best practices.
Git alone will not solve your problems, if you don't understand why it is useful, and how to use it to your advantage. Let's start by taking a step back and reviewing standard application development processes.
Typically, when an application is first built, it goes through initial development, and then once it has been reviewed and tested on a local or staging server, it is deployed to production (for customers to use). Once code is in production, developers may want to continue adding features, maybe fix some bugs, and refactor some code. But there are some challenges to consider:
Editing an application in production is dangerous (all mistakes become live), inefficient (you should not debug with real users), and sometimes not even possible (if it was compiled) or at least very difficult (if it was transpiled and minified, for example).
Working in teams
Working on the same codebase with multiple people is difficult and slow without a single source of truth to compare against. Who's code should be deployed? How do you work on it at the same time and prevent conflicts?
Code Reviews Code reviews often go back and forth before it is ready to go. With each of these iterations, it is tedious to keep track of changes between code reviews. Running a "diff" on every file, or simply reviewing every file every time is just not practical in most cases.
In order to determine how git can help solve these problems, we need to understand how it works. When you make changes to a file, in order to save those changes, you first stage the changes you want to save, and then commit them to your git history. For example:
When you make a commit, git stores a snapshot of the files (or a reference to existing files if it wasn't changed) to your local computer. If you want to store these changes to another server, you have to do a
Git also allows you to have multiple "histories" by allowing you to create multiple branches. Each branch is just a different chain of commits. You can merge branches by applying the commits that are only on the other branch to the branch to which they need to be added.
When I first start a project, I am usually the only one contributing. If that's the case, and the project has not been deployed to users yet, I will work off of the
main branch and push commits directly to that branch.
Once the code is in production, I want a local copy that I can edit and test, and a copy that represents the exact code that is deployed. This is because I want to be able to make hotfixes (quick bug fixes) to the production code at any time, as they will always take priority over new features. The application has to always stay functional for the live users. In this case, I will create a
development branch. This represents changes that will be reviewed and tested before they are merged into
main and deployed (usually all in one step using CD or Continuous Delivery tools like Github Actions).
If multiple developers are working on the same git repository, or at least reviewing code before it is deployed, we will create feature branches and a pull request for each feature. This allows each new feature to be reviewed for functionality, implementation, and best practices.
It's also good practice to establish a naming convention for branches, for example:
A new commit should be made when you make a change to the code that represents a new functionality or a bug fix. It is useful to think of it with the end in mind. If you are looking through a commit history, and want to go back to a previous version of code, you will only have the commit messages to look at. Making them as useful as possible will make this easier. Mixing a bunch of unrelated changes into one commit will make this a difficult challenge.
It's also good practice to establish a convention for commit messages, for example:
add contact form
fix submit button not submitting
A good pull request makes it as easy as possible for the reviewers. It is best to keep these narrow in scope, and reasonably small, to make testing and review more effective. Some things that make pull requests difficult to review include:
Unrelated Changes Keep the pull request scope narrow. If you are adding a contact form, don't also change the style of the navigation bar, and adjust a map component.
Whitespace Changes to Unrelated Code Linting code that is not part of the functionality is tedious to review and should be done separately. Ideally, all code is linted with the same rules from the very start before anything is pushed.
Unclear pull request title and description Make sure the pull request includes a clear description of the changes made and the desired outcome. It helps to think of how the pull request will be reviewed. Typically the following is done, in order:
- The code is pulled and tested by running the application. If the code doesn't even run, the reviewer will request that it be fixed before continuing with the review. Make sure to always test your code by running the application and testing as a user.
- Test the application for functionality. If the pull request is for a contact form, and the contact form doesn't actually work, then the reviewer will probably request a fix before continuing.
- Review the code for best practices, like making sure the DRY (Don't repeat yourself) principle is followed. Also making sure the code is not messy and will be easy to maintain in the future. This part can be more subjective, but is also a great time to learn different and better ways of doing something.