How to generate meaningful commits. Apply Conventional Commits Standard

Habitual chaos in commit names. Familiar picture?
Surely you know git-flow . This is a great set of conventions for organizing work with branches in Git. It is well documented and widely distributed. Usually we are familiar with the correct branching and talk a lot about it, but, unfortunately, we pay too little attention to the name of commits, so often the messages in Git are written haphazardly.
My name is Yerzhan Tashbenbetov, I work in one of the Yandex.Market teams. And today I will tell Habr's readers what tools we use to create meaningful commits in the team. I invite you to join the discussion of this topic.
The lack of agreement on commits makes it difficult to work with history in Git. That was on our team. Before using common for all regulations and implementation of automation, typical commits looked as follows:
SECRETMRKT-700: ΠΏΡΠΎΠΏΠ°Π»ΠΈ Π»ΠΎΠ³ΠΎΡΠΈΠΏΡ ΠΏΠ°ΡΡΠ½Π΅ΡΠΎΠ²
ΠΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΠΏΠ°Π΄Π°Π΅Ρ, ΠΏΠΎΠΏΡΠ°Π²ΠΈΠ».
SECRETMRKT-701, SECRETMRKT-702: ΠΡΡΠ΅Π½ΡΡΠΈΡΠΎΠ²Π°Π» ΠΊΠ°ΡΡΠΈΠ½ΠΊΠΈ Π½Π° Π²ΡΠ΅Ρ
...First, each developer wrote messages as he wanted: someone described the task, someone listed the changes made, someone used a random phrase generator. Everything was at odds with each other. Second, task numbers that were present in commits often shortened useful text. All this made it difficult to work effectively with history in Git.
For this reason, we implemented the Conventional Commits standard in the team , began to generate commits in the commitizen console utility and check the result using commitlint . As a result, commits have changed and become like this:
refactor(tutorial): ΠΎΠΏΡΠΈΠΌΠΈΠ·ΠΈΡΠΎΠ²Π°ΡΡ ΡΠ°Π±ΠΎΡΡ ΡΠΏΠΈΠΊΠΎΠ² Π² ΡΡΠ»ΡΠΈΠΏΠ°Ρ
feat(products): Π΄ΠΎΠ±Π°Π²ΠΈΡΡ Π±Π°Π½Π΅Ρ Ρ Π½ΠΎΠ²ΠΎΠ³ΠΎΠ΄Π½ΠΈΠΌΠΈ ΡΠΊΠΈΠ΄ΠΊΠ°ΠΌΠΈ
fix(products): ΠΈΡΠΏΡΠ°Π²ΠΈΡΡ Π² Π±Π°Π½Π΅ΡΠ΅ ΡΠΎΡΠΌΠ°Ρ Π΄Π°ΡΡReading the history and recognizing the changes made easier. We didnβt refuse to specify task numbers, everything was neatly transferred inside commits according to Conventional Commits convention .
Next, I'll tell you how to achieve a similar order in Git.
Best practices, recommendations, and common solutions for commiting
If you try to understand what practices are used in the industry, you can find the following options:
- Articles with general tips on writing commits. For the most part, they are quite logical and fairly well open the topic, but there is a sense of confusion and lack of a comprehensive solution to the issue.
- Standards for writing commits. They are few. They are documents with a clear list of rules, often written specifically for a large library or framework. These standards are captivating system approach, popularity and support in the open-source community.
We need more order in commits!
Methodology Conventional Commits stands out from the other standards and deserves scrutiny for several reasons:
- It is well documented and worked out. In its specification answers to the most common questions.
- The creators of the convention were inspired by the requirements for writing commits, which are used in the popular and time-tested framework AngularJS .
- Several large and popular open-source libraries (such as yargs and lerna ) follow the rules of the convention .
- The advantages include preparation for the automatic formation of the Release Notes and Change Log.
An example of a commit for this standard:
fix(products): ΠΏΠΎΠΏΡΠ°Π²ΠΈΡΡ Π΄Π»ΠΈΠ½Ρ ΡΡΡΠΎΠΊΠΈ Ρ ΡΠ΅Π½ΠΎΠΉ
Π§Π°ΡΡΡ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΎΠ² Π½Π΅ΠΏΡΠ°Π²ΠΈΠ»ΡΠ½ΠΎ ΠΎΡΠΎΠ±ΡΠ°ΠΆΠ°Π΅ΡΡΡ Π² ΠΌΠΎΠ±ΠΈΠ»ΡΠ½ΠΎΠΉ Π²Π΅ΡΡΠΈΠΈ ΠΈΠ·-Π·Π° ΠΎΡΠΈΠ±ΠΎΠΊ
Π² ΠΏΡΠΎΠ΅ΠΊΡΠΈΡΠΎΠ²Π°Π½ΠΈΠΈ ΡΠ½ΠΈΠ²Π΅ΡΡΠ°Π»ΡΠ½ΡΡ
ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ².
ΠΠΠ’Π ΠΠΠΠΠ«Π: SECRETMRKT-578, SECRETMRKT-602Basic abstracts of Conventional Commits
- The developer should adhere to the following commit structure:
<type> (<scope>): <subject>
<body>
<footer> - A commit should have a header, maybe a body and a footer.
- The commit title should start with a type ( type ) indicating the specifics of the changes made to the code base and end with a description.
- Along with the mandatory feat , fix (the use of which is strictly regulated), other types are allowed.
- A commit may have a scope . It characterizes the code snippet affected by the changes. The area follows the type of commit. The standard does not regulate a clear list of areas. Examples of domains: eslint, git, analytics, etc.
- The commit description should be immediately after the type / area.
- The commit body can be used to detail changes. The body should be separated from the description by an empty line.
- A footer should be used to specify external links, commit context, or other meta information. The footer should be separated from the body by a blank line.
In addition to the rules listed in the convention, we use the following popular recommendations:
- In the body of a commit we write what was changed and why .
- We use the following types of commits:
build Build a project or change external dependencies ci Configuring CI and working with scripts docs Documentation update feat Adding new functionality fix Error correction perf Changes to improve performance refactor Editing the code without correcting errors or adding new features revert Rollback to previous commits style Code style edits (tabs, indents, full stops, commas, etc.) test Adding tests - We write the description in the imperative mood , just like Git itself.
Merge branch 'fix / SECRETMRKT-749-fix-typos-in-titles'
- We do not download the description of the commit with punctuation marks.

