Comment Gardenscapes a failli être contrecarré une fois

Avertissement: Cette histoire s'est produite il y a plusieurs années. Mais il semble qu'il n'ait toujours pas perdu de sa pertinence.





... Nous avons développé Gardenscapes. Il y avait encore des traces des vieux Gardenscapes sous Windows. Ce n'était même pas Match-3, mais un objet caché. Et personne ne pouvait même imaginer les hauteurs que le jeu atteindrait.



Et puis un jour ...



Comment tout a commencé



Lors de l'accès au référentiel, nous avons vu le message suivant:



«Ce référentiel a été désactivé. L'accès à ce référentiel a été désactivé par le personnel de GitHub en raison d'une utilisation excessive des ressources, en violation de nos conditions d'utilisation. Veuillez contacter l'assistance pour restaurer l'accès à ce référentiel. Lisez ici pour en savoir plus sur la réduction de la taille de votre référentiel. "



Comme vous l'avez peut-être deviné, nous utilisons github pour héberger les dépôts git. Et donc, soudainement et sans déclarer la guerre, github a bloqué notre référentiel pour avoir dépassé la taille maximale autorisée. Le chiffre exact sur leur site Web n'a pas été donné. Au moment du verrouillage, la taille du dossier .git était d'environ 25 Go. (Remarque 2020: les limites sont désormais plus élevées et le site github indique explicitement que la taille du référentiel ne doit pas dépasser 100 Go).



Comment avons-nous réussi à créer un tel référentiel? La raison est claire: nous y stockons des fichiers binaires. Partout, il est écrit que ce n'est pas recommandé, mais c'est beaucoup plus facile pour nous. Nous voulons que le jeu soit lancé à partir du référentiel immédiatement, sans effort supplémentaire. Par conséquent, nous engageons des graphiques et d'autres ressources de jeu dans le référentiel.



Mais ce n'est pas si mal. Une leçon importante que nous avons tirée de toute cette histoire: ne parlez jamais à personne de Fight Club, vous ne pouvez pas valider les binaires dans le référentiel avec des fichiers qui changent fréquemment. Et nous l'avons fait: nous avons validé le fichier exécutable et les atlas de texture. Maintenant, nous sommes devenus beaucoup plus intelligents, et nous avons Teamcity, qui peut compiler un binaire et créer des atlas, ainsi que des scripts spéciaux qui téléchargent tout cela à l'utilisateur. Mais c'est une toute autre histoire... Et pour les fichiers très volumineux, nous utilisons Git LFS, Google Drive et d'autres avantages de la civilisation.



Lutte pour l'histoire



Donc, rien ne fonctionne pour personne. Nous avons dit à l'équipe qu'ils devraient travailler localement pendant une journée, mais ne pas essayer très dur, sinon ils régleraient les conflits plus tard (tout le monde était très bouleversé et est immédiatement parti prendre le thé). Et ils ont commencé à réfléchir à quoi faire. Il est clair qu'un nouveau référentiel est nécessaire, mais que faut-il y engager? Un moyen simple est l'état actuel de toutes les succursales. Mais cela ne nous a pas plu tant que ça, car l'historique des changements sera perdu, la commande git blame préférée de tout le monde se cassera, et tout ira périlleux. Par conséquent, nous avons décidé de faire ceci: effacer l'historique des fichiers binaires et conserver l'historique des fichiers texte.





Étape 1. Supprimez l'historique des binaires



Nous avions une copie locale complète du référentiel. La première chose que nous avons trouvée était l'excellent utilitaire BFG Repo-Cleaner . C'est très simple mais très rapide, et le titre est bon.



Un exemple de scénario d'exécution:



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


Les paramètres contiennent toutes les extensions des fichiers binaires que nous pourrions créer. De tous les commits dans le monde, les informations sur les fichiers avec ces extensions seront supprimées. L'utilitaire est intelligent et lors de la suppression de l'historique du fichier, il laisse sa version la plus récente. De plus, cette dernière version sera incluse dans le dernier commit sur la branche. Nous voulions également supprimer l'historique des fichiers exe et dll, mais l'utilitaire a donné une erreur. Apparemment, pour une raison quelconque, le traitement sous la forme de * .exe est interdit. De plus, si vous spécifiez explicitement un fichier, par exemple, gardenscapes.exe, alors tout fonctionne. (Remarque 2020: le bogue a peut-être déjà été corrigé).



Étape 2. Compressez le référentiel



Après la première étape, la taille du référentiel est toujours importante. La raison en est la façon dont git fonctionne. Nous n'avons supprimé que les liens vers des fichiers, mais les fichiers eux-mêmes sont restés.



