Monorepos JS en prod 5 : fusion des référentiels Git et conservation de l'historique des commits

Chez Adaltas, nous maintenons plusieurs projets open-source Node.js organisés en monorepos Git et publiés sur NPM. Nous avons partagé notre expérience pour travailler avec Monorepos de Lerne dans une série d’articles :

C’est maintenant au tour de notre populaire open-source CSV de nœud projet à migrer vers un monorepo. Cet article vous guidera à travers les approches, techniques et outils disponibles utilisés pour migrer plusieurs projets Node.js hébergés sur GitHub vers le monorepo Lerna. A la fin, nous proposons une script bash nous avons utilisé pour migrer le projet Node CSV. Ce script peut être appliqué à un projet différent avec juste une petite modification.

Exigences pour la migration

La CSV de nœud le projet combine 4 packages NPM pour travailler avec des fichiers CSV dans Node.js enveloppés par le parapluie csv forfait. Chaque package NPM a son riche historique de validation, et nous voulions enregistrer le maximum d’informations des anciens référentiels. Voici nos exigences pour la migration :

  • conserver l’historique des commits avec un maximum d’informations (telles que les balises, ses messages et les commits de fusion)
  • améliorer les messages de validation pour suivre les Engagements conventionnels spécification
  • préserver les problèmes GitHub

Structure monorepo

Eh bien, nous avons 5 packages NPM à migrer vers le monorepo Lerna :

Nous voulons obtenir une structure de répertoires qui ressemble à ceci :

packages/
  csv/
  csv-generate/
  csv-parse/
  csv-stringify/
  stream-transform/
lerna.json
package.json

Choisir la stratégie de journalisation Git

Lors de la migration de référentiels vers un monorepo, vous fusionnez leurs journaux de validation. Il y a 3 stratégies suggérées dans l’image ci-dessous.




Stratégies de journalisation Git

  • Succursale unique
    Il fournit un journal simple contenant uniquement les commits sur les branches par défaut (master) de tous les packages. Différents journaux sont joints séquentiellement en ajoutant le dernier commit du package précédent en tant que commit parent au premier commit du package suivant. Cette stratégie rompt le tri du journal par date de validation.
  • Plusieurs branches avec un parent commun
    Cela améliore la perception visuelle du journal en divisant les branches de différents référentiels. Un nouveau commit parent est ajouté à tous les premiers commits des branches. Au final, toutes les branches sont fusionnées dans la branche par défaut.
  • Plusieurs branches avec des parents différents
    Cette stratégie ne réécrit pas les premiers commits des anciens référentiels. Cela nécessite une intervention minimale dans l’historique des commits et semble logiquement plus correct car initialement, les référentiels n’avaient pas de parent commun.

Fusionner les journaux de validation

Lerne a un mécanisme intégré pour rassembler les packages NPM autonomes existants dans un monorepo préservant l’historique des commits. La lerna import La commande importe un package d’un référentiel externe dans packages/. La séquence de commandes est assez simple : vous devez initialiser les référentiels Git et Lerna, effectuer le premier commit, puis commencer à importer des packages à partir de référentiels Git clonés localement. Vous pouvez trouver des instructions d’utilisation de base dans la documentation ici.

Utilisant lerna import, vous ne pouvez suivre que la 1ère ou la 2ème stratégie de log Git décrite ci-dessus. Pour le 2e, vous devez créer une branche distincte par référentiel d’importation comme ceci :


git checkout -b package-1
lerna import /path/to/package-1

git checkout master

git checkout -b package-2
lerna import /path/to/package-2

lerna import fournit un outil facile à utiliser pour migrer les référentiels vers le monorepo Lerna. Cependant, il aplatit l’historique des commits en réduisant les commits de fusion et il ne migre pas les balises et leurs messages. Malheureusement, ces limitations ne répondaient pas à notre exigence de sauvegarder un maximum d’informations à partir des référentiels existants et nous avons dû utiliser un outil différent.

