“fix”, “update”, “ajustes”, “wip”. Esses são os commit messages mais comuns em repositórios de qualquer tamanho. São também completamente inúteis como documentação.
O histórico de um repositório é a única fonte que registra não apenas o que mudou, mas quando mudou e, se o commit message for bom, por que mudou. Destruir esse ativo com mensagens genéricas é um custo que aparece meses depois, quando alguém precisa entender por que uma decisão foi tomada.
O que um bom commit message responde
Um commit message eficaz responde três perguntas implícitas: o que foi feito, onde foi feito e por que foi feito.
“fix button” não responde nenhuma das três. “Fix submit button disabled state on mobile Safari” responde as duas primeiras. “Fix submit button disabled state on mobile Safari (input type=date triggers blur before submit on iOS 16)” responde as três.
A terceira resposta é a mais valiosa e a mais raramente escrita. O “o que” e o “onde” o git diff mostra. O “por que” só fica registrado se alguém escrever.
Commits atômicos
Um commit que muda cinquenta arquivos por razões não relacionadas é impossível de reverter parcialmente, difícil de revisar e impossível de entender pelo message.
Commits atômicos, cada um com uma única razão de existir, tornam o histórico navegável. É possível encontrar exatamente quando um comportamento foi introduzido, reverter uma mudança específica sem afetar o resto, e entender o raciocínio por trás de cada decisão.
A regra prática que costuma funcionar: se o commit message precisa da palavra “e” para descrever o que foi feito (“ajusta CSS do header e corrige bug do formulário”), provavelmente são dois commits. Dividir custa segundos. Tentar separar depois custa horas.
Convenções que ajudam
Conventional Commits é uma especificação que padronizou o formato de commit messages na indústria. A sintaxe é simples: prefixo de tipo (feat:, fix:, docs:, refactor:, chore:, perf:, test:), escopo opcional entre parênteses, e descrição em modo imperativo no presente.
Alguns exemplos:
feat(auth): adiciona login com OAuth2
fix(checkout): corrige cálculo de frete para CEPs do Norte
refactor(api): extrai lógica de paginação para módulo separado
docs(readme): atualiza instruções de instalação
A especificação também permite indicar mudanças que quebram compatibilidade (com ! após o tipo, ou com BREAKING CHANGE: no rodapé), o que facilita a geração automática de changelogs e o versionamento semântico.
Não é obrigatório adotar exatamente o Conventional Commits. É obrigatório ter algum padrão, documentado, que todo o time segue. Consistência no histórico é mais valiosa do que a convenção específica escolhida.
Quando o assunto não cabe na linha
Para mudanças mais complexas, a primeira linha do commit (o assunto) não dá conta sozinha. Existe uma convenção antiga, atribuída ao próprio Tim Pope em 2008, que continua útil: assunto com até 50 caracteres, linha em branco, corpo com linhas de até 72 caracteres.
O assunto responde “o que mudou”. O corpo responde “por que mudou” e “que alternativas foram consideradas”. Em commits de refactor significativo ou correção de bug raro, esse corpo é o que vai economizar horas de investigação no futuro.
Exemplo:
fix(api): retorna 422 em vez de 500 para payload malformado
O middleware de validação estava deixando passar JSON sintaticamente
válido mas com campos obrigatórios ausentes, gerando 500 no controller.
A correção move a validação de schema para antes do controller e
padroniza a resposta como 422 com lista de campos faltando.
Refs: #2841
Esse commit, lido três anos depois, ainda explica a decisão.
O teste do git log
Uma forma simples de avaliar a qualidade do histórico de um repositório: abrir o git log sem olhar os diffs e tentar entender o que aconteceu no projeto nos últimos três meses só pelas mensagens de commit.
Se não for possível, o histórico não está cumprindo sua função.
A posição da VM2
Em projetos de plataforma que vão evoluir ao longo de anos, com equipes que mudam e novas pessoas chegando, o histórico de commits vira parte da documentação de fato consultada. Não a documentação que ninguém abre, a que aparece quando alguém faz git blame numa linha estranha e precisa entender o que estava acontecendo.
Por isso a gente trata commit message como entregável: tem padrão definido, tem revisão em pull request, e tem rejeição quando o message não responde o “por quê” de uma mudança não trivial. Custa pouco no momento do commit. Economiza muito quando alguém volta no código seis meses depois.
Referências consultadas: especificação Conventional Commits 1.0.0 (conventionalcommits.org); convenção 50/72, Tim Pope (2008).