Journal

Deux branches, 1 714 fichiers d'écart : auditer avant de fusionner

En février 2026, le code de Jewely existait en deux versions. Les deux partaient du même commit, daté du 29 août 2025. L'une portait Crown-DP. L'autre faisait tourner Louis Julian...

Article4 fils étiquetés

En février 2026, le code de Jewely existait en deux versions. Les deux partaient du même commit, daté du 29 août 2025. L'une portait Crown-DP. L'autre faisait tourner Louis Julian en production.

Pour faire un CMS commun, il fallait décider laquelle des deux devenait la base. Cette note raconte comment j'ai pris cette décision, ce que j'ai écarté, et ce qu'elle a coûté.

L'écart, mesuré

Mesure Valeur
Fichiers différents entre les deux branches 1 714
Lignes ajoutées et supprimées +143 590 / −99 248
Commits côté Crown-DP depuis la séparation 643
Commits côté Louis Julian depuis la séparation 1 435

Les deux branches n'avaient pas fait le même travail. Côté Crown-DP, l'architecture avait changé : la gestion de plusieurs boutiques, des moyens de paiement interchangeables, un éditeur de pages par blocs, 198 tests, un déploiement Docker Swarm. Côté Louis Julian, la branche avait suivi la vie d'un site en production : les pages Rolex, l'ERP, la maintenance.

Un audit en lecture seule, avant tout merge

Je n'ai lancé aucun merge. J'ai d'abord comparé les deux branches fichier par fichier, couche par couche, et j'ai donné un verdict à chaque fichier.

Verdict Ce qu'il veut dire Fichiers
Prendre La version Crown-DP remplace l'autre environ 1 500
Garder les deux Les thèmes de chaque maison environ 180
Fusionner à la main Le fichier a changé des deux côtés environ 30
Garder l'ancienne Le fichier n'existe que côté Louis Julian environ 5
Ignorer Fichiers temporaires ou abandonnés environ 5

L'audit a changé la taille du problème. Il n'y avait pas 1 714 fichiers à fusionner. Il y en avait une trentaine à relire ligne par ligne : la fiche produit, les collections, les rôles du back-office, les services Rolex, les commandes d'import.

La décision : un tronc, et l'autre maison portée dessus

J'avais trois options.

  1. Fusionner Crown-DP dans la branche historique. Écartée. Les conflits auraient été massifs, et la branche historique n'avait aucun test pour dire si la fusion avait cassé quelque chose.
  2. Garder deux codes. Écartée. Chaque fonctionnalité aurait été construite deux fois, et l'écart aurait continué de grandir.
  3. Faire de Crown-DP le tronc, et y porter Louis Julian. Retenue. Louis Julian devient un thème et une configuration posés sur le code commun.

Le critère n'était pas l'ancienneté de la branche, ni le nombre de commits. C'était la présence des tests : la branche qui en avait pouvait dire si la suite se passait bien.

Une base de données par maison

La deuxième décision portait sur les données. La configuration avait prévu une colonne shop_id sur chaque table, pour ranger toutes les maisons dans une seule base. Je ne l'ai pas mise en œuvre. Chaque maison a son serveur, sa base de données et sa propre image Docker.

              code commun (Laravel, back-office Vue)
                            │
            ┌───────────────┴───────────────┐
  APP_SLUG_NAME=crown-dp        APP_SLUG_NAME=louis-julian
            │                               │
  configuration + templates       configuration + templates
  image Docker                    image Docker
  base de données                 base de données

Deux raisons. Une requête qui oublie un filtre ne peut pas montrer les commandes d'une maison à une autre. Une migration ratée ou un serveur en panne ne touche qu'une maison.

Le coût est connu : il y a autant de déploiements que de maisons. « Construit une fois » vaut pour le code, pas pour la mise en ligne. La colonne shop_id reste le plan B, écrit comme tel dans la feuille de route, pour le jour où le nombre de maisons rendra ce coût trop lourd.

Ce qui empêche les deux maisons de diverger à nouveau

Aucune condition sur le nom d'une maison dans le code commun. Ce qui distingue une maison est une option, déclarée dans sa configuration :

function feature_enabled(string $feature, bool $default = false): bool
{
    return (bool) shop_config("features.{$feature}", $default);
}

Une option déclarée pour une maison doit exister pour l'autre, même à false. La liste des options devient un contrat entre les maisons et le code commun.

Les noms écrits en dur, cherchés avant le portage. L'audit en a trouvé : l'identifiant de Crown-DP dans un helper de templates, et une adresse d'expédition d'e-mails dans trois fichiers. Ils ont été corrigés avant de porter la deuxième maison, pas après.

Les tests, une fois par maison.

strategy:
    fail-fast: false
    matrix:
        shop: [crown-dp, louis-julian]

fail-fast: false est voulu. Si une maison casse, je veux aussi le résultat de l'autre, pour savoir si le défaut est dans le code commun ou dans un thème.

Ce que ça a coûté

  • Le ménage d'abord. Le dépôt comptait 109 branches. J'en ai gardé 8 avant de commencer, pour que personne ne reparte d'une branche morte.
  • Des thèmes complets. Une maison ne peut plus emprunter le template d'une autre. Chaque maison doit donc avoir tous les siens. L'incident qui a imposé cette règle est raconté dans Un CMS pour plusieurs maisons, sans qu'elles se ressemblent.
  • Une manipulation Git en plus. Chaque branche de préproduction ne doit contenir que les fichiers de sa maison. J'ai choisi des fichiers .gitignore par dossier plutôt qu'un sparse-checkout, parce qu'il n'y a rien à configurer sur chaque poste. Le prix : après chaque merge depuis la branche principale, il faut retirer de l'index les fichiers des autres maisons. La note de décision le dit, et dit quand reprendre la question : à la quatrième maison.
  • Deux étapes, pas une. Le code de Louis Julian a été porté sur le tronc commun les 9 et 10 mars 2026, et le site a tourné dessus en préproduction. Sa production est ensuite passée sur une déclinaison du CMS : une instance à part, avec sa base de données, son serveur et ses propres mises à jour.

Ce que j'en retiens

  • Mesurer avant de choisir. Le chiffre qui faisait peur était 1 714. Le chiffre utile était 30.
  • Choisir le tronc sur ce qui protège la suite. Ici, les tests.
  • Écrire le plan B et ce qui le déclenche. shop_id pour une base unique, le sparse-checkout à la quatrième maison. La personne suivante sait ce qui a été écarté et quand y revenir.

Le résultat côté produit est dans l'étude de cas Crown-DP et le CMS Jewely.