Le natif git merge permet de fusionner des historiques non liés à l’aide de la --allow-unrelated-histories option. Il conserve l’historique complet des commits d’une branche ciblée avec ses balises. Dans ce cas, vous réaliserez la 3ème stratégie de log Git.

Fusion d’un historique de validation d’un référentiel externe dans un référentiel actuel à l’aide de --allow-unrelated-histories aussi simple que d’exécuter 2 commandes :


git remote add -f <external-repo-name> <external-repo-path>

git merge --allow-unrelated-histories <external-repo-name>/<branch-name>

Réécriture des messages de validation

Pour mettre plus d’ordre et de transparence dans le journal de validation combiné, nous préfixons tous les messages de validation avec leurs noms de package. De plus, nous les rendons compatibles avec les Engagements conventionnels spécification que nous suivons dans nos derniers projets. Cette spécification standardise les messages de validation en les rendant plus lisibles et faciles à automatiser.

Pour implémenter cela, nous devons réécrire tous les messages de validation en les préfixant avec la chaîne comme chore(): .

Nous avons choisi le chore type juste pour le rendre compatible avec la spécification, et nous ne voulions pas créer d’expressions régulières complexes pour le supporter pleinement.

Il existe 2 outils pour réécrire les messages de commit :

Suivant la recommandation de Git, nous choisissons le git filter-repo. Après avoir installé l’outil à l’aide de ces consignesla commande pour réécrire les messages de validation d’un référentiel actuel est :

git filter-repo --message-callback 'return b"chore(<package-name>): " + message'

Pour voir plus d’exemples d’utilisation de la réécriture de l’historique du référentiel avec git filter-repovous pouvez suivre cette documentation.

Transférer des problèmes GitHub

Après avoir migré les référentiels et publié un nouveau monorepo sur GitHub, nous souhaitons transférer les Problèmes liés à GitHub des anciens dépôts. Les problèmes peuvent être transférés d’un référentiel à un autre à l’aide de l’interface GitHub. Vous pouvez suivre ceci guide pour apprendre les consignes.

Malheureusement, au moment d’écrire ces lignes, il n’y a aucune possibilité d’effectuer un transfert de problèmes en masse. Les problèmes doivent être transférés un par un. Mais cela peut vous donner une excuse pour “oublier” de transférer les problèmes ennuyeux en attente créés par la communauté du projet 😉

Qu’en est-il de Demandes d’extraction GitHub? Il y aura une perte et nous devons vivre avec. Une bonne chose est que les liens entre les problèmes écrits dans les commentaires et les demandes de tirage liées seront sauvegardés grâce à la redirection.

Scénario de migration

Le script bash de migration exploite les approches choisies et les outils décrits ci-dessus. Il génère la ./node-csv répertoire contenant le CSV de nœud dossiers de projet réorganisés en un monorepo Lerna.

#!/bin/sh
set -e

REPOS=(
  https://github.com/adaltas/node-csv
  https://github.com/adaltas/node-csv-generate
  https://github.com/adaltas/node-csv-parse
  https://github.com/adaltas/node-csv-stringify
  https://github.com/adaltas/node-stream-transform
)
OUTPUT_DIR=node-csv
PACKAGES_DIR=packages

rm -rf $OUTPUT_DIR && mkdir $OUTPUT_DIR && cd $OUTPUT_DIR
git init .
git remote add origin $REPOS[0]

