Published

Writing the perfect git commit message

Why should you care?

Clean git history is essential because it helps to make it easier to understand the evolution of a project. When history is cluttered with unnecessary commits and merge conflicts, it can be challenging to understand what changes were made and why they were made. A clean history can also make it easier to revert to previous versions of the project, as it is easier to find specific commits when the history is organized.

Overall, a clean git history is essential for the maintainability and understandability of a project.

Lousy practise

A bad git history can take many forms, but some common characteristics include the following:

  • A large number of commits that do not convey the changes being made
  • Multiple merge conflicts that have not been adequately resolved
  • Use of commit messages that are not descriptive or do not accurately reflect the changes being made
  • A lack of clear separation between different features or bug fixes
  • Large commits that include many unrelated changes

In general, a horrible git history is challenging to understand and navigate. It may also indicate poor coding practices, such as a lack of testing or focus on code organization.

Here are some examples of what I consider lousy git commit messages:

  • "Fixing bug" - This commit message does not provide any information about the bug or how it was fixed.
  • "Updating code" - This commit message is too vague and does not provide any information about what was updated or why it was necessary.
  • "asdf" - This commit message provides no information and does not help understand the changes made.
  • "Final version" - This commit message does not describe any of the changes that were made and do not help understand the evolution of the project.
  • "Adding everything" - This commit message is too broad and does not provide any information about what was added or why it was necessary.

Good practise

Good git commit messages follow a few best practices:

  • First, they are descriptive and explain the changes made and their reasons.
  • They use clear and concise language.
  • They are written in the present tense.
  • They are no longer than 70 characters in the subject line (the commit message's first line).

Here are some examples of good commit messages:

  • "Fix typo in README file"
  • "Add tests for new feature"
  • "Refactor login module for improved performance"
  • "Update dependencies to fix security vulnerabilities"

In general, good commit messages are specific and informative and help provide context about the changes that were made. This makes it easier for others to understand the project's evolution and collaborate effectively.

Best practise

Conventional commits

Conventional commits are a structured way of writing commit messages that makes it easier to generate changelogs, release notes, and other documentation automatically. The goal of using conventional commits is to make it easier to understand the changes that have been made to a project and to allow tools to understand the intent of each commit more easily. Those things work because the way the commit messages are written is predictable. That also means humans can easily read and understand them.

Several conventions for formatting commit messages have been established as best practices. One widely used convention is the Angular convention, which defines the following structure for commit messages:

<type>(<scope>): <subject>

The type is a short, all-lowercase string that describes the nature of the commit. Some common types include:

  • feat: New feature for the user (not a part of the code, or ci, ...)
  • fix: Bugfix for the user (not a fix to build something, ...)
  • docs: Changes to the documentation
  • style: This could be the code's styling or general styling changes. Does not change any functionality.
  • refactor: Refactoring production code. For example: Renaming a variable
  • test: Only changes current or new tests. Does not change the production code
  • chore: Does not impact production.

The scope is an optional string that describes the part of the codebase that the commit affects.

The subject is a brief description of the changes made in the commit. It should be written in an imperative mood and should not exceed 50 characters.

For example, a commit message following the Angular convention might look like this:

feat(search): add support for searching by date range

The commit body

The body of a conventional commit is optional and is used to provide a more detailed description of the changes made. It should be used to explain the motivation for the changes, as well as any implementation details that may be relevant.

The body of the commit should be separated from the subject by a blank line and wrapped to a maximum line length of 72 characters.

Here is an example of a commit message with a body:

feat(search): add support for searching by date range
This commit adds a new feature to the search module that allows users to search for records within a specific date range. The date range can be specified using two new fields on the search form: a start date and an end date.
- Adds new fields to the search form for specifying the date range
- Updates the search API to accept the start and end dates as parameters
- Modifies the search results page to display the selected date range

In general, the body of a commit message should provide enough information to understand the changes and motivations behind them without going into unnecessary detail.

Breaking changes

In conventional commits, breaking changes are indicated by including the word "BREAKING CHANGE" in the commit message, followed by a colon and a description of the breaking change.

For example:

feat(search): add support for searching by date range
BREAKING CHANGE: The search API now requires a start and end date to be specified for all searches. The old API, which only accepted a single search term, is no longer supported.
This commit adds a new feature to the search module that allows users to search for records within a specific date range. The date range can be specified using two new fields on the search form: a start date and an end date.
- Adds new fields to the search form for specifying the date range
- Updates the search API to accept the start and end dates as parameters
- Modifies the search results page to display the selected date range

The description of the breaking change should explain the change's impact and any steps users will need to take to migrate to the new version.

You can also append '!' to the scope/type to get the same result:

feat(search)!: add support for searching by date range

Semantic versioning

  • fix: a commit of the type fix patches a bug in your codebase (this correlates with PATCH in Semantic Versioning).
  • feat: a commit of the type feat introduces a new feature to the codebase (this correlates with MINOR in Semantic Versioning).
  • BREAKING CHANGE: a commit that has a footer or appends '!' after the type/scope introduces a breaking API change (correlating with MAJOR in Semantic Versioning). A BREAKING CHANGE can be part of commits of any type.

TLDR

feat(scope): add magical unicorn ^--^^-----^ ^------------------^ | | | | | +->
Summary in present tense. Starting with add, remove, update, do. Think about: If
I commit this, my code would .... add magical unicorn | | | +-> optional scope
of the commit, component-name, container | +-------> Type: chore, docs, feat,
fix, refactor, style, or test.

Done reading?

Read more related articles