Standard commits Conventional Commits use kotribyutory lerna
How easy is it to go to the correct name of commits?
Need to add automation and convenience. To solve this issue, we need two tools: the commit generator and the commit linter, which is configured to be checked before pushing to the repository.
Configure the commitizen utility
This tool allows you to generate commits using the built-in wizard. In addition, commitizen is well supported by the community and, thanks to additional modules, is perfectly customizable.
- Install the commitizen utility globally (you may need administrator privileges).
npm i -g commitizen - Next install the adapter cz-customizable . It is needed to set up a template with questions used by the commitizen utility .
npm i -D cz-customizable - Create a file commitizen.js, it is needed to configure cz-customizable. Place the created file in the directory ./config/git. I recommend not to litter the project root with configuration files and try to group the files in the folder prepared for this. Content:Show commitizen.js
"use strict"; module.exports = { // ΠΠΎΠ±Π°Π²ΠΈΠΌ ΠΎΠΏΠΈΡΠ°Π½ΠΈΠ΅ Π½Π° ΡΡΡΡΠΊΠΎΠΌ ΡΠ·ΡΠΊΠ΅ ΠΊΠΎ Π²ΡΠ΅ΠΌ ΡΠΈΠΏΠ°ΠΌ types: [ { value: "build", name: "build: Π‘Π±ΠΎΡΠΊΠ° ΠΏΡΠΎΠ΅ΠΊΡΠ° ΠΈΠ»ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ Π²Π½Π΅ΡΠ½ΠΈΡ Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠ΅ΠΉ" }, { value: "ci", name: "ci: ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° CI ΠΈ ΡΠ°Π±ΠΎΡΠ° ΡΠΎ ΡΠΊΡΠΈΠΏΡΠ°ΠΌΠΈ" }, { value: "docs", name: "docs: ΠΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ" }, { value: "feat", name: "feat: ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ Π½ΠΎΠ²ΠΎΠ³ΠΎ ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π»Π°" }, { value: "fix", name: "fix: ΠΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΠΎΡΠΈΠ±ΠΎΠΊ" }, { value: "perf", name: "perf: ΠΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ Π½Π°ΠΏΡΠ°Π²Π»Π΅Π½Π½ΡΠ΅ Π½Π° ΡΠ»ΡΡΡΠ΅Π½ΠΈΠ΅ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ" }, { value: "refactor", name: "refactor: ΠΡΠ°Π²ΠΊΠΈ ΠΊΠΎΠ΄Π° Π±Π΅Π· ΠΈΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΡ ΠΎΡΠΈΠ±ΠΎΠΊ ΠΈΠ»ΠΈ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΡ Π½ΠΎΠ²ΡΡ ΡΡΠ½ΠΊΡΠΈΠΉ" }, { value: "revert", name: "revert: ΠΡΠΊΠ°Ρ Π½Π° ΠΏΡΠ΅Π΄ΡΠ΄ΡΡΠΈΠ΅ ΠΊΠΎΠΌΠΌΠΈΡΡ" }, { value: "style", name: "style: ΠΡΠ°Π²ΠΊΠΈ ΠΏΠΎ ΠΊΠΎΠ΄ΡΡΠ°ΠΉΠ»Ρ (ΡΠ°Π±Ρ, ΠΎΡΡΡΡΠΏΡ, ΡΠΎΡΠΊΠΈ, Π·Π°ΠΏΡΡΡΠ΅ ΠΈ Ρ.Π΄.)" }, { value: "test", name: "test: ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΡΠ΅ΡΡΠΎΠ²" } ], // ΠΠ±Π»Π°ΡΡΡ. ΠΠ½Π° Ρ Π°ΡΠ°ΠΊΡΠ΅ΡΠΈΠ·ΡΠ΅Ρ ΡΡΠ°Π³ΠΌΠ΅Π½Ρ ΠΊΠΎΠ΄Π°, ΠΊΠΎΡΠΎΡΡΡ Π·Π°ΡΡΠΎΠ½ΡΠ»ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ scopes: [ { name: "components" }, { name: "tutorial" }, { name: "catalog" }, { name: "product" } ], // ΠΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡ Π·Π°Π΄Π°ΡΡ ΡΠΏΠ΅Ρ ΠΠΠΠΠ‘Π’Π¬ Π΄Π»Ρ ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½Π½ΠΎΠ³ΠΎ ΡΠΈΠΏΠ° ΠΊΠΎΠΌΠΌΠΈΡΠ° (ΠΏΡΠΈΠΌΠ΅Ρ Π΄Π»Ρ 'fix')/* scopeOverrides: { fix: [ {name: 'style'}, {name: 'e2eTest'}, {name: 'unitTest'} ] }, */// ΠΠΎΠΌΠ΅Π½ΡΠ΅ΠΌ Π΄Π΅ΡΠΎΠ»ΡΠ½ΡΠ΅ Π²ΠΎΠΏΡΠΎΡΡ messages: { type: "ΠΠ°ΠΊΠΈΠ΅ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΡ Π²Ρ Π²Π½ΠΎΡΠΈΡΠ΅?", scope: "\nΠΡΠ±Π΅ΡΠΈΡΠ΅ ΠΠΠΠΠ‘Π’Π¬, ΠΊΠΎΡΠΎΡΡΡ Π²Ρ ΠΈΠ·ΠΌΠ΅Π½ΠΈΠ»ΠΈ (ΠΎΠΏΡΠΈΠΎΠ½Π°Π»ΡΠ½ΠΎ):", // Π‘ΠΏΡΠΎΡΠΈΠΌ Π΅ΡΠ»ΠΈ allowCustomScopes Π² true customScope: "Π£ΠΊΠ°ΠΆΠΈΡΠ΅ ΡΠ²ΠΎΡ ΠΠΠΠΠ‘Π’Π¬:", subject: "ΠΠ°ΠΏΠΈΡΠΈΡΠ΅ ΠΠΠ ΠΠ’ΠΠΠ ΠΎΠΏΠΈΡΠ°Π½ΠΈΠ΅ Π² ΠΠΠΠΠΠΠ’ΠΠΠ¬ΠΠΠ Π½Π°ΠΊΠ»ΠΎΠ½Π΅Π½ΠΈΠΈ:\n", body: 'ΠΠ°ΠΏΠΈΡΠΈΡΠ΅ ΠΠΠΠ ΠΠΠΠΠ ΠΎΠΏΠΈΡΠ°Π½ΠΈΠ΅ (ΠΎΠΏΡΠΈΠΎΠ½Π°Π»ΡΠ½ΠΎ). ΠΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ "|" Π΄Π»Ρ Π½ΠΎΠ²ΠΎΠΉ ΡΡΡΠΎΠΊΠΈ:\n', breaking: "Π‘ΠΏΠΈΡΠΎΠΊ BREAKING CHANGES (ΠΎΠΏΡΠΈΠΎΠ½Π°Π»ΡΠ½ΠΎ):\n", footer: "ΠΠ΅ΡΡΠΎ Π΄Π»Ρ ΠΌΠ΅ΡΠ° Π΄Π°Π½Π½ΡΡ (ΡΠΈΠΊΠ΅ΡΠΎΠ², ΡΡΡΠ»ΠΎΠΊ ΠΈ ΠΎΡΡΠ°Π»ΡΠ½ΠΎΠ³ΠΎ). ΠΠ°ΠΏΡΠΈΠΌΠ΅Ρ: SECRETMRKT-700, SECRETMRKT-800:\n", confirmCommit: "ΠΠ°Ρ ΡΡΡΡΠ°ΠΈΠ²Π°Π΅Ρ ΠΏΠΎΠ»ΡΡΠΈΠ²ΡΠΈΠΉΡΡ ΠΊΠΎΠΌΠΌΠΈΡ?" }, // Π Π°Π·ΡΠ΅ΡΠΈΠΌ ΡΠΎΠ±ΡΡΠ²Π΅Π½Π½ΡΡ ΠΠΠΠΠ‘Π’Π¬ allowCustomScopes: true, // ΠΠ°ΠΏΡΠ΅Ρ Π½Π° Breaking Changes allowBreakingChanges: false, // ΠΡΠ΅ΡΠΈΠΊΡ Π΄Π»Ρ Π½ΠΈΠΆΠ½Π΅Π³ΠΎ ΠΊΠΎΠ»ΠΎΠ½ΡΠΈΡΡΠ»Π° footerPrefix: "ΠΠΠ’Π ΠΠΠΠΠ«Π:", // limit subject length subjectLimit: 72 }; - Add the package.json links to the cz-customizable and previously created configuration file:Show part package.json
{ "config": { "commitizen": { "path": "node_modules/cz-customizable" }, "cz-customizable": { "config": "config/git/commitizen.js" } }, } - Let's check the resulting result. Type the following command in the terminal:
git cz
The commitizen wizard will first collect information about the type and area of ββthe commit, then sequentially ask for the text that will be in the description, in the body, in the footer, and after your consent will create a commit.
Be sure to look at an example of the work of the configured commitizen utility and the cz-cusomizable adapter connected to it
Configure the husky and commitlint utilities
- Install husky and commitlint in the project :
npm i -D husky @commitlint/cli - With the help of husky we will add check of kommit. To do this, immediately after the scripts in package.json, add the following hook and specify a link to the commitlint.js file in it:Show part package.json
{ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "husky": { "hooks": { "commit-msg": "commitlint -E HUSKY_GIT_PARAMS -g './config/git/commitlint.js'" } }, "devDependencies": { "@commitlint/cli": "^7.2.1", "husky": "^1.1.3", } - Create a file commitlint.js, necessary for the correct operation of the linter. Place the created file in the directory ./config/git. File contents:Show commitlint.js
// Π€Π°ΠΉΠ» ΡΠΎΠ·Π΄Π°Π½ Π½Π° ΠΎΡΠ½ΠΎΠ²Π΅ @commitlint/config-conventionalmodule.exports = { rules: { // Π’Π΅Π»ΠΎ ΠΊΠΎΠΌΠΌΠΈΡΠ° Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π½Π°ΡΠΈΠ½Π°ΡΡΡΡ Ρ ΠΏΡΡΡΠΎΠΉ ΡΡΡΠΎΠΊΠΈ"body-leading-blank": [2, "always"], // ΠΠΈΠΆΠ½ΠΈΠΉ ΠΊΠΎΠ»ΠΎΠ½ΡΠΈΡΡΠ» ΠΊΠΎΠΌΠΌΠΈΡΠ° Π΄ΠΎΠ»ΠΆΠ΅Π½ Π½Π°ΡΠΈΠ½Π°ΡΡΡΡ Ρ ΠΏΡΡΡΠΎΠΉ ΡΡΡΠΎΠΊΠΈ"footer-leading-blank": [2, "always"], // ΠΠ°ΠΊΡΠΈΠΌΠ°Π»ΡΠ½Π°Ρ Π΄Π»ΠΈΠ½Π° Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠ° 72 ΡΠΈΠΌΠ²ΠΎΠ»Π°"header-max-length": [2, "always", 72], // ΠΠ±Π»Π°ΡΡΡ Π²ΡΠ΅Π³Π΄Π° ΡΠΎΠ»ΡΠΊΠΎ Π² Π½ΠΈΠΆΠ½Π΅ΠΌ ΡΠ΅Π³ΠΈΡΡΡΠ΅"scope-case": [2, "always", "lower-case"], // ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅ Π½Π΅ ΠΌΠΎΠΆΠ΅Ρ Π±ΡΡΡ ΠΏΡΡΡΡΠΌ"subject-empty": [2, "never"], // ΠΠΏΠΈΡΠ°Π½ΠΈΠ΅ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π·Π°ΠΊΠ°Π½ΡΠΈΠ²Π°ΡΡΡΡ '.'"subject-full-stop": [2, "never", "."], // Π’ΠΈΠΏ Π²ΡΠ΅Π³Π΄Π° ΡΠΎΠ»ΡΠΊΠΎ Π² Π½ΠΈΠΆΠ½Π΅ΠΌ ΡΠ΅Π³ΠΈΡΡΡΠ΅"type-case": [2, "always", "lower-case"], // Π’ΠΈΠΏ Π½Π΅ ΠΌΠΎΠΆΠ΅Ρ Π±ΡΡΡ ΠΏΡΡΡΡΠΌ"type-empty": [2, "never"], // ΠΠ΅ΡΠ΅ΡΠΈΡΠ»ΠΈΠΌ Π²ΡΠ΅ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΡΠ΅ Π²Π°ΡΠΈΠ°Π½ΡΡ ΠΊΠΎΠΌΠΌΠΈΡΠΎΠ²"type-enum": [ 2, "always", [ "build", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test" ] ] } };
Everything. Now all commits will be checked before being sent to the repository :)
Be sure to look at the example of the work of the configured commitlint utility.
So choose commitizen or commitlint?
Both! In conjunction, they bring excellent results: the first generates commits, the second checks them.
Why do standards recommend the use of imperative?
This is a very interesting question. A commit is a change in code; a message in a commit can be interpreted as a guide to changing this code. Make, change, add, update, fix - all these are specific instructions for the developer.
By the way, the imperative is recommended in the Git versioning system itself :
[[imperative-mood]]
Describe your changes in imperative mood, e.g. "make xyzzy do frotz"
instead of "[This patch] makes xyzzy do frotz" or "[I] changed xyzzy
to do frotz", as if you are giving orders to the codebase to change
its behavior.Why stick to any conventions? Is it worth spending time on it? What is the profit in this?
Worth it. In general, I noticed that we have become more willing to detail the changes made to the code base. In the body of a commit, we describe in detail why we had to use certain solutions. Understanding the history has become objectively easier. Plus, our product is developing, and we expect replenishment in the team. I am sure that thanks to the implementation of the standard and automation, it will be easier for new users to integrate into the development process.
Try and share the result.
Useful links:
- Repository with all the code from this article.
- Standard Conventional Commits .
- Commitlint is a tool for validating commits.
- Online configurator correct commits.
- Good advice on the name of commits.