for repo in $REPOS[@]; do
  
  splited=($repo//// )
  package=$splited[$#splited[@]-1]/node-/
  
  rm -rf $TMPDIR/$package && mkdir $TMPDIR/$package && git clone $repo $TMPDIR/$package
  git filter-repo \
    --source $TMPDIR/$package \
    --target $TMPDIR/$package \
    --message-callback "return b'chore($package): ' + message"
  
  git remote add -f $package $TMPDIR/$package
  git merge --allow-unrelated-histories $package/master -m "chore($package): merge branch 'master' of $repo"
  
  mkdir -p $PACKAGES_DIR/$package
  files=$(find . -maxdepth 1 | egrep -v ^./.git$ | egrep -v ^.$ | egrep -v ^./$PACKAGES_DIR$)
  for file in $files// /[@]; do
    mv $file $PACKAGES_DIR/$package
  done
  git add .
  git commit -m "chore($package): move all package files to $PACKAGES_DIR/$package"
  
  git branch init/$package $package/master
done

rm $PACKAGES_DIR/**/CONTRIBUTING.md
rm $PACKAGES_DIR/**/CODE_OF_CONDUCT.md
rm -rf $PACKAGES_DIR/**/.github
git add .
git commit -m "chore: remove outdated packages files"

Pour exécuter ce script, il suffit de créer un fichier exécutable, par exemple avec le nom migrate.shcollez-y le contenu du script et exécutez-le avec la commande :

chmod u+x ./migrate.sh
./migrate.sh

Noter! N’oubliez pas d’installer git-filter-repo avant d’exécuter le script.

Notes pour chaque étape du script :

  • 1. Configurer
    Les variables de configuration définissent la liste des référentiels à migrer, le répertoire de destination du nouveau monorepo Lerna et le dossier des packages qu’il contient. Vous pouvez modifier ces variables pour réutiliser ce script pour votre projet.
  • 2. Initialiser un nouveau dépôt
    Nous initialisons un nouveau référentiel. Le premier référentiel est également enregistré en tant que serveur distant origin dépôt.
  • 3. Migrer les référentiels
    • 3.1. Obtenir le nom du package
      Il extrait les noms de packages à partir de leurs liens de référentiels. Dans notre cas, les référentiels sont préfixés par node- que nous ne voulons pas conserver.
    • 3.2. Réécrire les messages de validation via un référentiel temporaire
      Pour ajouter un préfixe aux commits de chaque paquet en utilisant le modèle chore(): , nous devons le créer séparément pour chaque référentiel. Ceci est possible via un référentiel cloné localement dans un dossier temporaire.
    • 3.3. Fusionner le référentiel dans monorepo
      Dans un premier temps, nous ajoutons un référentiel cloné localement en tant que distant au monorepo. Ensuite, nous fusionnons son historique de validation en spécifiant un message de validation de fusion.
    • 3.4. Déplacer les fichiers du référentiel vers le dossier packages
      Après la fusion, les fichiers du référentiel fusionné apparaissent sous le répertoire racine monorepo. Suivant le structure nous voulons atteindre, nous déplaçons ces fichiers vers le packages répertoire et validez-le.
    • 3.5. Créer une nouvelle branche
      L’historique des commits est désormais associé à nos monorepos via un référentiel distant. L’historique sera perdu si le référentiel d’origine est effacé. Pour stocker l’historique dans le monorepo, nous créons une branche qui suit le référentiel distant et le préfixe avec init/.
  • 4. Nettoyer et supprimer les fichiers obsolètes Par souci d’illustration, nous nettoyons certains fichiers de package qui sont obsolètes grâce à la migration. Certains de ces fichiers doivent être déplacés vers le répertoire racine du référentiel.

Prochaines étapes

Le référentiel GIT est maintenant prêt et, en tant que tel, est qualifié de monorepo. Pour le rendre utilisable, des fichiers supplémentaires doivent être créés comme une racine package.json dossier, le lerna.json fichier de configuration si vous utilisez Lerna et un README dossier. Référez-vous au premier article de notre série pour appliquer les modifications nécessaires et initialisez votre monorepo avec Lerna.

Conclusion

La migration de projets open-source existants vous demande d’être ordonné et minutieux car une petite erreur peut ruiner le travail de vos utilisateurs. Toutes les étapes doivent être soigneusement analysées et bien testées. Dans cet article, nous avons couvert l’étendue des travaux pour migrer plusieurs projets Node.js vers le monorepo Lerna. Nous avons envisagé différentes approches, techniques et outils disponibles pour automatiser la migration sur l’exemple de notre CSV de nœud projet open source.

Leave a Reply