Versionamento de scripts e change tracking

1. Fundamentos do Versionamento para Scripts Bash

Versionar scripts Bash não é apenas uma boa prática — é uma necessidade para qualquer ambiente que dependa de automação. Sem controle de versão, uma simples alteração mal documentada pode quebrar pipelines inteiros sem possibilidade de rollback rápido. O versionamento oferece rastreabilidade completa: quem modificou o quê, quando e por quê.

A estrutura de diretórios recomendada para um repositório de scripts Bash é:

projeto-scripts/
├── scripts/          # Scripts executáveis principais
├── lib/              # Funções e bibliotecas compartilhadas
├── tests/            # Testes unitários (com bats, shunit2, etc.)
├── CHANGELOG.md      # Documentação de mudanças por versão
├── CHANGELOG/        # (opcional) changelogs por release
├── .gitignore        # Arquivos ignorados pelo Git
└── README.md

O Git é o sistema de controle de versão padrão da indústria, mas alternativas como Mercurial e SVN ainda são usadas em contextos legados. Para scripts Bash, o Git oferece leveza, branching eficiente e integração com CI/CD.

2. Organização do Repositório com Múltiplos Scripts

Para projetos com múltiplos scripts, duas abordagens são comuns:

  • Monorepo: todos os scripts em um único repositório. Vantagens: consistência, reuso de funções lib/, facilidade de refatoração cruzada. Desvantagem: repositório grande.
  • Múltiplos repositórios: cada script ou conjunto de scripts em repositórios separados. Vantagem: isolamento. Desvantagem: dificuldade de manutenção de dependências.

A padronização de nomes é crucial. Use kebab-case para scripts (backup-database.sh, deploy-app.sh) e prefixos de domínio (net:ping, fs:clean-temp). Exemplo de função em lib/utils.sh:

# lib/utils.sh
# Função para logging padronizado
log_info() {
    echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $*"
}

O .gitignore deve ignorar arquivos temporários, logs e outputs de execução:

# .gitignore
*.log
*.tmp
output/
__pycache__/
*.swp
.env

3. Estratégias de Branching e Fluxo de Trabalho

