Re-écrire un historique git

Contexte

Ça y est. Après des mois de travail appliqué, le projet est achevé: toutes les fonctionalités ont été implementées, la couverture de tests est satisfaisante, les scripts de déploiement vers les différents environnements fonctionnent.

Bref le client est satisfait et l’heure est venue de transmettre le projet. Cet fois-ci le client souhaite être autonome et continuer seul à exploiter et faire évoluer le logiciel. C’est son choix, c’est son droit, pas de problème.

Une dernière vérification sur le dépôt dévoile une petit souci: le script de déploiement, lors de la phase de construction du paquet, utilise un fichier auth.json utilisé par composer afin d’accéder à des composants php sous licence. Et ce fichier est enregistré dans le dépôt. Donc si le projet est livré en l’état au client, celui-ci continuera à avoir accès aux dépôts privés.

Porte ouverte sur un espace délabré. Cliquer pour voir l’image en plus grand

Le problème

Après consultation du département des ventes, nous apprenons que nous devons distinguer plusieurs cas:

  • il y a des composants pour lesquels le client a acquis les droits sur les mises à jour futures;
  • pour d’autres logiciels, seule la version disponible au moment de la livraison devra rester accessible;

Pour les premiers composants, les identifiants peuvent rester dans le fichier auth.json sans problème. Lorsque les futurs développeurs du projet lanceront la commande composer update les dernières versions seront alors téléchargées. En revanche pour les autres composants (un seul en fait) il va falloir faire autrement.

La méthode va être la suivante:

  1. retirer les clefs d’accès au dépôt du composant en question du fichier auth.json
  2. insérer «/en dur/» dans le dépôt git les sources du composant, et mettre à jour les fichiers composer.json et composer.lock en conséquent.

Nous allons nous simplifier la tâche: la deuxième partie (copie du composant dans le dépôt) sera mise en œuvre dans un nouveau commit. C’est un peu de la triche parce que cela signifie que le projet livré ne tournera pas ou ne pourra pas être construit avec les commits précédents le dernier. Néanmoins:

  1. c’est contractuellement Ok;
  2. cela fera à peine un peu plus de travail pour les nouveaux développeurs lorsqu’ils voudront utiliser un git bisect.

Quant à la première partie du nettoyage de notre dépôt git, nous allons devoir utiliser un outil diabolique: git filter-branch.

La solution

Cet outil est tellement diabolique que la documentation officielle vous suggère d’utiliser une alternative plus sûre comme filter-repo. Après avoir attentivement lu la documentation des deux outils, j’ai préféré utiliser le plus dangereux des deux, filter-branch. Pas par amour du risque, mais par besoin de simplicité.

Les deux outils permettent de re-écrire l’historique d’un dépôt git. Modifier l’historique d’un dépôt git n’est pas anodin. Une fois le serveur de référence mis à jour avec le nouvel historique, tous les développeurs devront re-cloner leur dépôt avant de pouvoir pousser à nouveau leurs modifications. Bien sûre, dans le cas décrit ici, cela n’est pas problématique, puisqu’il n’y aura que des nouveaux développeurs.

Alors que filter-repo permet essentiellement de modifier l’historique d’un dépôt git en supprimant, ajoutant ou modifiant des fichiers dans chaque commit, filter-branch permet d’exécuter une commande avant chaque commit. Dans notre cas, nous allons écraser le fichier auth.json avec la version contenant les clefs permises.

git filter-branch --tree-filter 'cp /tmp/auth.json src/auth.json' HEAD

Et voilà!

À noter:

  1. tous vos commits ont maintenant des identifiants différents;
  2. le fichier src/auth.json a été automatiquement ajouté au premier commit, même si cela n’était pas le cas avant.

Mon projet comportait quelques centaines de commits. La commande n’a pas mis plus de quelques minutes pour s’exécuter sur un ordinateur type desktop.

Encore une fois, la commande git filter-branch est à utiliser avec parcimonie, et uniquement en dernier recours.