Semantic Versioning e Tags de Release

Semantic Versioning e Tags de Release

Semantic Versioning e tags de release: o que MAJOR, MINOR e PATCH comunicam, sufixos de pré-release, tags leves e anotadas no Git, Conventional Commits como base para automação e geração automática de versão e changelog com Release Please.
DevOps

10 min de leitura

Imagine receber uma mensagem de um colega dizendo: "o bug está na versão nova". Qual versão nova? Nova em relação a quê? Lançada quando?

Sem uma convenção de versionamento, equipes acabam usando nomes arbitrários — "versão de dezembro", "build final", "build final v2", "build final v2 revisado". Em produção, esse caos tem consequências reais: dificuldade em identificar qual código está rodando, impossibilidade de comparar comportamentos entre versões e incapacidade de comunicar claramente o impacto de uma atualização para clientes e parceiros.

O Semantic Versioning — ou SemVer — é a convenção que resolve esse problema. Adotada pela maioria dos projetos de software modernos, ela transforma um número de versão em uma comunicação precisa sobre o que mudou.

A Lógica do SemVer

Um número de versão SemVer tem o formato MAJOR.MINOR.PATCH — por exemplo, 2.4.1.

Cada parte tem um significado definido:

PATCH — incrementado quando são feitas correções de bugs compatíveis com versões anteriores. Quem usa 2.4.0 pode atualizar para 2.4.1 sem preocupação. Nada quebrou, apenas foi corrigido.

MINOR — incrementado quando novas funcionalidades são adicionadas de forma compatível com versões anteriores. Quem usa 2.4.1 pode atualizar para 2.5.0 com segurança — o que já funcionava continua funcionando.

MAJOR — incrementado quando mudanças incompatíveis são introduzidas. Quem usa 2.x precisa ler o changelog antes de atualizar para 3.0.0 — algo pode ter quebrado intencionalmente.

Regras adicionais importantes:

  • Quando MINOR é incrementado, PATCH volta a zero: 2.4.12.5.0
  • Quando MAJOR é incrementado, MINOR e PATCH voltam a zero: 2.5.33.0.0
  • Versões com MAJOR igual a zero (0.x.y) indicam desenvolvimento inicial — a API pública não é considerada estável

Sufixos de Pré-release

Para versões que ainda não estão prontas para produção, o SemVer permite sufixos:

1.0.0-alpha        # primeira fase de testes internos
1.0.0-alpha.2      # segunda iteração do alpha
1.0.0-beta.1       # fase de testes mais ampla
1.0.0-rc.1         # release candidate — candidato a versão final
1.0.0              # versão estável final

Em pipelines de CI/CD, é comum gerar versões de pré-release automaticamente para branches de desenvolvimento, enquanto versões estáveis são geradas apenas a partir de merges na main.

Tags no Git

No Git, uma tag é um ponteiro nomeado para um commit específico. Diferentemente das branches, tags não se movem — elas marcam um ponto fixo no histórico, ideal para representar releases.

# Cria uma tag leve (apenas um ponteiro)
git tag v1.2.0

# Cria uma tag anotada (recomendada — inclui mensagem, autor e data)
git tag -a v1.2.0 -m "Release 1.2.0: adiciona suporte a autenticação OAuth"

# Lista todas as tags
git tag

# Lista tags com filtro
git tag -l "v1.*"

# Mostra detalhes de uma tag anotada
git show v1.2.0

# Cria tag em um commit específico (não necessariamente o mais recente)
git tag -a v1.1.5 abc1234 -m "Hotfix: corrige falha de segurança no login"

Tags precisam ser enviadas explicitamente para o repositório remoto — o git push padrão não as inclui:

# Envia uma tag específica
git push origin v1.2.0

# Envia todas as tags locais
git push origin --tags

Conventional Commits: A Base para Automação

O Semantic Versioning define como nomear versões. O Conventional Commits define como escrever mensagens de commit de forma que ferramentas possam determinar automaticamente qual parte da versão deve ser incrementada.

O formato é simples:

<tipo>[escopo opcional]: <descrição>

[corpo opcional]

[rodapé opcional]

Os tipos mais comuns e sua relação com o SemVer:

# Incrementa PATCH — correção de bug
git commit -m "fix: corrige cálculo incorreto de frete para regiões remotas"

# Incrementa MINOR — nova funcionalidade
git commit -m "feat: adiciona suporte a pagamento via PIX"

