[Jean-Cloud](https://www.jean-cloud.net) est une petite association d'hébergement associatif sur du matériel de récupération. Elle lance en ce moment le projet Shlagernetes : un logiciel qui permet de répartir et gérer des services sur plusieurs serveurs de récupération. Git est utilisé dans certains cas pour installer un service sur un serveur ou le mettre à jour.
L’objectif est d'obtenir la dernière version (ou une version précise) d’un dépôt git, en utilisant le moins de ressources possible. Par ressources, nous entendons le flux de données qui part du remote pour arriver au dossier local, ainsi que la place mémoire occupée par le dépôt sur le serveur local.
Le dépôt Git créé n'enverra aucune donnée au remote. Il aura accès aux tags mais pas à l'historique. Il pourra éventuellement conserver certains fichiers locaux non-tracés en plus de ses clonages Git. Il incluera les éventuels submodules. Il peut vouloir télécharger le dernier commit de main (par défaut) ou bien un commit d'une certaine référence, c'est-à-dire branche ou tag.
Des tests sur différentes commandes ont été réalisés sur un dépôt factice. Le dossier de tests est transportable et peut être téléchargé ici. Attention, pour fonctionner en local, il nécessite d'autoriser le protocole pour les fichiers locaux : `git config --global protocol.file.allow always`.
Les tests consistent à analyser l'espace mémoire pris par le dépôt en local grâce à la commande bash "du", ainsi qu'à analyser le texte produit par Git lors du clonage.
-`depth=1` permet d'uniquement cloner le dernier commit et les objets nécessaires. Par défaut, elle est single-branch.
-`recurse-submodules` s'assure que le contenu des submodules est cloné
-`remote-submodules` s'assure que le contenu des submodules est cloné à partir du remote submodule originel
-`shallow-submodules` s'assure que seul le dernier commit des submodules est importé (pour que cela fonctionne en local, il faut préciser ://file/ avant le chemin des submodules)
`tags` permet de fetch les tags, elle doit être précisée y compris si un tag est fetched par référence
`depth=1` permet de considérer uniquement le dernier commit
`prune` permet de supprimer du dossier remote en local les références qui ne sont plus accessibles
`prune-tags` permet non seulement de supprimer du dossier remote en local les références qui ne sont plus accessibles, mais aussi de supprimer les tags locaux qui n'existent pas sur le remote
`recursive` applique la commande aux submodules de submodules etc.
`force` permet d'ignorer les changements locaux aux submodules et d'automatiquement check out la nouvelle version
`depth=1` permet de considérer uniquement le dernier commit du submodule
`remote` permet de mettre à jour depuis le remote submodule originel
ATTENTION : l'ordre compte. Exécuter cette instruction en premier la rendrait inefficace en raison de l'option `--recurse-submodules` du `git reset`. Celle-ci est néanmoins à conserver pour gérer le cas de délétion du submodule.
cette commande permet de marquer tous les reflogs isolés comme expirés immédiatement au lieu de 90 jours plus tard. Cela permet un plus grand nettoyage par git gc. git rev-list permet de vérifier quels objets sont reliés et ne seront pas marqués comme expirés.
Un clone partiel consiste à ne pas cloner tous les fichiers et/ou dossiers du dépôt, selon un filtre. Les filtres peuvent concerner les Binary Large Objects (blobs) ou bien les trees. Si le filtre concerne l'ancienneté, un clone partiel peut alors aussi être un clone superficiel. Les clones partiels peuvent être créés grâce à la commande `git clone --filter`.
Lors de check-out ou switch, des objets initialement ignorés par le `--filter` peuvent être importés.
Dans notre cas, nous ne souhaitons garder qu’un commit précis, qui sera dans tous les cas laissé passer par `git clone --filter` et ce n'est donc pas pertinent.
Les clones partiels peuvent aussi être créés par le `sparse-checking`. Certains fichiers et/ou dossiers n'apparaissent alors pas du tout dans le dossier local et ne sont pas concernés par les opérations git porcelain (de surface). Néanmoins, les objets associés à ces fichiers et dossiers sont toujours stockés dans le .git
Un clone superficiel peut être créé grâce à l'option `depth=<nombre>` qui indique le nombre de commits à conserver. Cette option est disponible pour la commande `clone` ainsi que `fetch`.
LFS est une extension Git qui permet de manipuler les fichiers choisis (par nom, expression ou taille) à l'aide d'un cache local. En pratique, les fichiers sont remplacés par des références dans le dépôt Git et un dossier local hors du dépôt est créé pour stocker les fichiers. Ils y sont téléchargés de manière lazy, c'est-à-dire uniquement lorsqu'ils sont checked out. Toutes les anciennes versions sont stockées sur un serveur en ligne.
C'est un mécanisme très intéressant, que nous ne retenons pas pour la même raison que le `git clone --filter` : nous ne souhaitons garder que la toute dernière version des fichiers, qui serait dans tous les cas téléchargée par LFS.
L'usage de la commande `git filter-branch` est déconseillé par la documentation Git. Elle présente plusieurs failles de sécurité et de performance. Elle permet de réécrire l'historique des branches grâce à des filtres.
La librairie Java repo-cleaner fonctionne, néanmoins la documentation Git estime que la librairie Python filter-repo est plus rapide et plus sûre. Nous ne souhaitons pas devoir installer Python ou Java donc ne creusons pas plus ces deux possibilités.
Nous souhaitons supprimer tout l'historique sans filtrer, donc la commande `git fetch --depth=1` suivie d'un git checkout, reset ou merge nous convient.
Une fois que nous avons fetched les modifications dans notre dossier local remote/, quelle est la meilleure façon de les appliquer à notre index et working directory ?
Néanmoins, puisque nous travaillons en `--depth=1`, les deux branches n'ont pas d'ancêtre commun, et nous devons d'ailleurs fournir l'option `--allow-unrelated-histories`. L'absence d'ancêtre commun empêche Git de reconnaître les similitudes à l'intérieur d'un même fichier. N'importe quelle modification d'un fichier tracé sur ours, même sur une nouvelle ligne, causera ainsi un conflit et sera écrasée. Cette commande permet tout de même de sauvegarder les fichiers nouvellement créés et committés sur ours.
Les fichiers non-committés nouvellement créés sont conservés à moins que `git clean` soit run.
[attention les notions de theirs et ours sont inversées ici car `git merge -s theirs` n'existe pas]
Cette commande applique une stratégie ours qui donne la prévalence à ours, qu'il y ait conflit ou pas. Elle va ignorer tous les changements et créations de fichiers committés sur theirs. Elle va également ignorer les modifications non committées. Les créations de fichier non-committées sont conservées à moins que `git clean` soit run. C'est le même résultat qu'avec la commande `git reset --hard`.
Comme l'option `git merge -s theirs` n'existe pas, nous devons faire une petite manipulation :
Cette commande est équivalente à `git merge -s ours` et à `git reset --hard`, à la différence près que nous finissons en detached HEAD state, ce qui n'est pas un problème dans notre cas puisque nous ne souhaitons pas push de modification depuis notre dépôt.
`git reset --hard` est équivalente à `git merge -s ours` et `git checkout --force -B`.
Avantage :
Inconvénient :
Les tests nous montrent que les options les plus économes en mémoire sont `git checkout -force -B`, `git merge -s ours`, `git --reset hard`, qui font la même chose. Néanmoins `git reset --hard` n'implique pas la création d'une branche temporaire et ne finit pas en detached HEAD state, c'est donc celle que nous retenons.
Les mêmes règles s'appliquent aux submodules qu'au reste du dépôt. Il est possible de préciser dans le fichier .gitmodules des règles d'import des submodules, comme un certain tag ou une certaine branche par exemple.
En retirant `--remote-submodules` du `git clone` et `--remote` du `git submodule update`, alors les submodules seront identiques au dépôt que nous clonons et plus au dépôt originel du submodule.
Le script est composé de vingt-neuf tests (listés dans les résultats ci-dessous), qui s'appuient sur trois fonctions : generate_random_file, get_storage_used et get_bandwidth.
Les tests suivants concernent la mise à jour du dépôt selon différentes commandes, avec pour chaque commande trois cas : après addition d'un fichier, après délétion d'un fichier, après addition puis délétion d'un fichier.