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.
"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 paramain. 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
maincom 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)
# 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.1Conventional Commits: A Especificação Completa
<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)
# 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:
npm install --save-dev husky @commitlint/cli @commitlint/config-conventional
# Inicializa o Husky
npx husky init/** @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'],
},
};#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# Valida a mensagem do commit contra as regras do commitlint
npx --no -- commitlint --edit "$1"{
"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:
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{
"$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.