Três operações grandes, todas irreversíveis na prática e todas com a mesma regra de ouro: trabalhe sobre uma cópia e mantenha o original intacto até o fim. Migrar de SVN preserva história com git svn clone e um arquivo de autores. Dividir um repositório em dois preservando o histórico de um subdiretório é git filter-repo --subdirectory-filter. Fundir dois repositórios é merge --allow-unrelated-histories. Em todas, o custo maior não é técnico: é de coordenação — todo mundo precisa reclonar, e as referências antigas quebram.
O --mirror copia todas as refs, não só os ramos — é o que garante que você possa voltar. Guarde isso em outro lugar e só o apague semanas depois de a operação estar validada em produção.
E planeje a coordenação antes de executar: anuncie a data, congele merges, e prepare as instruções de reclone. Cirurgia bem-sucedida com time despreparado ainda é um dia perdido.
Migrar de SVN para Git
Ainda acontece, e mais do que se imagina — sistemas corporativos iniciados nos anos 2000 seguem em Subversion.
Passo 1: mapear autores. O SVN guarda logins; o Git quer nome e e-mail.
# lista os autores existentes no repositório SVNsvn log --quiet | grep -E "^r[0-9]+ \|" | awk '{print $3}' | sort -u
Monte um autores.txt:
jsilva = João Silva <joao.silva@empresa.com>mferreira = Maria Ferreira <maria@empresa.com>
O --stdlayout assume a convenção trunk/, branches/, tags/. Se o repositório não a segue — e projetos antigos frequentemente não seguem —, é preciso indicar os caminhos manualmente com --trunk, --branches e --tags.
Passo 3: converter tags e ramos. O git svn traz tags do SVN como ramos remotos; é preciso convertê-las em tags Git de verdade. Ferramentas como o svn2git automatizam esse pós-processamento.
Passo 4: limpar e publicar. Revise o resultado (git log, contagem de commits, ramos), remova os metadados git-svn-id das mensagens se quiser (filter-repo --message-callback), e publique.
O clone pode levar dias
O que acontece: repositórios SVN grandes, com muitas revisões, levam horas ou dias para converter — o git svn busca revisão por revisão.
Por quê: o protocolo do SVN é centralizado e cada revisão é uma consulta.
Como conviver: rode num servidor, em sessão que sobreviva à desconexão, e valide numa amostra (-r HEAD~1000:HEAD) antes de rodar tudo. Para repositórios realmente grandes ou com história bagunçada, ferramentas dedicadas de conversão (como o reposurgeon) existem justamente para isso.
Dividir: extrair um subdiretório com sua história
Cenário clássico: um monólito onde servicos/faturamento/ vai virar repositório próprio, e você quer levar o histórico daquela pasta junto.
git clone <origem> faturamento && cd faturamentogit filter-repo --subdirectory-filter servicos/faturamento
O resultado: um repositório onde o conteúdo daquela pasta está na raiz, e o histórico contém só os commits que a tocaram, reescritos. Commits que não mexeram ali desaparecem.
Variação, quando você quer manter o caminho ou renomear:
Depois: criar o repositório novo, git remote add origin ..., publicar. E, no repositório de origem, remover a pasta num commit normal — não reescreva o original a menos que haja motivo forte (nota 25).
O que se perde na divisão
Os hashes mudam (nota 17), então referências a commits em issues, PRs e documentação antigos deixam de resolver no repositório novo. E mudanças que atravessavam a fronteira — um commit que alterava faturamento/ e comum/ juntos — passam a aparecer pela metade. É útil registrar num README do repositório novo o hash de origem da divisão e a data.
Fundir: trazer um repositório para dentro de outro
O inverso: frontend e backend viram um monorepo (nota 27).
O --allow-unrelated-histories é necessário porque as duas histórias não têm ancestral comum (nota 18) — sem essa flag, o Git recusa por segurança.
O problema é que isso despeja os arquivos do outro repositório na raiz. Para colocá-los numa subpasta preservando a história, reescreva antes de fundir:
cd ../frontendgit filter-repo --to-subdirectory-filter apps/frontend
Aí todos os commits daquele repositório passam a mexer em apps/frontend/..., e o merge encaixa no lugar certo, sem conflito e sem commit de movimentação.
graph LR
A["repo frontend<br/>arquivos na raiz"] -->|"filter-repo<br/>--to-subdirectory-filter"| B["repo frontend<br/>tudo sob apps/frontend/"]
B -->|"fetch + merge<br/>--allow-unrelated-histories"| C["monorepo<br/>história dos dois preservada"]
D["repo backend"] -->|idem| C
Depois da cirurgia: a lista de verificação
Item
Por quê
Todos reclonaram
pull sobre história reescrita cria monstros (nota 25)
PRs abertos migrados ou refeitos
as referências apontam para commits inexistentes
Tags recriadas e publicadas
--follow-tags não recria o que se perdeu
CI/CD apontando para o lugar certo
URLs, segredos, permissões
Proteções de branch reconfiguradas
rulesets não migram com o repositório (nota 15)
Links em documentação e issues
os hashes mudaram
Backup --mirror guardado
por semanas, não por dias
Registro do que foi feito
um MIGRACAO.md com data, comando e hash de origem
O último item é o que separa uma cirurgia de uma bagunça: quem herdar esse repositório daqui a três anos vai encontrar uma história que começa do nada, e merece saber por quê. É exatamente o tipo de artefato que a arqueologia de software procura e quase nunca encontra.
Armadilhas comuns
Fazer cirurgia sem congelar o trabalho
O que acontece: enquanto você converte, alguém commita no original. O resultado da migração já nasce desatualizado, e reconciliar é pior que refazer.
Por quê: a operação leva tempo e produz uma história incompatível.
Como evitar: janela combinada, repositório em modo somente leitura durante a operação, e um ensaio completo antes — inclusive da parte de coordenação.
Migrar tudo "porque dá"
O que acontece: trazem-se vinte anos de história de um SVN que ninguém vai consultar, com meses de trabalho de limpeza.
Por quê: parece perda descartar história.
Como decidir: pergunte quem vai consultar e para quê. Uma alternativa honesta e muito usada: importar apenas os últimos anos, e manter o SVN em modo leitura como arquivo consultável. Registre a decisão no MIGRACAO.md.
Esquecer que forks e clones antigos continuam existindo
O que acontece: meses depois, alguém empurra a partir de um clone pré-cirurgia e reintroduz a história antiga.
Por quê: cópias são autônomas (nota 01).
Como evitar: proteções de branch impedindo push não-fast-forward (nota 15), e comunicação explícita de que clones antigos devem ser apagados.
Resumo em uma frase
Cirurgia de repositório é sempre “copie, opere na cópia, valide, coordene a troca” — o comando é a parte fácil, a coordenação é a parte que dá errado.
Vídeo — a divisão feita sem cortes
splitting a monorepo with git filter-branch / filter-repo (anthonywritescode, 17 min) é uma sessão sem edição: o autor divide um repositório real, erra, volta e explica cada decisão. É exatamente a parte que um tutorial limpo esconde — e o que essa cirurgia parece quando não sai perfeita na primeira tentativa.
Pratique
Faça a divisão e a fusão num par de repositórios de brinquedo, que é o exercício que ensina mais rápido:
# divisãogit clone repo-grande extraido && cd extraidogit filter-repo --subdirectory-filter modulo-xgit log --oneline # só os commits daquele módulo, na raiz# fusãocd ../outro && git filter-repo --to-subdirectory-filter libs/outrocd ../destino && git remote add o ../outro && git fetch ogit merge --allow-unrelated-histories o/main
Rodar git log --oneline --graph no resultado da fusão, e ver duas linhagens independentes se encontrando num commit, é a melhor demonstração possível do que “história sem ancestral comum” significa.
O que vem a seguir
Falta a última nota do nível, e ela fecha a fronteira com a disciplina vizinha: o que a automação espera do repositório, e por que o pipeline se comporta diferente da sua máquina.
30 — Git no CI/CD e GitOps — o contrato entre repositório e pipeline.