Como Gardenscapes foi quase frustrado uma vez

Aviso: Esta história aconteceu há vários anos. Mas parece que ainda não perdeu sua relevância.





... Desenvolvemos Gardenscapes. Ele ainda tinha vestígios das antigas paisagens dos jardins sob o Windows. Não era nem Match-3, mas um objeto escondido. E ninguém poderia imaginar as alturas que o jogo iria atingir.



E então um belo dia ...



Como tudo começou



Ao acessar o repositório, vimos a seguinte mensagem:



“Este repositório foi desativado. O acesso a este repositório foi desativado pela equipe do GitHub devido ao uso excessivo de recursos, em violação aos nossos Termos de Serviço. Entre em contato com o suporte para restaurar o acesso a este repositório. Leia aqui para saber mais sobre como diminuir o tamanho do seu repositório. "



Como você deve ter adivinhado, usamos o github para hospedar repositórios git. E então, de repente e sem declarar guerra, o github bloqueou nosso repositório por exceder o tamanho máximo permitido. O número exato não foi informado em seu site. No momento do bloqueio, a pasta .git tinha aproximadamente 25 GB de tamanho. (Nota 2020: os limites agora são mais altos, e o site github afirma explicitamente que o tamanho do repositório não deve exceder 100 GB).



Como conseguimos fazer um repositório tão grande? O motivo é claro: armazenamos arquivos binários nele. Está escrito em todos os lugares que não é recomendado fazer isso, mas é muito mais fácil para nós. Queremos que o jogo seja iniciado a partir do repositório imediatamente, sem esforço adicional. Portanto, colocamos gráficos e outros recursos do jogo no repositório.



Mas isso não é tão ruim. Uma lição importante que aprendemos com toda essa história: nunca conte a ninguém sobre o Fight Club, você não pode enviar binários para o repositório com arquivos que mudam frequentemente. E fizemos isso: comprometemos o arquivo executável e os atlas de textura. Agora nos tornamos muito mais inteligentes e temos o Teamcity, que pode compilar um binário e criar atlas, além de scripts especiais que baixam tudo isso para o usuário. Mas essa é uma história completamente diferente... E para arquivos muito grandes, usamos Git LFS, Google Drive e outros benefícios da civilização.



Luta pela história



Portanto, nada funciona para ninguém. Dissemos à equipe que eles teriam que trabalhar localmente por um dia, mas não se esforçar muito, caso contrário resolveriam os conflitos mais tarde (todos estavam muito chateados e imediatamente saíram para o chá). E eles começaram a pensar no que fazer. É claro que um novo repositório é necessário, mas o que fazer para confirmar? Uma maneira fácil é o estado atual de todos os ramos. Mas não gostamos muito, porque o histórico de mudanças será perdido, o comando git blame favorito de todos será quebrado e tudo dará um salto mortal. Portanto, decidimos fazer isso: apagar o histórico dos arquivos binários e manter o histórico dos arquivos de texto.





Etapa 1. Exclua o histórico de binários



Tínhamos uma cópia local completa do repositório. A primeira coisa que fizemos foi encontrar o excelente utilitário BFG Repo-Cleaner . É muito simples, mas muito rápido, e o título é bom.



Um exemplo de cenário de execução:



java -jar bfg.jar bfg --delete-files *.{pvrtc,webp,png,jpeg,fla,swl,swf,pbi,bin,mask,ods,ogv,ogg,ttf,mp4} path_to_repository


Os parâmetros contêm todas as extensões dos arquivos binários que podemos criar. De todos os commits do mundo, informações sobre arquivos com essas extensões serão deletadas. O utilitário é inteligente e ao deletar o histórico de um arquivo deixa sua versão mais recente. Além disso, esta versão mais recente será incluída no commit mais recente do branch. Também queríamos excluir o histórico dos arquivos exe e dll, mas o utilitário apresentou um erro. Aparentemente, por algum motivo, o processamento na forma de * .exe é proibido. Além disso, se você especificar explicitamente um arquivo, por exemplo, gardenscapes.exe, tudo funciona. (Nota 2020: o bug pode já ter sido corrigido).



Etapa 2. Compactar o repositório



Após a primeira etapa, o tamanho do repositório ainda é grande. A razão para isso é a forma como o git funciona. Removemos apenas links para arquivos, mas os próprios arquivos permaneceram.



Para excluir fisicamente os arquivos, você precisa executar o comando git gc, a saber:



git reflog expire --expire=now --all


 e então:



git gc --prune=now --aggressive


