Voltar para Artigos
Carreira10 min de leitura

Versionamento Profissional: Git Flow, Conventional Commits e CHANGELOG Automático

Git Flow vs GitHub Flow vs Trunk-Based Development. Conventional Commits com escopo, BREAKING CHANGE e rodapé. Husky + commitlint para enforcement, e Release Please para CHANGELOG automático e SemVer.

12 de agosto de 2026

"Ajuste", "fix", "corrigindo bug", "WIP", "alterações" — esses commits são inúteis para qualquer pessoa além de quem os escreveu, e inúteis até para eles mesmos 3 meses depois. Em um projeto com 10 desenvolvedores por 2 anos, o histórico do Git é o único documento que descreve fielmente a evolução do sistema. Um histórico bem estruturado permite entender por que uma decisão foi tomada, fazer git bisect para encontrar quando um bug foi introduzido, e gerar CHANGELOGs automaticamente.

Este artigo cobre as três camadas do versionamento profissional: estratégia de branches (quando usar Git Flow vs GitHub Flow), Conventional Commits (como escrever mensagens semânticas) e automação (enforcement via Husky/commitlint e CHANGELOG via Release Please).

Estratégias de Branch: Escolha a Certa

  • Git Flow — branches main, develop, feature/*, release/*, hotfix/*. Para projetos com releases programadas e múltiplas versões em produção simultaneamente (ex: software desktop, libs públicas). Mais complexo, mais controle.
  • GitHub Flow — branches main + feature/*. PR direto para main. Para SaaS com deploy contínuo — mais simples, mais rápido. A maioria das equipes de produto usa isso.
  • Trunk-Based Development — todos commitam diretamente em main com feature flags. Para equipes muito maduras com CI/CD robusto. Maximiza velocidade de integração.

Para a maioria dos projetos web, o GitHub Flow é o ponto ideal: feature/nome-da-feature ramifica de main, PR com code review, merge com squash, deploy automático do main.

Git Flow Completo (Para Projetos com Releases)

bash
# Branches permanentes:
# main     → produção (tags de versão aqui)
# develop  → integração (staging/homologação)

# Fluxo de uma nova feature:
git checkout develop
git checkout -b feature/user-export-csv
# ... trabalho ...
git commit -m "feat(users): adiciona exportação de usuários em CSV"
git push origin feature/user-export-csv
# Abre PR: feature/user-export-csv → develop
# Após code review e aprovação: merge (squash ou merge commit)

# Release:
git checkout develop
git checkout -b release/1.2.0
# Apenas bugfixes nesta branch — não entra feature nova
git commit -m "fix: corrige formatação do CSV no Windows"
git checkout main && git merge release/1.2.0 --no-ff
git tag v1.2.0
git checkout develop && git merge release/1.2.0 --no-ff
git branch -d release/1.2.0

# Hotfix (bug urgente em produção):
git checkout main
git checkout -b hotfix/1.2.1
git commit -m "fix(auth): corrige bypass de autenticação em edge case"
git checkout main && git merge hotfix/1.2.1 --no-ff
git tag v1.2.1
git checkout develop && git merge hotfix/1.2.1 --no-ff
git branch -d hotfix/1.2.1

Conventional Commits: A Especificação Completa

Estrutura do Conventional Commit
<tipo>(<escopo opcional>): <descrição breve>

[corpo opcional — por que a mudança foi feita]

[rodapé opcional]
Closes #123
Co-authored-by: Maria Silva <maria@empresa.com>
BREAKING CHANGE: <descrição da quebra de compatibilidade>
  • feat — nova funcionalidade. Dispara Minor version no SemVer (1.1.0 → 1.2.0)
  • fix — correção de bug. Dispara Patch version (1.2.0 → 1.2.1)
  • docs — apenas documentação. Sem bump de versão.
  • style — formatação, espaços, ponto e vírgula. Sem bump.
  • refactor — reestruturação sem mudar comportamento. Sem bump.
  • perf — melhoria de performance. Patch version.
  • test — adição ou correção de testes. Sem bump.
  • build — mudanças no sistema de build, dependências (npm, webpack). Sem bump.
  • ci — mudanças em arquivos e scripts de CI (GitHub Actions, Jenkins). Sem bump.
  • chore — tarefas de manutenção diversas. Sem bump.
  • BREAKING CHANGE (no rodapé) — quebra de compatibilidade. Dispara Major version (1.2.1 → 2.0.0)
Exemplos reais de commits
# Feature com escopo
git commit -m "feat(auth): adiciona login via Google OAuth2"

# Fix com issue reference
git commit -m "fix(orders): corrige cálculo de frete para zonas rurais

O cálculo anterior não considerava o código de zona de entrega rural
(CEP terminado em 000). A API dos Correios retornava erro 422 nesses casos.

Closes #247"

# Breaking change: Major version bump
git commit -m "feat(api)!: renomeia endpoint /users/:id/profile para /profiles/:userId

ANTERORM: GET /api/users/:id/profile
NOVO: GET /api/profiles/:userId

BREAKING CHANGE: todos os clientes da API precisam atualizar as URLs.
Documentação de migração em MIGRATION.md.

Closes #198"

# Chore: sem bump de versão
git commit -m "chore(deps): atualiza TypeScript para 5.5 e corrige type errors"

# Performance
git commit -m "perf(db): adiciona índice composto em (user_id, created_at) na tabela orders

REDUZ query de listagem de pedidos de 850ms para 12ms no p99.
Ver análise completa em docs/performance/orders-index.md"

Enforcement: Husky + commitlint

Padrões sem enforcement são apenas sugestões. Configure o commitlint para rejeitar commits fora do padrão automaticamente:

bash
npm install --save-dev husky @commitlint/cli @commitlint/config-conventional

# Inicializa o Husky
npx husky init
commitlint.config.js
/** @type {import('@commitlint/types').UserConfig} */
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    // Tipos permitidos na sua equipe
    'type-enum': [
      2, // 2 = error (falha o commit)
      'always',
      ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert'],
    ],
    'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']],
    'subject-max-length': [2, 'always', 100],
    'body-max-line-length': [2, 'always', 120],
    'scope-case': [2, 'always', 'lower-case'],
  },
};
commit-msg
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# Valida a mensagem do commit contra as regras do commitlint
npx --no -- commitlint --edit "$1"
package.json
{
  "scripts": {
    "prepare": "husky"
  }
}

