Escrevendo bons commits no Git: convenções e mensagens descritivas

1. Por que a Qualidade dos Commits Importa?

1.1. Rastreabilidade e histórico limpo para revisão de código

Commits bem escritos transformam o histórico do Git em uma narrativa coerente do desenvolvimento do projeto. Quando cada commit descreve claramente sua intenção, revisores de código conseguem entender rapidamente as mudanças propostas sem precisar analisar cada linha alterada. Um histórico limpo permite que qualquer desenvolvedor, mesmo sem participar ativamente do projeto, compreenda a evolução do código ao longo do tempo.

1.2. Facilidade em operações de rollback e git bisect

Commits descritivos são essenciais para operações de reversão. Quando um bug é identificado, mensagens claras ajudam a localizar exatamente qual commit introduziu o problema. O comando git bisect, que realiza uma busca binária no histórico, torna-se muito mais eficiente quando cada commit contém informações precisas sobre o que foi alterado e por quê.

1.3. Impacto na colaboração em equipe e onboarding de novos desenvolvedores

Equipes que adotam convenções de commits reduzem significativamente o atrito na comunicação técnica. Novos membros conseguem entender rapidamente o fluxo de trabalho e o histórico do projeto. Commits padronizados funcionam como documentação viva, registrando decisões técnicas e contextos que seriam perdidos ao longo do tempo.

2. Anatomia de um Commit Ideal

2.1. Estrutura clássica: título, corpo e rodapé

Um commit bem estruturado segue este formato:

<tipo>(<escopo opcional>): <descrição curta>

<corpo opcional com explicações detalhadas>

<rodapé opcional com referências ou breaking changes>

2.2. Limite de caracteres e boas práticas de formatação

  • Título: máximo de 50 caracteres
  • Corpo: linhas com no máximo 72 caracteres
  • Linha em branco separando título do corpo
  • Linha em branco separando corpo do rodapé

2.3. Diferença entre mensagem descritiva e mensagem vaga

Mensagem vaga:

Corrige bug

Mensagem descritiva:

fix(auth): corrige falha de autenticação ao expirar token JWT

O token JWT estava sendo validado após expiração devido à falta
de verificação do campo 'exp'. Adiciona middleware para verificar
a expiração antes de processar requisições autenticadas.

Closes #142

3. Convenção Conventional Commits (1.0.0)

3.1. Prefixos obrigatórios

A especificação Conventional Commits define os seguintes prefixos principais:

  • feat: nova funcionalidade
  • fix: correção de bug
  • chore: tarefas de manutenção
  • docs: alterações em documentação
  • refactor: refatoração sem mudança de comportamento
  • test: adição ou modificação de testes
  • style: formatação de código
  • perf: melhoria de performance
  • ci: alterações em configuração de CI/CD
  • build: alterações no sistema de build

3.2. Uso de escopo e quebra de compatibilidade

O escopo especifica a área afetada pelo commit:

feat(api): adiciona endpoint de busca de usuários
fix(database): corrige migração de schema para PostgreSQL

Para mudanças que quebram compatibilidade, utiliza-se BREAKING CHANGE ou o prefixo !:

feat(api)!: altera formato de resposta de autenticação

BREAKING CHANGE: O campo 'token' foi renomeado para 'access_token'
e agora inclui data de expiração.

3.3. Exemplos práticos de cada tipo de commit

feat(payment): adiciona suporte a pagamentos via PIX

Implementa integração com gateway de pagamento para processar
transações via PIX. Inclui validação de chave aleatória e
confirmação de pagamento em tempo real.

Closes #89
fix(ui): corrige layout quebrado em dispositivos móveis

O menu de navegação estava sobrepondo o conteúdo principal
em telas menores que 768px. Aplica media query para ocultar
o menu em dispositivos móveis.

Fixes #234
refactor(utils): extrai lógica de validação para módulo separado

Remove duplicação de código movendo funções de validação
de e-mail e CPF para módulo dedicado em src/utils/validators.

4. Escrevendo Mensagens Descritivas e Contextuais

4.1. O "porquê" e o "como" — não apenas o "o que"

