O versionamento e a gestão de releases são pilares fundamentais para qualquer equipe de desenvolvimento que busca entregar software de forma confiável e previsível. Nesta aula, vamos explorar as práticas e ferramentas essenciais para versionar seu código, documentar mudanças e automatizar o processo de publicação de novas versões. Você vai entender como o versionamento semântico traz clareza para a evolução do software, como os changelogs comunicam mudanças aos usuários e como as tags do Git permitem marcar pontos específicos na história do projeto. Ao final, você verá como integrar tudo isso em um pipeline de automação de releases, economizando tempo e reduzindo erros manuais.

Dominar esses conceitos é indispensável para um profissional de DevOps, pois a entrega contínua depende de um versionamento claro e de um processo de release bem definido. Vamos começar com o alicerce: o versionamento semântico.

Semantic versioning

O versionamento semântico, ou SemVer, é um padrão para atribuir números de versão a releases de software. Ele estabelece um formato de três números separados por pontos: MAJOR.MINOR.PATCH (por exemplo, 1.4.3). Cada componente tem um significado específico, permitindo que desenvolvedores e usuários entendam o impacto de uma nova versão apenas olhando para o número.

O incremento de cada número segue regras claras:

  • MAJOR: incrementado quando você faz mudanças incompatíveis na API. Por exemplo, remover uma função que era pública ou alterar seu comportamento de forma que quebre quem a utiliza. Isso sinaliza que os usuários precisam revisar e possivelmente adaptar seu código.
  • MINOR: incrementado quando você adiciona funcionalidades que são compatíveis com versões anteriores, ou seja, novas features que não quebram nada que já existia. Também pode incluir mudanças internas que não afetam a API pública.
  • PATCH: incrementado quando você faz correções de bugs que são compatíveis com versões anteriores. Essas correções não alteram a API e geralmente são pequenas.

Além disso, o SemVer permite pré-releases e metadados de build. Por exemplo, 1.0.0-alpha indica uma pré-versão, enquanto 1.0.0+build.5 pode incluir informações de build. As regras de precedência determinam a ordem de versões, importante para gerenciar dependências.

Vamos ver um exemplo prático de como aplicar SemVer em um projeto. Suponha que você tenha uma biblioteca com versão atual 1.2.3. Se você corrigir um bug, a nova versão será 1.2.4. Se adicionar uma nova funcionalidade sem quebrar a API, será 1.3.0. Se fizer uma mudança que quebra a API, será 2.0.0.

Para garantir a adesão, muitas equipes usam ferramentas como semantic-release ou commitlint para automatizar a determinação da próxima versão com base nos commits. Por exemplo, commits com prefixo feat: incrementam MINOR, e fix: incrementam PATCH. Isso será explorado na seção de automação.

Aqui está um exemplo de como definir uma versão em um arquivo package.json (Node.js):

{
  "name": "meu-projeto",
  "version": "1.2.3"
}

Ou em um arquivo pyproject.toml (Python):

[project]
name = "meu-projeto"
version = "1.2.3"

É importante que toda a equipe siga essas regras para que o versionamento seja consistente e significativo.

Changelogs

Um changelog é um arquivo que documenta as mudanças feitas em cada versão do software. Ele serve como um histórico para desenvolvedores e usuários, permitindo que saibam o que mudou, o que foi corrigido e o que foi adicionado. Um bom changelog é claro, conciso e orientado a cada versão.

A prática comum é manter um arquivo CHANGELOG.md na raiz do projeto. Cada entrada deve incluir a versão, a data e uma lista de mudanças categorizadas, geralmente por tipo: Adicionado, Alterado, Corrigido, Removido. Isso facilita a leitura e a busca por informações.

Exemplo de um trecho de changelog:

# Changelog

## [2.1.0] - 2025-03-15
### Adicionado
- Nova função de autenticação por biometria.
- Suporte a múltiplos idiomas.

### Alterado
- Melhoria no desempenho do login.

### Corrigido
- Bug que causava falha ao salvar preferências.

## [2.0.0] - 2025-02-01
### Removido
- API antiga de autenticação.

Manter um changelog manualmente pode ser trabalhoso, por isso muitas equipes automatizam sua geração a partir dos commits, usando ferramentas como git-changelog ou standard-version. Isso garante que nenhuma mudança seja esquecida e que o changelog seja gerado de forma padronizada.

Além disso, o changelog é uma forma de comunicação com os usuários, mostrando que o projeto está ativo e evoluindo. É uma boa prática incluir links para issues ou pull requests relevantes em cada entrada.

Tags

No Git, tags são marcadores que apontam para um commit específico, geralmente usado para marcar uma versão de release. Elas são como etiquetas permanentes que permitem voltar a um ponto exato da história do projeto. As tags são fundamentais para releases, pois permitem identificar facilmente o código que foi publicado.

Existem dois tipos de tags: leves e anotadas. Tags leves são apenas um ponteiro para um commit, sem informações extras. Tags anotadas são objetos armazenados no banco de dados do Git, contendo mensagem, autor e data, além de poderem ser assinadas. Para releases, é recomendado usar tags anotadas, pois fornecem mais contexto.

Para criar uma tag anotada, use o comando:

git tag -a v1.2.3 -m "Release 1.2.3"

Para listar tags:

git tag

Para ver informações de uma tag:

