Have you ever looked at your git log to get a sense of a branch's status only to be met with a wall of ticket numbers? Or have you had to determine a version for your upcoming release while manually maintaining a changelog? One of the best things we can do for our future selves is to write good commit messages. They allow us to keep track of changes and communicate the status of the repository to other team members. A popular approach is to add ticket numbers or other identifiers to the commit message. That will help us eventually track down what is happening in a repository but that kind of information is really meant for your project and issue tracking integration. Alternatively, adding a detailed commit message is also helpful but reduces our ability to scan the log to get our overview if too verbose. To make these tasks easier on ourselves we can follow a few simple rules.
To The Rescue
Conventional Commits provide us with a specification centered around helping us write a detailed commit history that is easy for both humans to understand and other systems to digest. This more structured history gives us the ability to get that overview we were looking for at a glance with the added bonus of allowing us to programmatically generate changelogs and determine our next semantic version with additional tools.
Below is the Conventional Commit format and structure:
<type>[optional scope]: <description>
Though this looks like it would make commits more complicated the only required pieces of information are the type and description. The type describes the intent of the changed code and the description summarizes the change. There are even specifications for formatting descriptions but that isn’t mission-critical to our current discussion. At the end of the day, we are just labeling or categorizing our commit. Is it fixing a bug? Use the fix type. Does this commit include a new feature? Then use the feat type. Using these two types alone already adds a layer of valuable information to our commit messages. If you’d like to detail what part of the application or system is being updated, reference that section in the scope slot. Not only will you quickly add more information to the commit message but tools like Lerna can use this content to track changes of packages in a monorepo. The body stores a more detailed summary of our changes and the footer is a great spot to call out related tickets, references, and/or acknowledgments.
More On Types
Thanks to tools like @commitlint/conventional-commits (based on the Angular convention) we have other types available to us to develop some granularity in our commit logs. Though these can be modified to meet your specific needs, below is a list of common types we can use to categorize our commits:
● build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
● ci: Changes to our CI configuration files and scripts (example scopes: Circle, BrowserStack, SauceLabs)
● chore: A change that doesn’t have an effect on code and doesn’t meet the other type requirements (changing .gitignore)
● docs: Documentation only changes
● feat: A new feature
● fix: A bug fix
● perf: A code change that improves performance
● refactor: A code change that neither fixes a bug nor adds a feature
● test: Adding missing tests or correcting existing tests
Here are a few examples of how these types can be used with the conventional commit...convention:
● feat: add a new feature
● fix(utils): fix a bug in utils
● feat!: breaking change in API
● chore(deps): update dependencies
With the addition of these other types to the start of our commit messages, we’re able to provide a wealth of information to our commit log.
I know what you’re thinking. I’m already following guidelines and specifications to complete my functional code, why should I use another one just to write commits! There is a bit of a learning curve and adjustment, at least there was for me, when I started using this convention. I suggest starting by just adding the feat or fix type to the start of your commit messages. Those 2 types should meet the majority of our use cases. Then start sprinkling in others like docs, test, and chore if the production code isn’t changing. There are also great tools and extensions we can use to help us write commits using this convention. Here are a few extensions for our IDEs:
Visual Studio Code
● Conventional Commits
● Commitizen Support
● Conventional Commit
There are also command-line tools like this one by commitizen that help us generate a commit message through a guided workflow and a commit linter to make sure the project adheres to the specification with something like husky and commit hooks.
After adopting just the required pieces of the standard, type, and description, I noticed that my commits became more deliberate and methodical. The footer stores ticket references for integration and pipeline purposes and I occasionally use the body to summarize a large feature or to describe breaking changes. Repository maintenance and git operations became easier and later phased out manual changelog management by using conventional changelog. Something that is worth pointing out too is that the entire team doesn’t have to conform to this standard either. If you aren’t opposed to squashing commits then team leads can take care of maintaining the standard. Check out Conventional Commits to read more about the specification to see if this is right for you or your team. There is an excellent FAQ section there that addresses many concerns and has additional information regarding usage with existing projects.