Disclaimer: This post has been translated to English using a machine translation model. Please, let me know if you find any mistakes.
This post has been created based on the video by Carlos Azaustre, only that since he explains how to do everything with JavaScript tools, there are many Python developers who don't have Node.js installed, so I've made a version of the same but using all Python tools.
What are conventional commits?
The integration with change and release management tools.
Format of Commit Messages
A Conventional Commits message follows a specific format:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
Let's look at it in more detail.
Type type
The type of commit indicates the nature of the change. Some common types are:
- fix: It is used for bug fixes.
- feat: It is used for new features.
- docs: It is used for changes in the documentation.
- style: It is used for changes that do not affect the meaning of the code (for example, formatting, removal of white spaces).
- refactor: It is used for code changes that neither improve nor degrade functionality, such as reorganizing the code.
- perf: It is used for changes that improve performance.
- test: It is used to add or update tests.
- chore: It is used for changes in the process or development tools.
- ci: It is used for changes in the continuous integration configuration files.
- build: Used for changes that affect the build system or external dependencies.
- revert: It is used to reverse a previous commit.
Scope scope
The scope is optional and is used to specify the part of the project that was modified. For example, if you are working on a specific component of a web application, the scope could be the name of the component.
Example:
fix(auth): fix authentication issue
Description description
The description is a brief explanation of the change. It should be concise and clear, and provide enough context to understand the purpose of the commit.
Example:
fix(auth): fix authentication error on login page
Body body
The body is optional and is used to provide more details about the change. Here you can include motivations for the change and contrasts with the previous implementation.
Example:
fix(auth): fix authentication error on login page
El token de acceso estaba expirando antes de lo esperado debido a un error en el cálculo de la fecha de expiración. Se ha corregido ajustando la lógica de cálculo.
Footer footer
The footer is optional and is used for additional references, such as closed issue numbers or related commits.
Example:
fix(auth): fix authentication error on login page
El token de acceso estaba expirando antes de lo esperado debido a un error en el cálculo de la fecha de expiración. Se ha corregido ajustando la lógica de cálculo.
Closes #123
Benefits of Conventional Commits
- Clarity and Consistency: Standardized commit messages are easier to understand and follow, which improves collaboration in development teams.
- Automatic Generation of Release Notes: Tools can be used to automatically generate release notes based on commit messages.
- Integration with Change Management Tools: Many development and project management tools can integrate with Conventional Commits to automate tasks such as generating changelogs and managing releases.
- Structured Change History: The change history becomes more structured and easier to navigate, making it simpler to review changes and debug issues.
Practical Examples
Example 1: Bug Fix
fix(api): fix error in user validation
The user registration endpoint was allowing registrations with invalid email addresses. An additional validation has been added to ensure that email addresses are valid.
Closes #456
Example 2: Add a new feature
feat(api): add password recovery endpoint
Implementado un nuevo punto de conexión que permite a los usuarios solicitar un enlace para recuperar su contraseña. El enlace se envía a su dirección de correo electrónico registrada.
Closes #789
Example 3: Improving Documentation
docs: update contribution guide
Actualizadas las instrucciones de configuración para el entorno de desarrollo y se agregó una sección sobre ejecutar pruebas.
Closes #101
Tools for building messages that meet conventional commits
Although we have seen how to create commit messages using conventional commits, it is quite possible that we might make mistakes, so we can use tools that guide us in the creation of these messages. We are going to look at two: commitizen
and the Conventional Commits
plugin for vscode.
Commitizen
To use it, I will first create a new folder in which I will initialize a Git repository.
!mkdir ~/comitizen_folder && cd ~/comitizen_folder && git init
hint: Using 'master' as the name for the initial branch. This default branch namehint: is subject to change. To configure the initial branch name to use in allhint: of your new repositories, which will suppress this warning, call:hint:hint: git config --global init.defaultBranch <name>hint:hint: Names commonly chosen instead of 'master' are 'main', 'trunk' andhint: 'development'. The just-created branch can be renamed via this command:hint:hint: git branch -m <name>Initialized empty Git repository in /home/maximofernandez/comitizen_folder/.git/
Now I install commitizen
%pip install --user -U commitizen
Collecting commitizenDownloading commitizen-3.29.1-py3-none-any.whl.metadata (7.6 kB)Collecting argcomplete<3.6,>=1.12.1 (from commitizen)Downloading argcomplete-3.5.1-py3-none-any.whl.metadata (16 kB)Requirement already satisfied: charset-normalizer<4,>=2.1.0 in /home/maximofernandez/miniforge3/lib/python3.12/site-packages (from commitizen) (3.3.2)Requirement already satisfied: colorama<0.5.0,>=0.4.1 in /home/maximofernandez/miniforge3/lib/python3.12/site-packages (from commitizen) (0.4.6)Collecting decli<0.7.0,>=0.6.0 (from commitizen)Downloading decli-0.6.2-py3-none-any.whl.metadata (17 kB)Requirement already satisfied: jinja2>=2.10.3 in /home/maximofernandez/miniforge3/lib/python3.12/site-packages (from commitizen) (3.1.4)Requirement already satisfied: packaging>=19 in /home/maximofernandez/.local/lib/python3.12/site-packages (from commitizen) (24.1)Requirement already satisfied: pyyaml>=3.08 in /home/maximofernandez/miniforge3/lib/python3.12/site-packages (from commitizen) (6.0.2)Collecting questionary<3.0,>=2.0 (from commitizen)Downloading questionary-2.0.1-py3-none-any.whl.metadata (5.4 kB)Collecting termcolor<3,>=1.1 (from commitizen)Downloading termcolor-2.5.0-py3-none-any.whl.metadata (6.1 kB)Collecting tomlkit<1.0.0,>=0.5.3 (from commitizen)Downloading tomlkit-0.13.2-py3-none-any.whl.metadata (2.7 kB)Requirement already satisfied: MarkupSafe>=2.0 in /home/maximofernandez/miniforge3/lib/python3.12/site-packages (from jinja2>=2.10.3->commitizen) (2.1.5)Collecting prompt_toolkit<=3.0.36,>=2.0 (from questionary<3.0,>=2.0->commitizen)Downloading prompt_toolkit-3.0.36-py3-none-any.whl.metadata (7.0 kB)Requirement already satisfied: wcwidth in /home/maximofernandez/miniforge3/lib/python3.12/site-packages (from prompt_toolkit<=3.0.36,>=2.0->questionary<3.0,>=2.0->commitizen) (0.2.13)Downloading commitizen-3.29.1-py3-none-any.whl (71 kB)Downloading argcomplete-3.5.1-py3-none-any.whl (43 kB)Downloading decli-0.6.2-py3-none-any.whl (7.9 kB)Downloading questionary-2.0.1-py3-none-any.whl (34 kB)Downloading termcolor-2.5.0-py3-none-any.whl (7.8 kB)Downloading tomlkit-0.13.2-py3-none-any.whl (37 kB)Downloading prompt_toolkit-3.0.36-py3-none-any.whl (386 kB)Installing collected packages: tomlkit, termcolor, prompt_toolkit, decli, argcomplete, questionary, commitizenERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.ipython 8.27.0 requires prompt-toolkit<3.1.0,>=3.0.41, but you have prompt-toolkit 3.0.36 which is incompatible.Successfully installed argcomplete-3.5.1 commitizen-3.29.1 decli-0.6.2 prompt_toolkit-3.0.36 questionary-2.0.1 termcolor-2.5.0 tomlkit-0.13.2Note: you may need to restart the kernel to use updated packages.
We check the installation
!cz version
3.29.1
I create a new file in the folder where I have initialized the Git repository and add it to the staging area.
!cd ~/comitizen_folder && touch README.md && git add README.md
If I do git status
I will see that the file is in the staging area and that I should now make a commit.
!cd ~/comitizen_folder && git status
On branch masterNo commits yetChanges to be committed:(use "git rm --cached <file>..." to unstage)new file: README.md
It's time to create a commit with commitizen
. To do this, run cz commit
and an assistant will appear to help you create the commit.
!cd ~/comitizen_folder && cz commit
? Select the type of change you are committing docs: Documentation only changes? What is the scope of this change? (class or file name): (press [enter] to skip)readme? Write a short and imperative summary of the code changes: (lower case and no period)First innit, create readme? Provide additional contextual information about the code changes: (press [enter] to skip)This is the first commit, I create a empty readme? Is this a BREAKING CHANGE? Correlates with MAJOR in SemVer No? Footer. Information about Breaking Changes and reference issues that this commit closes: (press [enter] to skip)docs(readme): First innit, create readmeThis is the first commit, I create a empty readme[master (root-commit) 4f646d4] docs(readme): First innit, create readme1 file changed, 0 insertions(+), 0 deletions(-)create mode 100644 README.mdCommit successful!
We have successfully created our first commit with commitizen
that follows the rules of Conventional Commits
.
Conventional Commit Plugin for vscode
Now we are going to do the same thing but with the Conventional Commit vscode plugin.
First, you need to install the plugin and once it is installed, press Ctrl + Shift + P
and type Conventional Commit
, hit Enter
and an assistant will appear to create the commit.
For me, using this plugin has two advantages over commitizen
- The first is that it allows us to add emojis from gitmoji. This, if emojis are not overused and only a few are used, makes it easier to identify the type of commit when viewing the commit history.* The second is that it keeps track of the
scope
s you have used, so it prevents new scopes from being created and instead reuses the ones you have already used.
Tools to check that the conventional commits convention is followed
We have seen how to create commit messages that follow the conventional commits
convention, but a good practice is to create a tool to check that the created commit follows the convention, especially when working in a team.
There are tools that allow us to do this, such as pre-commit
, but what they do is modify the git hooks, so we're going to do it ourselves and use commitizen
to help us validate the commit message.
We have already installed commitizen
, so let's see how it can be used to check a commit message.
First we create a file called commit-msg
in the .git/hooks
folder and give it execution permissions. Inside Git hooks there are various types of files that can be used for different tasks, in this case we will use commit-msg
which runs just before the commit is created.
!cd ~/comitizen_folder/.git/hooks && touch commit-msg && chmod +x commit-msg
Now we add the following code to the commit-msg
file
#!/bin/sh
This script validates the commit message using commitizen
COMMIT_MSG_FILE=$1
cz check --commit-msg-file $COMMIT_MSG_FILE
!cd ~/comitizen_folder/.git/hooks && \echo '#!/bin/sh' > commit-msg && \echo '# Este script valida el mensaje del commit usando commitizen' >> commit-msg && \echo ' ' >> commit-msg && \echo 'COMMIT_MSG_FILE=$1' >> commit-msg && \echo 'cz check --commit-msg-file $COMMIT_MSG_FILE' >> commit-msg
Once done, we try to make a commit with an incorrect message. First, we modify the README and add it to the staging area.
!cd ~/comitizen_folder && echo '.' >> README.md && git add README.md
Now we make a commit with an incorrect message
!cd ~/comitizen_folder && git commit -m "Add dot to README"
commit validation: failed!please enter a commit message in the commitizen format.commit "": "Add dot to README"pattern: (?s)(build|ci|docs|feat|fix|perf|refactor|style|test|chore|revert|bump)((S+))?!?:( [^ ]+)(( .*)|(s*))?$
Now we make a commit with a correct message
!cd ~/comitizen_folder && git commit -m "docs(readme): :memo: Add dot to README"
Commit validation: successful![master d488656] docs(readme): :memo: Add dot to README1 file changed, 1 insertion(+), 1 deletion(-)
It has validated the commit correctly, so if we look at the commit history, we will see that the commit with the incorrect message was not created and the commit with the correct message was.
!cd ~/comitizen_folder && git log
commit d488656297c7cb448a25dd33a008cb5ce1e14e83 (HEAD -> master)Author: MaximoFN <maximofn@gmail.com>Date: Tue Oct 8 11:22:19 2024 +0200docs(readme): :memo: Add dot to READMEcommit fb518c2b903a259b2e44972e88aabf5656f97be9Author: MaximoFN <maximofn@gmail.com>Date: Tue Oct 8 10:57:41 2024 +0200docs(readme): :memo: Update readmeUpdate readme with text conventional commitscommit 4f646d45047a45b549243efbfde9e331d45e23f1Author: MaximoFN <maximofn@gmail.com>Date: Tue Oct 8 10:48:07 2024 +0200docs(readme): First innit, create readmeThis is the first commit, I create a empty readme
Tools for generating changelogs from conventional commits
Since we have the commits written using the same convention, we can automatically create a changelog with git-changelog
. We install the dependencies.
%pip install git-changelog
Collecting git-changelogDownloading git_changelog-2.5.2-py3-none-any.whl.metadata (5.4 kB)Collecting appdirs>=1.4 (from git-changelog)Downloading appdirs-1.4.4-py2.py3-none-any.whl.metadata (9.0 kB)Requirement already satisfied: Jinja2>=2.10 in /home/maximofernandez/miniforge3/lib/python3.12/site-packages (from git-changelog) (3.1.4)Requirement already satisfied: packaging>=24.0 in /home/maximofernandez/.local/lib/python3.12/site-packages (from git-changelog) (24.1)Collecting semver>=2.13 (from git-changelog)Downloading semver-3.0.2-py3-none-any.whl.metadata (5.0 kB)Requirement already satisfied: MarkupSafe>=2.0 in /home/maximofernandez/miniforge3/lib/python3.12/site-packages (from Jinja2>=2.10->git-changelog) (2.1.5)Downloading git_changelog-2.5.2-py3-none-any.whl (32 kB)Downloading appdirs-1.4.4-py2.py3-none-any.whl (9.6 kB)Downloading semver-3.0.2-py3-none-any.whl (17 kB)Installing collected packages: appdirs, semver, git-changelogSuccessfully installed appdirs-1.4.4 git-changelog-2.5.2 semver-3.0.2Note: you may need to restart the kernel to use updated packages.
Now we can create a changelog with git-changelog
. Since we created very simple commits, the changelog will be very simple.
!cd ~/comitizen_folder && git-changelog
# ChangelogAll notable changes to this project will be documented in this file.The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).<!-- insertion marker -->## Unreleased<small>[Compare with latest]()</small><!-- insertion marker -->
We can also ask him to write it to a file and many more options
!git-changelog -h
usage: git-changelog [--config-file [PATH ...]] [-b] [-B VERSION] [-n SCHEME][-h] [-i] [-g REGEX] [-m MARKER] [-o FILE] [-p PROVIDER][-r] [-R] [-I FILE] [-c CONVENTION] [-s SECTIONS][-t TEMPLATE] [-T] [-E] [-Z] [-F RANGE] [-j KEY=VALUE][-V] [--debug-info][REPOSITORY]Automatic Changelog generator using Jinja2 templates.This tool parses your commit messages to extract useful datathat is then rendered using Jinja2 templates, for example toa changelog file formatted in Markdown.Each Git tag will be treated as a version of your project.Each version contains a set of commits, and will be an entryin your changelog. Commits in each version will be groupedby sections, depending on the commit convention you follow.### Conventions#### Basic*Default sections:*- add: Added- fix: Fixed- change: Changed- remove: Removed*Additional sections:*- merge: Merged- doc: Documented#### Angular*Default sections:*- feat: Features- fix: Bug Fixes- revert: Reverts- ref, refactor: Code Refactoring- perf: Performance Improvements*Additional sections:*- build: Build- chore: Chore- ci: Continuous Integration- deps: Dependencies- doc, docs: Docs- style: Style- test, tests: Tests#### ConventionalCommit*Default sections:*- feat: Features- fix: Bug Fixes- revert: Reverts- ref, refactor: Code Refactoring- perf: Performance Improvements*Additional sections:*- build: Build- chore: Chore- ci: Continuous Integration- deps: Dependencies- doc, docs: Docs- style: Style- test, tests: Testspositional arguments:REPOSITORY The repository path, relative or absolute. Default:current working directory.options:--config-file [PATH ...]Configuration file(s).-b, --bump-latest Deprecated, use --bump=auto instead. Guess the newlatest version by bumping the previous one based onthe set of unreleased commits. For example, if acommit contains breaking changes, bump the majornumber (or the minor number for 0.x versions). Else ifthere are new features, bump the minor number. Elsejust bump the patch number. Default: unset (false).-B VERSION, --bump VERSIONSpecify the bump from latest version for the set ofunreleased commits. Can be one of `auto`, `major`,`minor`, `patch` or a valid SemVer version (eg.1.2.3). For both SemVer and PEP 440 versioning schemes(see -n), `auto` will bump the major number if acommit contains breaking changes (or the minor numberfor 0.x versions, see -Z), else the minor number ifthere are new features, else the patch number.Default: unset (false).-n SCHEME, --versioning SCHEMEVersioning scheme to use when bumping and comparingversions. The selected scheme will impact the valuesaccepted by the `--bump` option. Supported: `pep440`,`semver`. PEP 440 provides the following bumpstrategies: `auto`, `epoch`, `release`, `major`,`minor`, `micro`, `patch`, `pre`, `alpha`, `beta`,`candidate`, `post`, `dev`. Values `auto`, `major`,`minor`, `micro` can be suffixed with one of `+alpha`,`+beta`, `+candidate`, and/or `+dev`. Values `alpha`,`beta` and `candidate` can be suffixed with `+dev`.Examples: `auto+alpha`, `major+beta+dev`, `micro+dev`,`candidate+dev`, etc.. SemVer provides the followingbump strategies: `auto`, `major`, `minor`, `patch`,`release`. See the docs for more information. Default:unset (`semver`).-h, --help Show this help message and exit.-i, --in-place Insert new entries (versions missing from changelog)in-place. An output file must be specified. Withcustom templates, you can pass two additionalarguments: `--version-regex` and `--marker-line`. Whenwriting in-place, an `in_place` variable will beinjected in the Jinja context, allowing to adapt thegenerated contents (for example to skip changelogheaders or footers). Default: unset (false).-g REGEX, --version-regex REGEXA regular expression to match versions in the existingchangelog (used to find the latest release) whenwriting in-place. The regular expression must be aPython regex with a `version` named group. Default:`^## [(?P<version>v?[^]]+)`.-m MARKER, --marker-line MARKERA marker line at which to insert new entries (versionsmissing from changelog). If two marker lines arepresent in the changelog, the contents between thosetwo lines will be overwritten (useful to update an'Unreleased' entry for example). Default: `<!--insertion marker -->`.-o FILE, --output FILEOutput to given file. Default: standard output.-p PROVIDER, --provider PROVIDERExplicitly specify the repository provider. Default:unset.-r, --parse-refs Parse provider-specific references in commit messages(GitHub/GitLab/Bitbucket issues, PRs, etc.). Default:unset (false).-R, --release-notes Output release notes to stdout based on the last entryin the changelog. Default: unset (false).-I FILE, --input FILERead from given file when creating release notes.Default: `CHANGELOG.md`.-c CONVENTION, --convention CONVENTION, --commit-style CONVENTION, --style CONVENTIONThe commit convention to match against. Default:`basic`.-s SECTIONS, --sections SECTIONSA comma-separated list of sections to render. See theavailable sections for each supported convention inthe description. Default: unset (None).-t TEMPLATE, --template TEMPLATEThe Jinja2 template to use. Prefix it with `path:` tospecify the path to a Jinja templated file. Default:`keepachangelog`.-T, --trailers, --git-trailersParse Git trailers in the commit message. Seehttps://git-scm.com/docs/git-interpret-trailers.Default: unset (false).-E, --omit-empty-versionsOmit empty versions from the output. Default: unset(false).-Z, --no-zerover By default, breaking changes on a 0.x don't bump themajor version, maintaining it at 0. With this option,a breaking change will bump a 0.x version to 1.0.-F RANGE, --filter-commits RANGEThe Git revision-range filter to use (e.g.`v1.2.0..`). Default: no filter.-j KEY=VALUE, --jinja-context KEY=VALUEPass additional key/value pairs to the template.Option can be used multiple times. The key/value pairsare accessible as 'jinja_context' in the template.-V, --version Show the current version of the program and exit.--debug-info Print debug information.
We could already generate changelogs easily from the commits that follow the conventional commits
convention. Additionally, we can add it to a CI/CD pipeline so that it is generated automatically with each release.