Adapte o Git Flow para scripts Bash:

  • main — versão estável e em produção
  • develop — integração de features em desenvolvimento
  • feature/* — branches para novas funcionalidades (ex.: feature/backup-s3)
  • hotfix/* — correções urgentes em produção (ex.: hotfix/fix-permissions)

Commits semânticos facilitam a geração de changelog:

feat(backup): adiciona suporte a compressão gzip
fix(deploy): corrige permissão do diretório temporário
docs(readme): atualiza instruções de instalação
refactor(utils): extrai função de validação de data

Políticas de merge: todo merge para main ou develop deve passar por revisão de código (pull request) e testes automatizados. Exemplo de comando para criar um branch de feature:

git checkout -b feature/backup-encryption develop
git commit -m "feat(backup): adiciona criptografia AES-256"
git push origin feature/backup-encryption

4. Versionamento Semântico e Tags

Aplique SemVer (MAJOR.MINOR.PATCH) aos scripts Bash:

  • MAJOR: mudanças incompatíveis (ex.: alteração de parâmetros obrigatórios)
  • MINOR: novas funcionalidades compatíveis (ex.: nova flag --compress)
  • PATCH: correções de bugs ou melhorias internas

Crie tags Git para releases:

git tag -a v1.0.0 -m "Release v1.0.0 - Backup com criptografia"
git tag -a v1.1.0-beta -m "Release v1.1.0-beta - Suporte a S3"
git push origin --tags

Para gerar changelog automaticamente a partir de commits e tags, use o comando:

git log --oneline --decorate v1.0.0..v1.1.0

5. Change Tracking e Documentação de Mudanças

Mantenha um CHANGELOG.md com seções por versão, seguindo o padrão Keep a Changelog:

# Changelog

## [1.1.0] - 2024-03-15
### Added
- Suporte a compressão gzip no backup
- Flag --encrypt para criptografia AES-256

### Fixed
- Permissão do diretório temporário em sistemas SELinux

## [1.0.0] - 2024-02-20
### Added
- Funcionalidade básica de backup de banco de dados
- Logs com timestamp

Use git log e git diff para rastrear alterações específicas:

# Ver commits de um arquivo específico
git log --oneline scripts/backup.sh

# Ver diff entre duas versões
git diff v1.0.0..v1.1.0 -- scripts/backup.sh

Inclua um cabeçalho em cada script com versão, autor e histórico:

#!/usr/bin/env bash
# backup.sh - Script de backup de banco de dados
# Versão: 1.1.0
# Autor: Maria Silva <maria@exemplo.com>
# Última modificação: 2024-03-15
# Histórico:
#   1.0.0 - 2024-02-20 - Versão inicial
#   1.1.0 - 2024-03-15 - Adicionado compressão e criptografia

6. Integração com Ferramentas de CI/CD e Linting

Em pipelines CI/CD, valide o versionamento antes de cada deploy:

# Exemplo de checagem em pipeline (GitHub Actions)
- name: Check version tag
  run: |
    if ! git describe --exact-match HEAD 2>/dev/null; then
      echo "Erro: commit não tem tag de versão"
      exit 1
    fi

Use ShellCheck para linting e testes unitários com bats:

# Instalar ShellCheck
sudo apt install shellcheck

# Verificar script
shellcheck scripts/backup.sh

# Executar testes com bats
bats tests/backup.bats

Automatize o bump de versão com um script auxiliar bump.sh:

#!/usr/bin/env bash
# bump.sh - Incrementa versão e cria tag
VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
MAJOR=$(echo "$VERSION" | cut -d. -f1 | tr -d 'v')
MINOR=$(echo "$VERSION" | cut -d. -f2)
PATCH=$(echo "$VERSION" | cut -d. -f3)

case "$1" in
  major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
  minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
  patch) PATCH=$((PATCH + 1)) ;;
  *) echo "Uso: $0 {major|minor|patch}"; exit 1 ;;
esac

NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}"
git tag -a "$NEW_TAG" -m "Release $NEW_TAG"
git push origin "$NEW_TAG"
echo "Nova tag: $NEW_TAG"

7. Boas Práticas para Colaboração e Revisão

Pull requests devem ter descrição clara das mudanças e impacto nos scripts:

## Descrição
Adiciona suporte a backup incremental com rsync.

## Mudanças
- scripts/backup.sh: nova flag --incremental
- lib/rsync-utils.sh: funções auxiliares para rsync
- tests/backup.bats: testes para backup incremental

## Impacto
- Compatível com versões anteriores (flag opcional)
- Requer rsync instalado no sistema

A revisão de código deve focar em:

  • Portabilidade: evite construções específicas de Bash (use #!/usr/bin/env bash)
  • Segurança: valide entradas, evite eval, use aspas em variáveis
  • Compatibilidade: scripts devem funcionar em Bash 4.x+

Use hooks Git para validar formatação e versionamento:

# .git/hooks/pre-commit
#!/usr/bin/env bash
echo "Executando ShellCheck..."
for file in $(git diff --cached --name-only --diff-filter=ACM | grep '\.sh$'); do
    shellcheck "$file" || exit 1
done

8. Exemplos Práticos e Templates

Template de cabeçalho de script com versionamento e changelog inline:

#!/usr/bin/env bash
# deploy.sh - Script de deploy automatizado
# Versão: 2.0.0
# Autor: João Costa <joao@exemplo.com>
# Última modificação: 2024-03-20
# Histórico:
#   1.0.0 - 2024-01-10 - Versão inicial com deploy via scp
#   1.1.0 - 2024-02-05 - Adicionado suporte a rollback
#   2.0.0 - 2024-03-20 - Reescrevido para usar rsync (incompatível com v1.x)

Script para gerar changelog automático a partir de tags:

#!/usr/bin/env bash
# generate-changelog.sh
# Gera changelog entre duas tags

PREV_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "")
CURR_TAG=$(git describe --tags --abbrev=0 HEAD 2>/dev/null || echo "HEAD")

if [ -z "$PREV_TAG" ]; then
    echo "## [$CURR_TAG] - $(date +%Y-%m-%d)"
    git log --oneline --no-decorate
else
    echo "## [$CURR_TAG] - $(date +%Y-%m-%d)"
    echo "### Mudanças desde $PREV_TAG"
    git log --oneline --no-decorate "$PREV_TAG..$CURR_TAG"
fi

Comandos úteis para o dia a dia:

# Criar tag anotada
git tag -a v1.0.0 -m "Release v1.0.0 - Backup básico"

# Ver histórico com tags
git log --oneline --decorate

# Listar todas as tags
git tag -l

# Ver diff entre duas versões de um script
git diff v1.0.0..v1.1.0 -- scripts/backup.sh

Referências