git show v1.2.3

É comum usar a convenção de prefixo v antes do número da versão (ex.: v1.2.3), mas isso é opcional. A tag deve ser criada no commit que representa a versão final do código antes do release.

Após criar a tag, é preciso enviá-la para o repositório remoto:

git push origin v1.2.3

Ou enviar todas as tags:

git push origin --tags

As tags também são usadas por ferramentas de CI/CD para disparar pipelines de deploy. Por exemplo, no GitHub Actions, você pode configurar um workflow que é acionado quando uma tag com o padrão v* é criada.

Automação de releases

Automatizar o processo de release é essencial para reduzir erros manuais e acelerar a entrega. Isso envolve determinar a próxima versão, gerar o changelog, criar a tag e publicar o artefato (por exemplo, pacote npm, imagem Docker, executável). Ferramentas como semantic-release automatizam todo esse fluxo, integrando-se ao histórico de commits.

O semantic-release analisa os commits desde a última tag para determinar o tipo de mudança (MAJOR, MINOR ou PATCH) e então gera a nova versão, atualiza o changelog, cria a tag e publica o pacote. Ele segue a especificação de Conventional Commits, que define prefixos como feat:, fix:, BREAKING CHANGE:.

Vamos ver um exemplo de configuração do semantic-release em um projeto Node.js. Primeiro, instale as dependências:

npm install --save-dev semantic-release @semantic-release/changelog @semantic-release/git

Depois, configure o arquivo .releaserc.json:

{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/changelog",
    "@semantic-release/npm",
    "@semantic-release/git"
  ]
}

Em um pipeline de CI, você pode executar o semantic-release após os testes. Por exemplo, em um GitHub Actions workflow:

name: CI

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm test
      - run: npx semantic-release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

Esse workflow executa os testes e, se passarem, o semantic-release analisa os commits e, se houver mudanças que justifiquem um release, ele cria a versão, publica no npm e cria a tag e o changelog automaticamente.

Outra ferramenta popular é o standard-version, que também automatiza a criação de tags e changelogs, mas de forma mais simples, sem publicar artefatos. Você pode integrar essas ferramentas ao seu fluxo de trabalho para garantir releases consistentes.

Além disso, é importante configurar permissões adequadas no repositório para que o bot de automação possa criar tags e fazer push. Em muitos casos, você precisará de um token com privilégios de escrita.

Boas práticas e observações finais

Algumas boas práticas para versionamento e releases:

  • Adote o versionamento semântico desde o início do projeto.
  • Mantenha o changelog atualizado, de preferência automatizado.
  • Utilize tags anotadas para releases.
  • Integre a automação de releases ao seu pipeline de CI/CD.
  • Use Conventional Commits para padronizar as mensagens de commit, facilitando a análise automatizada.
  • Teste o processo de release em um ambiente de staging antes de aplicar em produção.

Lembre-se de que o versionamento é uma forma de comunicação com sua equipe e usuários. Uma versão bem definida transmite confiança e previsibilidade.

Referências

Exercícios

  1. Explique a diferença entre incrementar a versão MAJOR, MINOR e PATCH, e dê um exemplo de cada tipo de mudança que justificaria cada incremento.

    ✓ Resposta: MAJOR é incrementado quando há mudanças incompatíveis na API, como remover uma função pública. MINOR é incrementado quando novas funcionalidades são adicionadas de forma compatível, como adicionar um novo parâmetro opcional. PATCH é incrementado para correções de bugs compatíveis, como corrigir um cálculo errado. Exemplo: se a versão atual é 1.0.0, uma correção de bug vira 1.0.1, uma nova feature vira 1.1.0, e uma quebra de API vira 2.0.0.
  2. Crie um changelog para uma versão fictícia 1.4.0 de um projeto, incluindo as categorias: Adicionado, Alterado e Corrigido.

    ✓ Resposta: Exemplo de changelog:
    ## [1.4.0] - 2025-03-20
    ### Adicionado
    - Novo endpoint para exportar relatórios em PDF.
    - Suporte a autenticação via OAuth2.
    
    ### Alterado
    - Melhorado o tempo de resposta do endpoint de busca.
    
    ### Corrigido
    - Corrigido bug que causava perda de dados ao salvar configurações.
  3. Escreva os comandos Git para criar uma tag anotada chamada v1.4.0 no commit atual e enviá-la para o repositório remoto.

    ✓ Resposta: Os comandos são:
    git tag -a v1.4.0 -m "Release 1.4.0"
    git push origin v1.4.0
  4. Qual é a função do plugin @semantic-release/changelog na configuração do semantic-release?

    ✓ Resposta: O plugin @semantic-release/changelog é responsável por gerar ou atualizar o arquivo CHANGELOG.md com base nas notas de release geradas pelo semantic-release. Ele garante que o changelog seja mantido automaticamente, refletindo as mudanças de cada versão.
  5. Descreva um cenário em que você usaria uma tag leve em vez de uma tag anotada, e explique por quê.

    ✓ Resposta: Uma tag leve pode ser usada para marcar um ponto temporário, como um commit intermediário de uma feature em desenvolvimento, sem necessidade de criar uma etiqueta oficial de release. Ela é útil para referências rápidas, mas para releases oficiais é recomendado usar tags anotadas, pois fornecem metadados adicionais e facilitam a rastreabilidade.