Roda git log --oneline no seu projeto agora.
Vai. Roda mesmo.
Se apareceu uma sequência tipo ajustes, correção, wip, agora vai, ajustes 2, revert dos ajustes — bem-vindo ao clube. Todo mundo já escreveu essa novela.
O problema aparece três meses depois, quando você precisa entender por que aquela linha do OrderService está daquele jeito. Você roda um git blame, chega no commit e lê: "ajustes". Obrigado, eu do passado. Muito esclarecedor.
Histórico do Git não é burocracia. É a única documentação que envelhece junto com o código, porque está grudada nele. E ela custa 10 segundos a mais por commit pra ficar boa.
O problema: seu histórico não responde nenhuma pergunta
Olha esse log e tenta responder três coisas: o que entrou de novo? o que quebrou? dá pra fazer deploy?
a3f9c21 ajustes
7b2e881 correção do bug
0c41d7a wip
e91f3b2 mudanças solicitadas
5d8a04c agora vai
Não dá. E isso tem custo real:
-
Code review — o revisor não sabe se
ajustesé refactor inofensivo ou mudança de regra de negócio - Release — alguém tem que reler o diff inteiro pra escrever o "o que mudou nessa versão"
-
Investigação de bug —
git log --grepnão serve pra nada quando tudo se chama a mesma coisa -
Você mesmo, na segunda-feira — depois de um fim de semana, aquele
wipnão significa nada
O conceito: um prefixo, e só
Conventional Commits é uma convenção de mensagem tão simples que dá pra aprender no tempo de ler este parágrafo:
<tipo>(<escopo opcional>): <descrição>
Os tipos que você vai usar em 95% dos casos:
-
feat— funcionalidade nova -
fix— correção de bug -
refactor— mudou o código sem mudar comportamento -
test— mexeu em teste -
docs— mexeu em documentação -
chore— tarefa de manutenção (dependência, config, build) -
perf— melhoria de performance
Na prática, o mesmo histórico de antes vira isto:
a3f9c21 feat(checkout): aplica cupom de desconto no total
7b2e881 fix(auth): corrige redirect após login expirado
0c41d7a refactor(orders): extrai cálculo de frete para Action
e91f3b2 test(checkout): cobre cupom expirado e cupom inválido
5d8a04c chore(deps): atualiza laravel/framework para 11.9
Agora as três perguntas do começo se respondem só de bater o olho. E git log --oneline --grep "^feat" te dá a lista de tudo que entrou de novo desde a última tag. Sem esforço.
O escopo, o corpo e o !
O escopo entre parênteses é opcional, mas vale muito num projeto Laravel: checkout, auth, api, admin. É o "onde" da mudança, e permite filtrar por área.
Quando a mensagem de uma linha não conta a história toda, o corpo entra. Regra de ouro: o título diz o que, o corpo diz por quê.
fix(pedidos): usa transaction ao confirmar pedido
Sem a transaction, uma falha no gateway deixava o pedido como
confirmado e o estoque já debitado. Agora ou tudo entra ou nada entra.
Refs #412
Esse commit vale ouro no git blame daqui a um ano. O diff mostra o que mudou; ele explica por quê.
E tem o caso da mudança que quebra compatibilidade — sinalizada com ! antes dos dois-pontos:
feat(api)!: remove campo legacy_id do UserResource
Isso é um aviso gritante pra quem consome a sua API, e é o que separa uma release menor de uma release maior.
O CHANGELOG de graça
Aqui é onde o padrão deixa de ser organização e vira ferramenta.
Como a mensagem passa a ser legível por máquina, dá pra derivar a versão automaticamente, seguindo Semantic Versioning:
- Só
fix,chore,refactor→ sobe o patch (1.2.3 → 1.2.4) - Tem
featno meio → sobe o minor (1.2.3 → 1.3.0) - Tem
!ouBREAKING CHANGE→ sobe o major (1.2.3 → 2.0.0)
Ferramentas como o release-please ou o git-cliff leem o histórico, calculam a nova versão, geram o CHANGELOG.md agrupado por tipo e criam a tag. Num workflow do GitHub Actions, isso roda sozinho a cada merge na main.
Você não escreve release notes nunca mais. Elas são um subproduto de você ter escrito commits decentes.
Se quiser garantir o padrão no time, o commitlint rejeita mensagem fora do formato no hook commit-msg. Só uma dica: comece pela convenção combinada com o time e só depois automatize a validação. Ferramenta antes de acordo vira briga no PR.
Pegadinha: o prefixo não é mágico
Esse é o mal-entendido mais comum, e eu já vi acontecer:
fix: ajustes
feat: mudanças
chore: coisas
Parabéns, agora seu histórico é inútil e padronizado. 😅
O prefixo é a menor parte do valor. O que faz diferença é a descrição ser específica: fix(auth): corrige redirect após login expirado conta uma história. fix: ajustes não.
Duas outras convenções que ajudam:
-
Imperativo, sem "eu" —
adiciona,corrige,remove. Completa a frase "esse commit ___ o quê?" - Commit atômico — uma mudança lógica por commit. Se você precisa de um "e" no meio da mensagem, provavelmente eram dois commits
E fica tranquilo: bagunçou na hora de escrever? git commit --amend conserta o último, e git rebase -i conserta o resto antes de virar PR.
Bônus: e agora?
Adota no próximo commit. Não precisa reescrever o passado nem convencer o time inteiro numa reunião — o histórico melhora a partir de hoje, e a diferença aparece sozinha no primeiro git log que alguém rodar.
Depois de um mês, roda isso e vê o que o seu projeto anda fazendo de verdade:
git log --oneline --since="1 month ago" | cut -d' ' -f2 | sort | uniq -c | sort -rn
Se chore e fix dominam e mal aparece feat, você acabou de descobrir algo importante sobre onde o seu tempo está indo.
Antes de você fechar a aba
Vai lá, roda o git log --oneline de novo e me conta nos comentários: qual é a pior mensagem de commit que você encontrou (bônus se for sua)? 😄
Top comments (0)