Esta é a seqüência de comandos recomendada pelo autor do utilitário. Aqui gc realmente leva muito tempo. Além disso, com as configurações de repositório padrão, o cliente git não tem memória suficiente para completar a operação e precisa dançar com um pandeiro. (Nota 2020: naquela época tínhamos uma versão de 32 bits do git. Muito provavelmente, esses problemas não estão mais na versão de 64 bits).



Etapa 3. Gravando commits no novo repositório



Essa acabou sendo a parte mais interessante da missão. 



Para entender o que se segue, você precisa entender como o git funciona. Você pode ler mais sobre o git em muitos lugares, incluindo nosso blog:



  1. Git: Dicas para iniciantes - Parte 1
  2. Git: dicas para iniciantes - parte 2
  3. Git: Dicas para iniciantes - Parte 3


Portanto, temos muitíssimos commits localmente, esses commits estão corretos, ou seja, sem o histórico de binários. Parece que basta executar git push e tudo funcionará sozinho. Mas não!



Se você apenas executar o comando git push -u master, então o git alegremente começa o processo de upload de dados para o servidor, mas falha com um erro de cerca de 2 GB. Isso significa que você não poderá fazer upload de tantos commits de uma vez. Vamos comer o elefante em partes. Calculamos que 2.000 commits provavelmente caberiam em 2 GB. O tamanho total do nosso repositório era então de cerca de 20.000 commits, distribuídos entre 4 branches: master-v101-v102-v103. (Nota 2020: eh, juventude! Desde então, tudo se tornou muito mais sério. Já existem mais de 100.000 commits neste repositório, e existem várias dúzias de ramos de lançamento. Ao mesmo tempo, ainda nos encaixamos nos limites do Github)



Em primeiro lugar, consideramos o número de commits nos ramos quando comando de ajuda:



git rev-list --count <branch-name>


Por exemplo, existem aproximadamente 10.000 commits no branch master. Agora podemos usar a sintaxe estendida para o comando git push, a saber:



git push -u origin HEAD~8000:refs/origin/master


HEAD ~ 8000: refs / origin / master é o denominado refspec. O lado esquerdo diz que você precisa levar os commits até um commit que está a 8.000 de HEAD, ou seja, apenas cerca de 2.000 commits. E o lado certo é que você precisa empurrá-los para o branch master remoto. O caminho completo para refs / origin / master branch é necessário aqui.



Depois disso, ainda não existe um branch master e, por exemplo, git fetch não será capaz de baixá-lo. Isso não é surpreendente - afinal, o commit que aponta para o HEAD dela ainda não existe. No entanto, repetindo o comando git push HEAD ~ 8000: refs / origin / master , vimos a resposta que esses commits já estão no servidor, e, portanto, o trabalho está feito afinal.



Em seguida, pensamos que o processo é claro e o resto do trabalho pode ser atribuído ao script. O último commit será muito grande, pois conterá todos os binários. Portanto, apenas no caso, os últimos 10 commits são preenchidos separadamente. O script ficou assim:



git push origin HEAD~6000:refs/origin/master
git push origin HEAD~5000:refs/origin/master
git push origin HEAD~4000:refs/origin/master
git push origin HEAD~3000:refs/origin/master
git push origin HEAD~2000:refs/origin/master
git push origin HEAD~1000:refs/origin/master
git push origin HEAD~10:refs/origin/master
git push origin master
 
git checkout v101
 
git push -u origin HEAD~1000:refs/origin/v101
git push origin HEAD~10:refs/origin/v101
git push origin v101
 
git checkout v102
…  ..


Ou seja, gravamos consistentemente todos os nossos branches no servidor, 2.000 commits por push e os últimos 10 commits separadamente.



Essa história toda demorou muito, e o relógio foi mostrado perto das 12 da noite. Portanto, deixamos o roteiro funcionar durante a noite, fizemos as orações adequadas a Cthulhu (Nota 2020: ainda era relativamente popular na época) e fomos para casa. 



O final. Final feliz



De manhã, tendo aberto o repositório no site do github, nos certificamos de que o script funcionou com sucesso e que todos os commits e branches estavam no lugar.



Como resultado: o tamanho do repositório (pasta .git) foi reduzido de 25 GB para 7,5 GB. Ao mesmo tempo, todo o histórico de commits importante - tudo exceto binários - é preservado. Os designers do jogo beberam mais chá do que o normal. Os programadores tiveram uma experiência inesquecível. E começaram a pensar urgentemente em como fazê-lo para que não fosse necessário enviar o arquivo executável para o repositório, mas seria conveniente trabalhar com ele.



All Articles