Escrever boas mensagens de commit no Git
1. Visão geral
Mensagens de commit são aquelas que você mais relê ao fazer debug, revisar código ou escrever changelog — muito mais do que o código que acabou de escrever. Este artigo ajuda você a dominar a estrutura padrão de mensagens de commit, aplicar Conventional Commits e verificar automaticamente mensagens com commitlint + husky. Depois de ler, você escreverá commits claros e consistentes, e seu time poderá gerar changelog automaticamente a partir do histórico do git.
2. Por que mensagens de commit importam
Uma boa mensagem de commit responde 3 perguntas: o quê mudou, por que mudou e qual é o impacto. Sem ela, você precisa usar git show em cada commit para adivinhar a intenção do autor — gastando muito mais tempo do que se tivesse escrito uma mensagem decente desde o início.
Comparando duas mensagens para a mesma mudança:
# Ruim
fix bug
# Bom
fix(payment): lidar com null pointer quando PaymentService.charge() recebe Order sem Customer
A mensagem ruim não diz qual é o bug nem onde está. A boa diz qual é o módulo (payment), a ação (fix) e a causa específica — em 5 segundos você entende.
3. Estrutura padrão de mensagens de commit
Git recomenda uma estrutura de 3 partes: subject (assunto), body (corpo) e footer (rodapé).
<type>(<scope>): <subject>
<body>
<footer>
subject: resumo breve, ≤ 50 caracteres, no presente (“add”, não “added”).body: explica por que a mudança, não o quê (o diff já mostra isso).footer: metadados — breaking change, issues relacionadas, co-author.
Exemplo completo:
fix(order): evitar double-charge ao retry de pagamento falhado
PaymentService.charge() não tem idempotency key então quando
há timeout de rede e o cliente faz retry, o sistema chama
a API gateway duas vezes para o mesmo Order.
Adiciona idempotencyKey = order.getId() + "-" + attemptCount
no request header para que o gateway faça o dedupe automaticamente.
Closes #482
3.1. Linha subject
Subject é a linha mais importante — aparece em git log --oneline, no título de PR, no changelog. Regras:
# Errado - primeira letra maiúscula, ponto no final, passado
Fixed the bug in order service.
# Certo - primeira letra minúscula, sem ponto, presente
fix: order não deduz estoque ao pagar com carteira digital
Nota: O limite de 50 caracteres não é uma lei, mas ultrapassar 72 caracteres será cortado em muitos terminais e na UI do GitHub. Tente ser conciso.
3.2. Seção body
Body é para explicar o contexto — aquilo que o diff não consegue contar: por que escolheu essa solução, por que outras foram descartadas, há side effects?
refactor(cart): converter CartService de síncrono para assíncrono
Antes CartService.checkout() chamava InventoryService e
ShippingService sequencialmente, latência total ~800ms. Agora
usa CompletableFuture para executar as duas chamadas em paralelo,
reduzindo a latência para ~450ms.
API pública não muda, apenas a implementação interna.
Separe subject e body com uma linha em branco — git e ferramentas de parse de commit contam com essa convenção.
4. Conventional Commits
Conventional Commits é o padrão mais popular atualmente, definindo type fixos para que ferramentas possam categorizar automaticamente e gerar changelog.
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
type mais comuns:
| Type | Significado |
|---|---|
feat |
adiciona uma nova funcionalidade |
fix |
corrige um bug |
docs |
altera apenas documentação |
style |
formata código, não altera lógica |
refactor |
altera estrutura do código, não altera comportamento |
test |
adiciona/altera testes |
chore |
tarefas miúdas (build, dependência, config) |
perf |
melhora de performance |
Exemplo com scope como nome do módulo:
feat(auth): suporte para login com OAuth2 Google
Adiciona GoogleOAuth2Provider implementando OAuth2LoginProvider.
Configuração de client-id, client-secret adicionada em
application.properties.
4.1. Breaking change
Quando a mudança quebra a compatibilidade retroativa, marque com ! após type/scope ou com footer BREAKING CHANGE:.
feat(api)!: mudar resposta de GET /orders/{id} para OrderDto
BREAKING CHANGE: campo "customerName" muda para objeto
"customer" contendo id, name, email. Clientes precisam atualizar parser.
Semantic-release e ferramentas CI/CD usam a linha BREAKING CHANGE: para fazer bump automático de major version.
5. Verificação automática de mensagens de commit
Convenções só ajudam se o time as segue consistentemente. Use commitlint para bloquear commits mal formatados localmente, antes do push.
<!-- Não se aplica a projeto Node, apenas exemplo de dependência se você usar Maven wrapper para husky-git-hooks -->
Na prática commitlint roda em Node, instale via npm:
npm install --save-dev @commitlint/cli @commitlint/config-conventional husky
Crie o arquivo de configuração:
// commitlint.config.js
module.exports = { extends: ['@commitlint/config-conventional'] };
Instale o hook commit-msg com husky:
npx husky init
echo 'npx --no -- commitlint --edit "$1"' > .husky/commit-msg
Agora tente fazer commit com formato errado:
git commit -m "fixed bug"
⧗ input: fixed bug
✖ subject may not be empty [subject-empty]
✖ type must be one of [feat, fix, docs, style, refactor, test, chore, perf]
✖ found 2 problems, 0 warnings
husky - commit-msg script failed (code 1)
O commit é bloqueado imediatamente, forçando você a corrigir o formato antes de criar o commit.
5.1. Usar com projetos Java/Spring Boot
Se o projeto é Maven, você ainda pode usar husky + commitlint em paralelo (precisam apenas de Node runtime, não mexem na build Java). Ou, se quer algo só Java, crie um script simples de verificação e chame via core.hooksPath:
#!/bin/bash
# .githooks/commit-msg
PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf)(\([a-z-]+\))?: .{1,50}"
MSG=$(cat "$1")
if ! [[ "$MSG" =~ $PATTERN ]]; then
echo "Mensagem de commit não segue Conventional Commits" >&2
exit 1
fi
git config core.hooksPath .githooks
chmod +x .githooks/commit-msg
5.2. Testar o script de verificação de formato
Se mantém um script de verificação próprio (sem commitlint), deve ter testes para a regex. Suponha que empacota a lógica de parse em uma classe Java para reutilizar no CI:
public class CommitMessageValidator {
private static final Pattern PATTERN =
Pattern.compile("^(feat|fix|docs|style|refactor|test|chore|perf)" +
"(\\([a-z0-9-]+\\))?: .{1,50}");
public boolean isValid(String message) {
String subject = message.lines().findFirst().orElse("");
return PATTERN.matcher(subject).find();
}
}
class CommitMessageValidatorTest {
private final CommitMessageValidator validator = new CommitMessageValidator();
@Test
void shouldAcceptConventionalFormat() {
boolean result = validator.isValid("fix(order): evitar double-charge ao retry");
assertThat(result).isTrue();
}
@Test
void shouldRejectMessageWithoutType() {
boolean result = validator.isValid("fixed the bug");
assertThat(result).isFalse();
}
@Test
void shouldRejectUnknownType() {
boolean result = validator.isValid("update: adiciona algo");
assertThat(result).isFalse();
}
}
Esses testes garantem que a lógica de validação não regride quando você altera a regex depois — por exemplo, quando adiciona um novo type mas esquece de atualizar os testes.
6. Erros comuns
Commit muito grande, mensagem genérica. “update code” para um commit que altera 15 arquivos diferentes é sinal de que você deve quebrar em vários commits pequenos, cada um com um propósito claro (atomic commit).
Misturar vários tipos de mudança em um commit. Fazer fix de bug e refactor não relacionado no mesmo commit dificulta git revert e git bisect — reverter um fix pode acabar perdendo o refactor.
Body repetindo subject. Body deve adicionar contexto, não reescrever a primeira linha com frase mais longa.
# Repetição, sem sentido
fix(order): corrige erro ao calcular total
Corrige o erro ao calcular o total no OrderService.
Usar emoji/caracteres decorativos no lugar de conteúdo. Emoji não é errado, mas se usar para substituir descrição real, o changelog automático fica vazio de informação.
Nota: Conventional Commits funciona bem com times que usam CI/CD para gerar changelog automaticamente ou semantic versioning. Para times pequenos com poucos commits, aplicar regras muito rígidas pode causar atrito desnecessário — priorize clareza sobre conformidade mecânica.
7. Conclusão
Boa mensagem de commit não precisa ser longa, só precisa seguir a estrutura: subject breve no presente, body explicando o motivo, footer com metadados necessários. Aplicar Conventional Commits ajuda o time a ler o histórico do git mais rápido e permite ferramentas automatizarem changelog e versioning. Configure commitlint ou git hooks customizados para transformar convenções em restrições automáticas, evitando depender da consciência de cada pessoa no time.