Commits convencionais
Aviso: Este post foi traduzido para o português usando um modelo de tradução automática. Por favor, me avise se encontrar algum erro.
Este post foi criado a partir do vídeo de Carlos Azaustre, só que como ele explica como fazer tudo com ferramentas de JavaScript, há muita gente desenvolvedora de Python que não tem instalado Node.js, portanto fiz uma versão do mesmo mas tudo com ferramentas de Python.
O que são os conventional commits?
a integração com ferramentas de gestão de mudanças e releases.
Formato das Mensagens de Commit
Uma mensagem de commit em Conventional Commits segue um formato específico:
git<tipo>[escopo
[corpo opcional]
[rodapé(s) opcional(is)]
Vamos vê-lo mais detalhadamente
Tipo type
O tipo de commit indica a natureza da mudança. Alguns tipos comuns são:
- fix: É usado para correção de bugs.* feat: É usado para novas funcionalidades. * docs: É usado para mudanças na documentação.* style: Utiliza-se para alterações que não afetam o significado do código (por exemplo, formatação, remoção de espaços em branco). * refactor: É usado para mudanças de código que nem melhoram nem pioram a funcionalidade, como reorganizar o código. * perf: Usado para mudanças que melhoram o desempenho.* test: Utiliza-se para adicionar ou atualizar testes. * chore: Utiliza-se para mudanças no processo ou nas ferramentas de desenvolvimento.* ci: É usado para alterações nos arquivos de configuração de integração contínua. * build: Usado para alterações que afetam o sistema de construção ou dependências externas. * revert: Utiliza-se para reverter um commit anterior.
Escopo scope
O escopo é opcional e é utilizado para especificar a parte do projeto que foi modificada. Por exemplo, se você está trabalhando em um componente específico de um aplicativo web, o escopo pode ser o nome do componente. Exemplo:
git
fix(auth): corrigir problema de autenticação
Descrição description
A descrição é uma breve explicação da mudança. Deve ser concisa e clara, e fornecer contexto suficiente para entender o propósito do commit. Exemplo:
gitfix(auth):
Corpo body
O corpo é opcional e é utilizado para fornecer mais detalhes sobre a mudança. Aqui você pode incluir motivações para a mudança e contrastes com a implementação anterior. Exemplo:
git
fix(auth): corrigir erro de autenticação na página de login
O token de acesso estava expirando mais cedo do que o esperado devido a um erro no cálculo da data de expiração. Isso foi corrigido ajustando a lógica de cálculo.
Rodapé footer
O rodapé é opcional e é utilizado para referências adicionais, como números de issues fechados ou commits relacionados. Exemplo:
gitfix(auth):
O token de acesso estava expirando antes do esperado devido a um erro no cálculo da data de expiração. Ele foi corrigido ajustando a lógica de cálculo.
Fecha #123
Benefícios dos Conventional Commits
- Clareza e Consistência: As mensagens de commit padronizadas são mais fáceis de entender e seguir, o que melhora a colaboração em equipes de desenvolvimento. * Geração Automática de Notas de Versão: Podem ser usadas ferramentas para gerar notas de versão automaticamente com base nas mensagens de commit.* Integração com Ferramentas de Gerenciamento de Mudanças: Muitas ferramentas de desenvolvimento e gerenciamento de projetos podem se integrar com Conventional Commits para automatizar tarefas como a geração de changelogs e o gerenciamento de releases. * Histórico de Mudanças Estruturado: O histórico de mudanças torna-se mais estruturado e fácil de navegar, o que facilita a revisão de mudanças e a depuração de problemas.
Exemplos práticos
Exemplo 1: Correção de um Bug
git
```fix(api): corrigir erro na validação do usuário
O endpoint de registro de usuário estava permitindo registros com endereços de e-mail inválidos. Uma validação adicional foi adicionada para garantir que os endereços de e-mail sejam válidos.
Fecha #456```
Exemplo 2: Adicionar uma Nova Funcionalidade
git
feat(api): adicionar endpoint de recuperação de senha
Implementado um novo endpoint que permite aos usuários solicitar um link de recuperação de senha. O link é enviado para o endereço de e-mail registrado.
Fecha #789
Exemplo 3: Melhoria da documentação
git
docs: atualizar guia de contribuição
Atualizou as instruções de configuração para o ambiente de desenvolvimento e adicionou uma seção sobre como executar os testes.
Fecha #101```
Ferramentas para construir mensagens que atendam aos conventional commits
Embora tenhamos visto como criar mensagens de commit através de conventional commits, é bastante possível que cometamos erros, por isso podemos usar ferramentas que nos guiem na criação dessas mensagens. Vamos ver duas: commitizen
e o plugin Conventional Commit
do vscode.
Commitizen
Para usá-la, primeiro vou criar uma nova pasta na qual vou iniciar um repositório do Git
!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/
Agora instalo 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.
Verificamos a instalação
!cz version
3.29.1
Crio um novo arquivo na pasta na qual eu inicializei o repositório de git e o adiciono à área de staging
!cd ~/comitizen_folder && touch README.md && git add README.md
Se eu fizer git status
, verei que o arquivo está na área de staging e que agora devo fazer um commit.
!cd ~/comitizen_folder && touch README.md && git add README.md!cd ~/comitizen_folder && git status
On branch masterNo commits yetChanges to be committed:(use "git rm --cached <file>..." to unstage)new file: README.md
É hora de criar um commit com commitizen
, para isso executamos cz commit
e aparecerá um assistente para criar o 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!
Pronto, já temos nosso primeiro commit criado com commitizen
que segue as regras de Conventional Commits
Plugin Conventional Commit de vscode
Agora vamos fazer o mesmo, só que com o plugin do vscode Conventional Commit
Primeiro, é preciso instalar o plugin e, uma vez instalado, pressionamos Ctrl + Shift + P
e escrevemos Conventional Commit
, pressionamos enter
e aparecerá um assistente para criar o commit.
Para mim, o uso deste plugin tem duas vantagens sobre o commitizen
- A primeira é que nos permite adicionar emojis do gitmoji. O que, se não se abusa dos emojis e se usar apenas alguns, torna mais fácil identificar o tipo de commit ao ver o histórico de commits.* A segunda é que guarda os âmbitos
scope
s que você usou, portanto não cria novos âmbitos, mas reutiliza os que você já usou.
Ferramentas para verificar a conformidade com a convenção de conventional commits
Vimos como criar mensagens de commit que seguem a convenção de conventional commits
, mas uma boa prática é criar uma ferramenta para verificar se o commit criado segue a convenção, especialmente quando se trabalha em equipe.
Há ferramentas que nos permitem fazer isso como pre-commit
, mas o que fazem é modificar os hooks do git, então vamos fazer isso nós mesmos e usar o commitizen
para nos ajudar a validar a mensagem de commit.
Já instalamos o commitizen
, então vamos ver como podemos usá-lo para verificar uma mensagem de commit
Primeiro, criamos um arquivo chamado commit-msg
na pasta .git/hooks
e damos permissões de execução. Dentro dos hooks do Git, há vários tipos de arquivos que podem ser usados para diferentes tarefas. Neste caso, vamos usar commit-msg
, que é executado justo antes de o commit ser criado.
!cd ~/comitizen_folder/.git/hooks && touch commit-msg && chmod +x commit-msg
Agora adicionamos o seguinte código ao arquivo commit-msg
#!/bin/shEste script valida a mensagem do commit usando commitizen
COMMIT_MSG_FILE=$1cz check --commit-msg-file $COMMIT_MSG_FILE```
!cd ~/comitizen_folder/.git/hooks && touch commit-msg && chmod +x commit-msg!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
Uma vez feito, tentamos fazer um commit com uma mensagem incorreta. Primeiro modificamos o readme e o adicionamos à área de staging.
!cd ~/comitizen_folder/.git/hooks && touch commit-msg && chmod +x commit-msg!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!cd ~/comitizen_folder && echo '.' >> README.md && git add README.md
Agora fazemos um commit com uma mensagem incorreta
!cd ~/comitizen_folder/.git/hooks && touch commit-msg && chmod +x commit-msg!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!cd ~/comitizen_folder && echo '.' >> README.md && git add README.md!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*))?$
Agora fazemos um commit com uma mensagem correta
!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(-)
Nos validou o commit corretamente, portanto se olharmos o histórico de commits, veremos que o commit com a mensagem incorreta não foi criado e o commit com a mensagem correta sim.
!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
Ferramentas para criar changelogs a partir de conventional commits
Como temos os commits escritos pelo mesmo convênio, podemos criar changelog automaticamente com git-changelog
. Instalamos as dependências
%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.
Agora podemos criar um changelog com git-changelog
. Como criamos alguns commits muito simples, o changelog será muito simples.
!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 -->
Também podemos pedir-lhe que escreva em um arquivo e muitas mais opções
!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.
Já podemos gerar changelogs facilmente a partir dos commits que seguem a convenção de conventional commits
. Além disso, podemos adicioná-lo a um pipeline de CI/CD para que seja gerado automaticamente em cada release.