Como migrar código para um novo repositório e manter o histórico do GIT?
O GIT é uma ferramenta de controle de versão bastante difundida no universo dos desenvolvedores de software. De acordo com o portal expandedramblings, o Github – plataforma de hospedagem e gerenciamento de código fonte – atualmente possui cerca de 73 milhões de usuários ativos (números atualizados em 27 de novembro de 2021).
Esta popularidade pode se explicar principalmente por sua intuitividade na utilização e o poder de versionar e gerenciar código-fonte por intermédio de branches para um dado repositório. Gerenciar código-fonte isoladamente, de maneira incremental, amplamente colaborativa – múltiplos usuários enriquecendo a base de código, trabalhando simultaneamente em novas funcionalidades – de maneira simples e objetiva é o que me instiga a, sem pestanejar, iniciar meus projetos de software (de qualquer porte) por um git init.
Os repositórios no GIT podem ser gerenciados de duas maneiras; através de um:
monorepo (mono-repositório): um repositório único que agrega múltiplos projetos, dispostos de maneira modular em uma única estrutura de controle de versão (por exemplo: projeto_a_backend, projeto_a_front_end, projeto_b_backend, projeto_b_frontend); ilustrando:
monorepo
projeto_a_backend
// codigo_fonte_backend
projeto_a_frontend
// codigo_fonte_frontend
OU
multirepo (multi-repositório): como o nome diz, é a representação de múltiplos projetos em múltiplos repositórios (por exemplo: repositório projeto_a_backend, repositório projeto_a_frontend, repositório projeto_b_backend, repositório projeto_b_frontend); ilustrando:
projeto_a_backend
// codigo_fonte_backend
projeto_a_frontend
// codigo_fonte_frontend
Inicialmente pode parecer que essas informações não fazem muito sentido, mas acalme-se, pois elas não estão aqui de graça.
Problemática
Em um dos meus desafios no mercado de trabalho, a empresa em que eu atuava costumava gerenciar seus projetos em um monorepo. Em um certo momento dos produtos contidos no monorepo, o gerenciamento dessa maneira parou de fazer sentido – devido ao crescimento do código-fonte, das múltiplas linguagens programação em um mesmo repositório e os desacoplamentos que estavam ocorrendo por desdobramentos em regras de negócio.
Isto posto, a solução mais simples e tentadora seria: criar um novo repositório, copiar os fontes de cada um dos projetos do monorepo para sua correspondência no multirepo e voilá, problema resolvido, não é mesmo? E se eu te disse que a resposta é NÃO?
Ué... mas por que esta não seria a solução? Se o código-fonte está todo lá... quer dizer que não basta só executar o entusiasmante git init e mandar o commit inicial do projeto? O que poderia dar errado? Bom, inicialmente nada... Mas digamos que em um dado ponto de um desses novos repositórios eu precisasse consultar o histórico de modificações de um arquivo específico... E ao consultar o histórico, surpresa: a única modificação do arquivo é o commit inicial do projeto. Nada de histórico, tudo perdido na migração. E agora?
Claramente esse não foi o caminho adotado por mim, pois em hipótese alguma poderia perder a “trajetória do projeto” contida no histórico. A seguir, vou demonstrar como resolvi esse problema!
Solução
Por motivos de confidencialidade não poderei demonstrar o projeto real, migrado naquela situação; para elucidar o estudo de caso, criei dois repositórios hipotéticos: o monorepo, que centraliza os projetos projeto_a_backend e o projeto_a_frontend e os multirepos referente a migração do projeto_a_backend e do projeto_a_frontend. O exercício aqui irá consistir em efetuar a migração do projeto_a_backend e do projeto_a_frontend para fora do monorepo, mantendo exclusivamente os arquivos pertencentes a cada projeto, bem como o histórico de commits deles.
Ao iniciar, a estrutura do repositório monorepo é a seguinte – com o seguinte histórico de commits:
commit f85952b6ee55371b9cee90a8cdd91b969d7cca1b (HEAD -> master)
Author: Kaio Cesar Koerich <kckoerich@MacBook-Pro-de-Kaio-2.local>
Date: Wed Aug 3 22:43:27 2022 -0300
Edição do codigo_fonte_backend
commit 898746ec0d48243e7268a0c9bc10967d26295757
Author: Kaio Cesar Koerich <kckoerich@MacBook-Pro-de-Kaio-2.local>
Date: Wed Aug 3 22:42:23 2022 -0300
Edição do codigo_fonte_frontend
commit 888573ce8db917031e3f7dbe84e2a8f04eb7634c
Author: Kaio Cesar Koerich <kckoerich@MacBook-Pro-de-Kaio-2.local>
Date: Wed Aug 3 22:40:43 2022 -0300
Criação do projeto_a_frontend
commit c802fbf18027432b1c021ab2bf4e69d74b68bf78
Author: Kaio Cesar Koerich <kckoerich@MacBook-Pro-de-Kaio-2.local>
Date: Wed Aug 3 22:38:52 2022 -0300
Criação do projeto_a_backend
O resultado esperado após a divisão do monorepo deve ser:
Para a migração do projeto_a_backend para o seu próprio repositório, esse novo repositório deverá ter apenas os commits “Criação do projeto_a_backend” e ” Edição do codigo_fonte_backend” e mais um commit adicional referente a divisão do monorepo em multirepos.
Para a migração do projeto_a_frontend para o seu próprio repositório, esse novo repositório deverá ter apenas os commits “Criação do projeto_a_frontend” e “Edição do codigo_fonte_frontend” e mais um commit adicional referente a divisão do monorepo em multirepos.
Para atingir esse resultado, vamos ao passo-a-passo:
Antes de iniciar o processo de migração, crie uma cópia do projeto monorepo e renomeie-a para monorepo_tmp. Isto feito, instale o git-filter-repo (no MacOS, ele pode ser instalado através do brew, executando brew install git-filter-repo).
No diretório projeto monorepo_tmp (cópia do monorepo):
- execute: git remote rm origin para remover a origem do repositório temporário
- execute: git filter-repo --path projeto_a_backend --tag-rename '':projeto_a_backend –force para filtrar os commits associados ao projeto_a_backend
- mova os arquivos e diretórios resultantes do filtro aplicado para a raiz do repositório monorepo_tmp
- crie um commit para registrar as alterações: git add git commit -am "Divisão do monorepo em multirepos - projeto_a_backend"
- navegue até o diretório projeto_a_backend
- execute: git remote add monorepo_tmp ~/Development/revelo/workspace/monorepo_tmp
- execute: git pull monorepo_tmp master --allow-unrelated-histories
- execute: git remote rm monorepo_tmp confira o histórico de commits do novo repositório com o git log
O resultado deve ser:
commit acb7b2e59cb53fab2f246e6ef3c581331506ea37 (HEAD -> master, monorepo/master)
Author: Kaio Cesar Koerich <kckoerich@MacBook-Pro-de-Kaio-2.local>
Date: Wed Aug 3 23:43:55 2022 -0300
Divisão do monorepo em multirepos - projeto_a_backend
commit dbd826563f5bed6a219ed2490fb51f019a05cc9c
Author: Kaio Cesar Koerich <kckoerich@MacBook-Pro-de-Kaio-2.local>
Date: Wed Aug 3 22:43:27 2022 -0300
Edição do codigo_fonte_backend
commit c802fbf18027432b1c021ab2bf4e69d74b68bf78
Author: Kaio Cesar Koerich <kckoerich@MacBook-Pro-de-Kaio-2.local>
Date: Wed Aug 3 22:38:52 2022 -0300
Criação do projeto_a_backend
E a estrutura do projeto_a_backend deve estar da seguinte maneira:
Fazendo o mesmo para o projeto_a_frontend (atentando-se a fazer as devidas substituições nos passos 2 e no segundo bullet do passo 4), o resultado deve ser:
commit ac1340b8143c1433a4e932da9e3ba72c3a6aeaa3 (HEAD -> master)
Author: Kaio Cesar Koerich <kckoerich@MacBook-Pro-de-Kaio-2.local>
Date: Thu Aug 4 00:11:56 2022 -0300
Divisão do monorepo em multirepos - projeto_a_frontend
commit b425a0c990d2711ae40260715916b5e7c48f7e0f
Author: Kaio Cesar Koerich <kckoerich@MacBook-Pro-de-Kaio-2.local>
Date: Wed Aug 3 22:42:23 2022 -0300
Edição do codigo_fonte_frontend
commit 920a9e9f2963396c9ca2529736cd2ef66eb41b40
Author: Kaio Cesar Koerich <kckoerich@MacBook-Pro-de-Kaio-2.local>
Date: Wed Aug 3 22:40:43 2022 -0300
Criação do projeto_a_frontend
E a estrutura do projeto_a_frontend deve estar da seguinte maneira:
Conclusão
Migrar o projeto copiando e colando o código-fonte de um repositório para o outro é praticamente um crime contra o patrimônio de uma corporação – é sim sobre matar toda a evolução do produto até o momento da migração.
Independente do tamanho de uma companhia, quando se trata de código-fonte é importante que mantenhamos o histórico de commits do GIT em uma eventual migração de repositório, afinal é através deste histórico que podemos acompanhar a evolução do produto, entender algumas das tomadas de decisões efetuadas no projeto, bem como acompanhar a evolução das regras de negócio aplicadas àquele produto.