Pour supprimer physiquement les fichiers, vous devez exécuter la commande git gc, à savoir:



git reflog expire --expire=now --all


 et que:



git gc --prune=now --aggressive


Il s'agit de la séquence de commandes recommandée par l'auteur de l'utilitaire. Ici, gc prend vraiment beaucoup de temps. De plus, avec les paramètres de référentiel par défaut, le client git n'a pas assez de mémoire pour terminer l'opération et a besoin de danser avec un tambourin. (Remarque 2020: à l'époque, nous avions une version 32 bits de git. Très probablement, ces problèmes ne sont plus dans la version 64 bits).



Étape 3. Écriture des validations dans le nouveau référentiel



Cela s'est avéré être la partie la plus intéressante de la quête. 



Pour comprendre ce qui suit, vous devez comprendre comment fonctionne git. Vous pouvez en savoir plus sur git dans de nombreux endroits, y compris notre blog:



  1. Git: Conseils pour les débutants - Partie 1
  2. Git: Conseils pour les débutants - Partie 2
  3. Git: Conseils pour les débutants - Partie 3


Donc, nous avons un très, très grand nombre de commits localement, ces commits sont corrects, c'est-à-dire sans l'historique des binaires. Il semblerait qu'il suffit d'exécuter git push et tout fonctionnera tout seul. Mais non!



Si vous exécutez simplement la commande git push -u master, puis git commence joyeusement le processus de téléchargement des données sur le serveur, mais se bloque avec une erreur d'environ 2 Go. Cela signifie que vous ne pourrez pas télécharger autant de commits en une seule fois. Nous mangerons l'éléphant par parties. Nous avons pensé que 2000 commits tiendraient probablement dans 2 Go. La taille totale de notre référentiel était alors d'environ 20 000 commits, répartis entre 4 branches: master-v101-v102-v103. (Note 2020: eh, jeunesse! Depuis, tout est devenu beaucoup plus sérieux. Il y a déjà plus de 100 000 commits dans ce référentiel, et il y a plusieurs dizaines de branches de release. En même temps, on rentre toujours dans les limites de Github)



Tout d'abord, on considère le nombre de commits dans les branches quand commande help:



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


Par exemple, il y a environ 10 000 validations dans la branche principale. Nous pouvons maintenant utiliser la syntaxe étendue pour la commande git push, à savoir:



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


HEAD ~ 8000: refs / origin / master est ce qu'on appelle refspec. Le côté gauche indique que vous devez prendre des commits jusqu'à un commit qui est à 8 000 de HEAD, c'est-à-dire à peu près 2 000 commits. Et le côté droit est que vous devez les pousser vers la branche principale distante. Le chemin complet vers la branche refs / origin / master est nécessaire ici.



Après cela, il n'y a toujours pas de branche principale et, par exemple, git fetch ne pourra pas la télécharger. Ce n'est pas surprenant - après tout, le commit qui pointerait vers sa HEAD n'existe pas encore. Néanmoins, en répétant la commande git push HEAD ~ 8000: refs / origin / master , nous avons vu la réponse que ces commits sont déjà sur le serveur, et, par conséquent, le travail est fait après tout.



Ensuite, nous avons pensé que le processus était clair et que le reste du travail pouvait être affecté au script. Le dernier commit sera très volumineux, car il contiendra tous les binaires. Par conséquent, juste au cas où, les 10 derniers commits sont remplis séparément. Le script s'est avéré comme ceci:



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
…  ..


Autrement dit, nous écrivons systématiquement toutes nos branches sur le serveur, 2000 commits par push et les 10 derniers commits séparément.



Toute cette histoire a pris beaucoup de temps et l'horloge a été affichée plus près de 12 heures du soir. Nous avons donc laissé le script travailler pendant la nuit, avons dit les prières appropriées à Cthulhu (Note 2020: c'était encore relativement populaire à l'époque) et sommes rentrés chez nous. 



Le final. Fin heureuse



Dans la matinée, après avoir ouvert le référentiel sur le site github, nous nous sommes assurés que le script fonctionnait correctement et que tous les commits et branches étaient en place.



Résultat: la taille du référentiel (dossier .git) a été réduite de 25 Go à 7,5 Go. En même temps, tous les historiques de commit importants - tout sauf les binaires - sont préservés. Les concepteurs du jeu ont bu plus de thé que d'habitude. Les programmeurs ont eu une expérience inoubliable. Et ils ont commencé à réfléchir de toute urgence à la façon de le faire afin qu'il ne soit pas nécessaire de valider le fichier exécutable dans le référentiel, mais il serait pratique de travailler avec.



All Articles