// Após npm install, o Husky instala os hooks automaticamente.
// Qualquer commit fora do padrão é rejeitado antes de ser criado:
// ✖ type must be one of [feat, fix, ...] [type-enum]
// ✖ subject may not be empty [subject-empty]

CHANGELOG Automático com Release Please

O Release Please (Google) lê o histórico de Conventional Commits, determina o próximo número de versão SemVer automaticamente, e cria um PR com CHANGELOG gerado. Quando você dá merge no PR, ele cria a tag e a GitHub Release:

release.yml
name: Release Please

on:
  push:
    branches: [main]

permissions:
  contents: write
  pull-requests: write

jobs:
  release-please:
    runs-on: ubuntu-latest
    steps:
      - uses: googleapis/release-please-action@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          release-type: node
release-please-config.json
{
  "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
  "release-type": "node",
  "packages": {
    ".": {}
  },
  "changelog-sections": [
    { "type": "feat",     "section": "✨ Funcionalidades" },
    { "type": "fix",      "section": "🐛 Correções" },
    { "type": "perf",     "section": "⚡ Performance" },
    { "type": "refactor", "section": "♻️ Refatorações", "hidden": true },
    { "type": "chore",    "section": "🔧 Manutenção",   "hidden": true }
  ]
}

O Release Please cria um PR chamado "chore: release 1.3.0" com o CHANGELOG atualizado. Você revisa, aprova e faz o merge quando estiver pronto para o release. Isso separa o momento do commit da decisão de fazer o release — ideal para equipes que precisam de aprovação antes de publicar.

Conclusão

Conventional Commits transforma o histórico do Git de uma sequência de mensagens aleatórias em uma linha do tempo semântica e legível por máquinas. O Husky garante que ninguém da equipe pule o padrão. O Release Please elimina o trabalho manual de escrever CHANGELOGs e determinar versões. O resultado é um fluxo de release totalmente auditável, automatizado e previsível.