# Incrementa MAJOR — mudança incompatível (indicada no rodapé)
git commit -m "feat!: remove suporte à API v1

BREAKING CHANGE: os endpoints /api/v1/* foram removidos.
Migre para /api/v2/* conforme documentação."

# Não incrementa versão — mudanças que não afetam o produto
git commit -m "docs: atualiza exemplos de autenticação no README"
git commit -m "chore: atualiza dependências de desenvolvimento"
git commit -m "ci: adiciona cache de dependências no pipeline"
git commit -m "refactor: extrai lógica de validação para módulo separado"
git commit -m "test: adiciona testes para o módulo de pagamento"

Automatizando Releases com GitHub Actions

Com Conventional Commits no histórico, é possível automatizar completamente o processo de versionamento e geração de changelog. A ferramenta Release Please, mantida pelo Google, faz exatamente isso:

# .github/workflows/release.yml
name: Release

on:
  push:
    branches: [ main ]

permissions:
  contents: write
  pull-requests: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: googleapis/release-please-action@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          release-type: node

O que esse workflow faz: a cada push na main, o Release Please analisa os commits desde a última release, determina qual parte do SemVer deve ser incrementada, cria automaticamente um Pull Request com a atualização de versão e o changelog gerado. Quando esse PR é mergeado, uma tag Git e uma GitHub Release são criadas automaticamente.

Criando uma Release no GitHub Manualmente

Para projetos que preferem controle manual sobre o processo de release:

# Garante que a main está atualizada
git checkout main
git pull

# Cria a tag anotada
git tag -a v2.1.0 -m "Release 2.1.0

Novidades:
- Autenticação via OAuth com Google e GitHub
- Dashboard de métricas em tempo real
- Correção de falha em sessões simultâneas"

# Envia a tag
git push origin v2.1.0

No GitHub, navega-se até Releases → Create a release, seleciona-se a tag criada e adiciona-se as notas de release. O GitHub renderiza o markdown e disponibiliza a release publicamente, com download do código-fonte em zip e tar.gz automaticamente.

Um Fluxo Completo de Versionamento

Reunindo tudo em um cenário real:

# Desenvolvimento da funcionalidade
git checkout -b feature/dashboard-metricas
git commit -m "feat: adiciona componente de gráfico de requisições por minuto"
git commit -m "feat: adiciona filtro por intervalo de datas no dashboard"
git commit -m "test: adiciona testes do componente de gráfico"
git push origin feature/dashboard-metricas

# PR criado, revisado e mergeado na main
# O Release Please detecta dois commits 'feat' e propõe incremento MINOR
# Versão atual: 1.4.2 → proposta: 1.5.0

# PR de release mergeado → tag v1.5.0 criada automaticamente
# Changelog gerado automaticamente:

# ## [1.5.0] - 2025-03-10
# ### Features
# - adiciona componente de gráfico de requisições por minuto
# - adiciona filtro por intervalo de datas no dashboard

Fontes e leituras recomendadas

Especificações

Ferramentas

  • Release Please — GitHub — Ferramenta do Google para automação de releases baseada em Conventional Commits. Documentação completa no repositório.
  • semantic-release — Alternativa ao Release Please, mais configurável e com ecossistema de plugins. Popular em projetos Node.js.
  • git-cliff — Gerador de changelog altamente configurável, escrito em Rust. Suporta templates customizados para o formato de changelog.

Leitura complementar

  • Keep a Changelog — Guia e convenção para escrita manual de changelogs, em português. Explica os princípios por trás de um bom changelog mesmo quando o processo não é automatizado.

Exercícios

Exercício 1

Partindo da versão 2.4.1, qual é a próxima versão em cada caso abaixo? Por que os campos à direita voltam a zero?

a) correção de um bug, sem quebrar nada
b) nova funcionalidade compatível com o que já existia
c) remoção de endpoints que os clientes usavam
Ver resposta

✓ Resposta: (a) 2.4.2 — só o PATCH avança. (b) 2.5.0 — o MINOR avança e o PATCH zera. (c) 3.0.0 — o MAJOR avança e MINOR e PATCH zeram. O zeramento não é estética: o número precisa ser lido como uma afirmação sobre o histórico. Se a versão nova fosse 2.5.1, ela afirmaria que já houve uma correção aplicada sobre uma 2.5.0 — uma versão que nunca existiu. Zerar mantém o número honesto sobre o que de fato aconteceu.

Exercício 2

O que uma versão 0.x.y comunica? Que cuidado isso exige de quem consome essa dependência?

Ver resposta

✓ Resposta: MAJOR igual a zero sinaliza desenvolvimento inicial: a API pública ainda não é considerada estável. Na prática, isso suspende a garantia central do SemVer — a de que subir o MINOR é seguro. Entre 0.3.0 e 0.4.0 a compatibilidade pode ter sido quebrada legitimamente, porque a promessa só passa a valer a partir do 1.0.0. Para quem consome, o cuidado é concreto: fixar a versão exata em vez de usar faixas como ^0.3.0, e ler o changelog a cada atualização em lugar de confiar no número.

Exercício 3

Qual a diferença entre uma tag leve e uma tag anotada? E por que tags não são enviadas pelo git push comum?

Ver resposta

✓ Resposta: A tag leve é apenas um ponteiro para um commit. A anotada, criada com -a, é um objeto próprio no Git, que carrega autor, data e mensagem — por isso é a recomendada para release: ela registra quem publicou, quando e o quê, informação que a tag leve simplesmente não guarda. Quanto ao push, tags não são branches: são referências independentes, e o Git não presume que toda marcação feita localmente deva ir para o remoto. Daí a necessidade de git push origin v1.2.0 ou --tags. Esquecer esse passo é um tropeço frequente — a release existe na máquina de quem a criou e não existe para mais ninguém, nem para o pipeline configurado para disparar quando surge uma tag.

Exercício 4

Relacione cada tipo de Conventional Commit à parte do SemVer que ele incrementa. Por que docs, chore, ci, refactor e test não incrementam nada?

Ver resposta

✓ Resposta: fix: incrementa PATCH. feat: incrementa MINOR. feat!: ou o rodapé BREAKING CHANGE: incrementam MAJOR. Os demais não incrementam porque não alteram o que a pessoa que usa o software recebe: refatorar, documentar, ajustar o pipeline ou acrescentar testes muda o projeto por dentro, não o contrato externo — e é o contrato externo que o número de versão descreve. O ganho de fundo é outro: a convenção transforma a mensagem de commit em dado processável, e a decisão de versão deixa de ser um julgamento humano no fim do ciclo para virar consequência do que já foi registrado ao longo dele.

Exercício 5

O que o Release Please faz a cada push na main? Por que ele abre um Pull Request em vez de já criar a tag diretamente?

Ver resposta

✓ Resposta: Ele analisa os commits acumulados desde a última release, determina qual parte do SemVer deve ser incrementada e abre um PR contendo o bump de versão e o changelog já gerado. A tag e a GitHub Release só nascem quando esse PR é mergeado. O PR intermediário existe por dois motivos. O primeiro é conferência: você enxerga qual versão será publicada e exatamente o que entrará no changelog antes que isso se torne público e imutável. O segundo é agrupamento: vários merges podem se acumular no mesmo PR de release, evitando que cada commit na main vire uma versão publicada.

Exercício 6

No cenário do dashboard, dois commits feat produziram a versão 1.5.0 — e não 1.6.0. Por quê? E o que aconteceria se, no mesmo intervalo, houvesse também um fix e um BREAKING CHANGE?

Ver resposta

✓ Resposta: Porque o SemVer descreve a natureza da mudança, não a quantidade dela. Dois, dez ou cinquenta commits feat acumulados desde a 1.4.2 geram um único incremento de MINOR, resultando em 1.5.0. Com um fix junto, prevalece o de maior impacto: a versão continua 1.5.0 e a correção apenas aparece no changelog, sem elevar mais nada. Já um BREAKING CHANGE no intervalo vence todos os outros e leva a versão a 2.0.0 — porque o único aviso que realmente importa para quem consome é o de que algo deixou de funcionar como antes.

Comentários

Mais em DevOps

Azure para Quem Já Conhece AWS
Azure para Quem Já Conhece AWS

Um mapa de tradução para quem já domina a AWS: a hierarquia de tenant…

Processos, Serviços e o Comando `systemctl`
Processos, Serviços e o Comando `systemctl`

Processos e serviços no Linux: leitura de ps aux e top, busca com pgrep, a…

Testes Automatizados no Pipeline: Qualidade sem Atrito
Testes Automatizados no Pipeline: Qualidade sem Atrito

A pirâmide de testes aplicada ao pipeline: a proporção 70/20/10 e o antipadrão…