Git filter-branch vs filter-repo: reescrevendo histórico com segurança
1. Introdução à reescrita de histórico no Git
Reescrever o histórico de um repositório Git é uma operação delicada, porém necessária em diversas situações. Os casos de uso mais comuns incluem:
- Remoção de dados sensíveis: arquivos com senhas, chaves de API ou informações pessoais que nunca deveriam ter sido commitados
- Limpeza de arquivos grandes: remoção de binários ou arquivos que inflam desnecessariamente o repositório
- Correção de autoria: ajuste de e-mails ou nomes de autores incorretos
Os riscos inerentes à reescrita de histórico são significativos: commits órfãos, conflitos em branches compartilhados, perda irreversível de dados e quebra do fluxo de trabalho da equipe. Por isso, a escolha da ferramenta correta é crucial.
Duas ferramentas dominam esse cenário: o clássico git filter-branch (considerado legado) e o moderno git filter-repo (oficialmente recomendado). Neste artigo, exploraremos ambas em profundidade.
2. Git filter-branch: a ferramenta clássica (e problemática)
O git filter-branch foi durante anos a única opção nativa para reescrita de histórico. Sua sintaxe básica permite operações como remoção de arquivos:
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch arquivo-sensivel.pdf" \
--prune-empty --tag-name-filter cat -- --all
Para alterar autor:
git filter-branch --env-filter \
'if [ "$GIT_AUTHOR_EMAIL" = "antigo@email.com" ]; then
export GIT_AUTHOR_EMAIL="novo@email.com";
export GIT_COMMITTER_EMAIL="novo@email.com";
fi' -- --all
Limitações conhecidas
O filter-branch apresenta problemas graves:
- Lentidão extrema: em repositórios com milhares de commits, a operação pode levar horas
- Comportamento imprevisível: falha silenciosamente em merges complexos
- Falta de segurança: não exige confirmação explícita antes de reescrever referências
- Uso intensivo de memória: mantém todo o histórico em RAM
Armadilhas comuns
Usuários frequentes do filter-branch conhecem bem os perigos:
# PERIGO: sobrescrita acidental de branches
git filter-branch --tree-filter 'rm -f senhas.txt' HEAD
# PERIGO: sem backup, sem verificação
# Se algo der errado, o reflog pode não salvar
A documentação oficial do Git passou a desencorajar ativamente o uso do filter-branch, classificando-o como uma ferramenta que "pode causar problemas" e "não é segura".
3. Git filter-repo: a alternativa moderna e segura
O git filter-repo é uma ferramenta externa, oficialmente recomendada pela equipe do Git. Sua instalação é simples:
# Instalação via pip
pip install git-filter-repo
# Ou via gerenciador de pacotes do sistema
brew install git-filter-repo # macOS
sudo apt install git-filter-repo # Ubuntu/Debian
Comandos essenciais
Remoção de arquivo específico:
git filter-repo --path arquivo-sensivel.pdf --invert-paths
Substituição de e-mails usando mailmap:
# Primeiro, crie um arquivo .mailmap
git filter-repo --mailmap <(echo "Novo Nome <novo@email.com> <antigo@email.com>")
Renomeação de tags:
git filter-repo --tag-rename 'v1:release-v1'
Vantagens sobre filter-branch
O filter-repo oferece benefícios substanciais:
- Performance: 10x a 100x mais rápido que
filter-branch - Segurança: exige
--forceexplícito para reescrever - Suporte nativo a merges: processa merges complexos sem intervenção manual
- Uso eficiente de recursos: opera em modo streaming, sem carregar tudo em memória
4. Comparação prática: filter-branch vs filter-repo
Cenário 1: Remoção de dados sensíveis
Com filter-branch:
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch dados-clientes.csv" \
--prune-empty --tag-name-filter cat -- --all
# Tempo estimado: 15 minutos para 5000 commits
Com filter-repo:
git filter-repo --path dados-clientes.csv --invert-paths
# Tempo estimado: 30 segundos para 5000 commits
Cenário 2: Substituição de e-mails em massa
Com filter-branch:
git filter-branch --env-filter \
'if [ "$GIT_AUTHOR_EMAIL" = "@empresa-antiga.com" ]; then
export GIT_AUTHOR_EMAIL="@empresa-nova.com";
export GIT_COMMITTER_EMAIL="@empresa-nova.com";
fi' -- --all
Com filter-repo:
git filter-repo --email-callback \
'return email.replace(b"@empresa-antiga.com", b"@empresa-nova.com")'
Cenário 3: Extração de subdiretório
Com filter-branch (problemático):
git filter-branch --subdirectory-filter src/modulo -- --all
# Perde histórico de merges e tags
Com filter-repo:
git filter-repo --path src/modulo/ --path-rename src/modulo/:
# Preserva merges e permite renomear caminhos
Em repositórios de médio porte (10.000 commits, 500MB), o filter-repo completa operações em segundos onde o filter-branch levaria horas.
5. Boas práticas de segurança ao reescrever histórico
Antes de qualquer operação de reescrita, siga este checklist:
Backup completo
# Crie um mirror completo do repositório
git clone --mirror https://github.com/usuario/repo.git repo-backup
# Verifique a integridade do backup
cd repo-backup
git fsck --full
Uso de branches isolados
# Nunca reescreva main diretamente
git checkout -b reescrita-segura
git filter-repo --path dados-sensiveis.txt --invert-paths
# Apenas após validação, force o push
git push origin reescrita-segura --force
Verificação pós-operação
# Verifique a integridade do repositório
git fsck --full
# Compare hashes de commits preservados
git log --oneline --all | head -20
# Revise o diff final
git diff main..reescrita-segura --stat
Comunicação com a equipe
Sempre avise a equipe com antecedência e estabeleça uma janela de manutenção. Todos devem ter feito push de seus trabalhos antes da reescrita.
6. Casos especiais e resolução de problemas
Merges complexos
O filter-repo lida nativamente com merges complexos. O filter-branch exige o uso de --parent-filter:
# filter-branch: necessário filtro manual para merges
git filter-branch --parent-filter \
'sed "s/^\$//"' -- --all
Tags e releases
# filter-repo preserva tags por padrão
git filter-repo --path arquivo-grande.zip --invert-paths
# Se precisar renomear tags
git filter-repo --tag-rename 'v:release-'
Recuperação de desastres
Se algo der errado:
# Use o reflog para encontrar o estado anterior
git reflog show --all
# Restaure a partir do backup
cd repo-backup
git push ../repo-original --all --force
git push ../repo-original --tags --force
7. Conclusão e recomendações finais
As diferenças cruciais entre as ferramentas são claras:
| Característica | filter-branch | filter-repo |
|---|---|---|
| Performance | Lento (horas) | Rápido (segundos) |
| Segurança | Baixa | Alta (--force obrigatório) |
| Merges | Problemático | Suporte nativo |
| Manutenção | Legado (não recomendado) | Ativo (recomendado) |
Quando usar filter-branch? Apenas em ambientes extremamente restritos onde a instalação de ferramentas externas é proibida. Em todos os outros casos, use filter-repo.
Checklist final para reescrita segura:
- ✅ Crie backup completo (
git clone --mirror) - ✅ Trabalhe em branch isolado
- ✅ Teste a operação em um clone local
- ✅ Valide o resultado com
git fsck - ✅ Comunique a equipe
- ✅ Use
git push --force-with-lease(não--forcesimples) - ✅ Confirme que todos os membros atualizaram seus clones
Reescrever histórico é uma operação de alto risco. Com as ferramentas e práticas corretas, você pode realizá-la com segurança e eficiência.
Referências
- Documentação oficial do git-filter-repo — Repositório oficial com documentação completa, exemplos e guia de migração do filter-branch
- Documentação oficial do git-filter-branch — Página de manual oficial do Git, com alertas sobre as limitações da ferramenta
- Git Tools - Rewriting History (Pro Git Book) — Capítulo do livro Pro Git sobre reescrita de histórico, incluindo filter-branch e filter-repo
- How to Use Git Filter-Repo to Rewrite Git History — Tutorial da Atlassian com exemplos práticos de migração e uso do filter-repo
- Removing sensitive data from a repository (GitHub Docs) — Guia oficial do GitHub sobre remoção de dados sensíveis, com recomendações de ferramentas
- git filter-repo: The New, Faster Way to Rewrite Git History — Artigo do GitLab comparando performance entre filter-branch e filter-repo em repositórios reais