![thumbnail](/assets/images/posts/conventional-commit-messages/thumbnail_conventional.jpg)
While I was preparing my personal crypto portfolio manager mobile application for my thesis work, I aimed to follow a proper software development structure in which version control took a highlighted part. Creating a timeline of our code is not only about saving the progress—it also serves as a kind of documentation system.
The problem was, most of the time I struggled with writing commit messages. I didn’t know when and what to commit and with what kind of modularity. So, I wondered if there was an adequate way to give a title to the changes I made in the code. That’s when I stumbled upon conventional commit messages.
Table of Contents
- What Are Conventional Commit Messages?
- Type
- Scope
- Breaking Changes
- Description
- Body
- Footer
- Why Conventional Commit Messages Make a Difference
- Tools
What Are Conventional Commit Messages?
Conventional commit messages are heavily inspired by the Angular commit message format. The idea is to have an explicit commit history, which makes it easier to write automated tools on top of. It helps create a concise, well-structured system that also provides better readability.
Its abstract structure mainly focuses on highlighting the type, the scope, and the description of the changed code:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
When using this approach, we try to strive for simplicity. It’s a good practice to use lowercase in the logical elements of the message.
Let’s break each part down, so you can get a better understanding of how to use conventional commit messages properly.
Type
Types are required to use, since, as it will be cleared out in later sections, an automated tool can define the type and scale of the change based on these keywords. There are certain defined types that are allowed to be used when implementing this kind of committing:
feat: A new feature for the user or a significant change to existing functionality.
feat(login): add social media login options
fix: A bug fix for the user or a correction to existing functionality.
fix(nav): resolve issue with menu not collapsing on mobile
chore: Routine tasks, maintenance, and general code upkeep.
chore: update unit tests for authentication module
docs: Changes or additions to documentation.
docs(readme): update installation instructions
style: Code style changes (e.g., formatting) that do not affect the logic.
style(app): format code according to linting rules
refactor: Code changes that neither fix a bug nor add a feature.
refactor: simplify error handling in user service
test: Adding missing tests or correcting existing tests.
test: add integration tests for user registration
build: Changes that affect the build system or external dependencies.
build(deps): update npm packages to latest versions
ci: Changes to the continuous integration configuration or scripts.
ci(travis): configure deployment steps for staging environment
perf: Code changes that improve performance.
perf: optimize database queries for user authentication
These types help in categorizing and understanding the nature of changes made in the codebase, making it easier for team members to track and manage the project's development.
Scope
The scope of the commit message is optional but it can provide additional context of the location where the change is being made. Other than that, adding a scope can help with the readability of the message. You can indicate the module, component, or section where the modification is taking place.
There are certain guidelines that you can follow to define the scope:
- Be specific: The scope should be defined so that it’s obvious where the change is at. It can refer to a particular module, file, or functional area.
- Keep it Concise: The scope should be concise while providing enough information to understand the context of the change. Avoid overly long or detailed scope names.
- Align with Project Structure: If your project has a clear structure or follows a specific naming convention for modules or components, try to align the scope with that structure.
Breaking Changes
When adding BREAKING CHANGE
to the footer of the commit (more on footers later) or adding an !
after the type or scope is an indication of a particular code change that introduces breaking changes to the existing codebase. In Semantic Versioning that would correlate to a major update. It doesn’t depend on the type, therefore it can be added to any commit message.
A breaking change may require users or developers to update their code, configurations, or dependencies to adapt to the changes.
Examples
feat(api): add new parameter to the calculateTotal function
BREAKING CHANGE: The signature of the calculateTotal function has changed. Update your calls to include the new parameter.
chore(config): update configuration file format
BREAKING CHANGE: The configuration file format has changed. Please review and update your configuration accordingly.
Description
Descriptions are mandatory to use. Here is where the main purpose of the modification gets its title, therefore it should be concise and clear.
In order to phrase a commit message properly, first, we need to answer the “When to commit?” question. The most straightforward response for that is to commit after completing a logical unit.
Being aware of the aforementioned types of conventional commit messages is a great help for that. As you can see in the examples above, the types are strongly connected to the purpose of the description. You've added a new (logical) part to a readme file? docs. You've added authentication to the login screen? feat. You've added a new test? test.
Now, descriptions have their own structure too:
- Grammar: They start with a verb in imperative form AND present tense. It’s not a good practice to use “adding…” or “would like to add…” or “I added…” for example. Simply—”add…”. It’s like you’re telling the program what to do, but you can also write down the part that would come after “This commit will…”. Also, don’t use exclamation marks or punctuation at the end.
- Length: In git, the maximum length of the description of the commit message is 72 characters but it is suggested to use a maximum of 50 characters. Use more than 72 and it will cut the message at the end of the interval and wrap it. Clearly describe what the commit does and why it is necessary. Avoid ambiguous or overly technical language that might be difficult for others to understand.
Body
The body is optional to use. Its main purpose is to help readers understand the reasoning behind the change and any relevant details. It doesn’t have a character limit, but when composing the body, it's a good practice to wrap lines at 72 characters.
Example
feat(api): add new endpoint for user profile retrieval
This commit introduces a new API endpoint that allows clients to retrieve
user profile information based on the provided user ID.
Footer
In conventional commit messages, the footer is another optional section that appears after the body and is separated by a blank line.
It’s in the footer section where we define a BREAKING CHANGE
. Other than that, we can include additional metadata, references to external resources, or information related to the commit.
Examples
fix(bug): resolve issue with data validation
Fixes #234
chore(docs): update installation guide
Co-authored-by: Jane Smith <jane.smith@example.com>
Why Conventional Commit Messages Make a Difference
This convention aims to bring clarity and consistency to version control histories, making it easier for developers, automated tools, and processes to understand and manage the evolution of a codebase.
Semantic Versioning
Conventional commit messages often come in conjunction with Semantic Versioning (SemVer). SemVer is a versioning scheme that uses version numbers to communicate a meaning about the underlying code changes. By using a conventional commit format, it becomes easier to automate the determination of version bumps (major, minor, patch) based on the types of changes introduced. Conventional commit messages help categorize changes into types like "feat" for new features, "fix" for bug fixes, etc., and can automatically determine a semantic version bump.
Changelogs
Changelogs are documents that share the changes made in a project or software in reverse chronological order. When you receive an update for an application on your phone, if you navigate to the App Store / Google Play etc. you can find some kind of a description or summary of what changed that triggered the update.
There are mainly two ways to create a changelog:
- Manually writing down all the changes
- Auto-generating the changelog from commit messages
The last one—often also referred to as the lazy option-is where the conventional commit messages take a part in.
There are various tools and libraries designed to automate the process of updating changelogs based on conventional commit messages. Examples include release-please, semantic-release, and Conventional Changelog. These tools analyze the commit history, interpret the conventional commit messages, and generate changelog entries for each release.
Tools
Sometimes it can be a faff to pay attention to the length of the commit message, you forget to add the type, or you accidentally don’t follow a convention. There are several tools that can help you out with writing conventional commit messages.
Commitizen
Commitizen is a tool for creating standardized and conventional commit messages. It facilitates the process of adhering to a conventional commit message convention by providing an interactive command-line interface for generating commit messages.
To install commitizen run the following command in your project folder:
npm install commitizen -g
Then, initialize commitizen in your project (choose the one suitable for you):
# npm
commitizen init cz-conventional-changelog --save-dev --save-exact
# yarn
commitizen init cz-conventional-changelog --yarn --dev --exact
# pnpm
commitizen init cz-conventional-changelog --pnpm --save-dev --save-exact
After initializing commitizen, you simply have to use the git add
command to stage the changes and use git cz
that will trigger committing with the help of this tool:
Commitlint
While Commitizen primarily focuses on guiding developers through an interactive process, there are other tools that also check, validate against predefined criteria, and enforce the standards of conventional commit messages. This process is called linting, and it helps prevent errors.
Commitlint is a linter tool that checks commit messages against a set of rules, helping to maintain consistency and clarity in a project's versioning and changelog generation. In simpler terms, it will display errors if your message doesn’t follow the proper rules of conventional commit messages.
To install Commitlint you can find a very detailed guide in this article.