Uma mensagem eficaz responde a três perguntas:

  1. O que foi alterado? (título)
  2. Por que foi alterado? (contexto no corpo)
  3. Como foi alterado? (detalhes técnicos no corpo)

Exemplo completo:

feat(notifications): implementa notificações push para iOS

O aplicativo não possuía suporte a notificações push no iOS,
o que impedia alertas em tempo real para usuários do iPhone.

Implementa registro no APNs (Apple Push Notification service)
usando o framework UserNotifications. O token do dispositivo
é armazenado no banco de dados e associado ao usuário logado.

Requer configuração das chaves de certificado no ambiente.

4.2. Uso de verbos no imperativo e tom consistente

Sempre use verbos no imperativo, como se estivesse dando uma ordem:

  • ✅ "Adiciona suporte a..."
  • ✅ "Corrige falha em..."
  • ❌ "Adicionado suporte a..."
  • ❌ "Corrigindo falha em..."

4.3. Evitando ruídos: commits atômicos e mensagens enxutas

Cada commit deve representar uma única mudança lógica. Commits atômicos facilitam revisão, reversão e cherry-pick. Evite:

  • Commits que misturam correção de bug com nova funcionalidade
  • Mensagens que descrevem múltiplas alterações não relacionadas
  • Commits com título genérico como "Várias correções"

5. Ferramentas e Automação para Padronização

5.1. Hooks com commitlint e husky

Configure validação automática de mensagens de commit:

# Instalação com npm
npm install --save-dev @commitlint/cli @commitlint/config-conventional
npm install --save-dev husky

# Configuração do commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional']
}

5.2. Templates de commit com git commit --template

Crie um template para padronizar mensagens:

# .gitmessage.txt

# <tipo>(<escopo>): <descrição curta>
# |<--- limite de 50 caracteres --->|

# <corpo explicativo>
# |<--- limite de 72 caracteres por linha --->|

# Explicação do que foi alterado e por quê.

# Rodapé opcional: Closes #123, BREAKING CHANGE, etc.

Use com: git config commit.template .gitmessage.txt

5.3. Geração automática de changelog com standard-version

# Instalação
npm install --save-dev standard-version

# Geração automática de changelog
npx standard-version

6. Commits em Fluxos de Trabalho Colaborativos

6.1. Squash e rebase: quando e como reorganizar commits

Use git rebase -i para reorganizar commits antes do merge:

# Reorganizar últimos 3 commits
git rebase -i HEAD~3

# Squash commits relacionados
pick abc123 feat(auth): adiciona login
squash def456 feat(auth): corrige validação
squash ghi789 feat(auth): adiciona logout

6.2. Boas práticas em pull requests e code review

  • Mantenha PRs pequenos e focados
  • Use squash merge para consolidar commits de uma branch
  • Escreva descrições de PR que complementam os commits

6.3. Integração com GitHub Actions e semantic versioning

Configure workflows que validam mensagens de commit:

# .github/workflows/commitlint.yml
name: Lint Commit Messages
on: [pull_request]
jobs:
  commitlint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: wagoid/commitlint-github-action@v5

7. Erros Comuns e Como Corrigi-los

7.1. Commits grandes demais ou com múltiplos propósitos

Problema: Um commit que adiciona funcionalidade, corrige bug e refatora código.

Solução: Divida em commits atômicos:

refactor(api): extrai lógica de validação
fix(api): corrige tratamento de erro 500
feat(api): adiciona endpoint de busca

7.2. Mensagens genéricas ("fix", "update", "misc")

Problema: Commits que não explicam o contexto.

Solução: Substitua por mensagens específicas:

# ❌ Ruim
fix: corrige erro

# ✅ Bom
fix(database): corrige deadlock em transações concorrentes

7.3. Como reescrever o histórico com git commit --amend e git rebase -i

Corrigir último commit:

git commit --amend -m "feat(api): corrige mensagem do commit anterior"

Reescrever histórico interativamente:

git rebase -i HEAD~5
# Use 'reword' para alterar mensagens
# Use 'edit' para modificar conteúdo do commit

Referências