Branches fournisseur

Comme c'est particulièrement le cas en développement logiciel, les données que vous gérez dans votre système de gestion de versions ont souvent un lien étroit avec les données de quelqu'un d'autre, ou en sont peut-être dépendantes. Généralement, les besoins de votre projet vous obligeront à rester aussi à jour que possible avec les données fournies par cette entité externe, sans sacrifier la stabilité de votre propre projet. Ce scénario arrive très souvent, partout où les informations générées par un groupe de personnes ont un effet direct sur celles qui sont générées par un autre groupe de personnes.

Par exemple, il arrive que des développeurs de logiciel travaillent sur une application qui utilise une bibliothèque tierce. Subversion a justement une relation de ce type avec la bibliothèque Apache Portable Runtime (APR) (voir la section intitulée « APR, la bibliothèque Apache de portabilité des exécutables »). Le code source de Subversion dépend de la bibliothèque APR pour tous ses besoins de portabilité. Durant les étapes initiales de développement de Subversion, le projet suivait les changements de l'interface de programmation d'APR de près, restant toujours « à la pointe » des évolutions du code de la bibliothèque. Maintenant que APR et Subversion ont tous deux gagné en maturité, Subversion n'essaie de se synchroniser avec l'interface de programmation de l'APR qu'à des étapes de publication stables et bien testées.

Donc, si votre projet dépend des informations de quelqu'un d'autre, vous pourriez tenter de synchroniser ces informations avec les vôtres de plusieurs manières. La plus pénible serait de donner des instructions orales ou écrites à tous les contributeurs de votre projet, leur demandant de s'assurer qu'ils disposent des bonnes versions de ces informations tierces dont votre projet a besoin. Si les informations tierces sont gérées dans un dépôt Subversion, vous pourriez aussi utiliser les définitions externes de Subversion pour en fait « agrafer » des versions spécifiques de ces informations à un endroit quelconque dans le dossier de votre copie de travail (voir la section intitulée « Définition de références externes »).

Mais parfois vous voudrez gérer des modifications personnalisées de ce code tierce à l'intérieur de votre propre système de gestion de versions. En reprenant l'exemple du développement logiciel, les programmeurs pourraient vouloir apporter des modifications à cette bibliothèque tierce pour leurs propres besoins. Ces modifications incluraient peut-être de nouvelles fonctionnalités ou des corrections de bogues, gérées en interne seulement jusqu'à ce qu'elles soient incluses dans une version officielle de la bibliothèque tierce. Ou alors ces changements ne seront peut-être jamais remontés vers ceux qui gèrent cette bibliothèque, existant seulement en tant qu'optimisations « maison » permettant de mieux adapter la bibliothèque aux besoin des développeurs du logiciel.

À présent vous êtes face à une situation intéressante. Votre projet pourrait héberger ses modifications maison des données tierces de manière désordonnée, comme par exemple en utilisant des fichiers de patch ou des versions alternatives complètes des fichiers et dossiers. Mais ces méthodes deviennent rapidement de vrais casse-tête à gérer, nécessitant des mécanismes pour reporter vos modifications maison au code tierce et nécessitant le report de ces modifications à chaque version successive du code tierce dont vous dépendez.

La solution de ce problème est d'utiliser des branches fournisseur. Une branche fournisseur est une arborescence au sein de votre propre système de gestion de versions qui contient des informations fournies par une entité tierce, ou fournisseur. Chaque version des données du fournisseur que vous décidez d'incorporer dans votre projet est appelée une livraison fournisseur.

Les branches fournisseur présentent deux avantages. Premièrement, en incluant la livraison fournisseur actuellement supportée dans votre propre système de gestion de versions, vous avez la garantie que les membres de votre projet n'auront jamais besoin de se demander s'ils ont la bonne version des données du fournisseur. Ils reçoivent simplement la bonne version pendant les mises à jour usuelles de leur copie de travail. Deuxièmement, parce que ces données font partie de votre propre dépôt Subversion, vous pouvez y conserver vos modifications maison : vous n'avez plus besoin d'une méthode automatisée (ou pire, manuelle) pour reporter vos propres changements.

Procédure générale de gestion des branches fournisseur

La gestion des branches fournisseur fonctionne généralement de la façon suivante : vous créez d'abord un répertoire à la racine (par exemple /fournisseur) qui contiendra les branches fournisseur. Ensuite vous importez le code tierce dans un sous-dossier de ce dossier racine. Vous copiez ensuite ce sous-répertoire vers l'emplacement approprié de votre branche de développement principale (par exemple /trunk). Vous faites bien attention à toujours effectuer vos modifications locales dans la branche de développement principale. À chaque nouvelle version du code tierce, vous le déposez dans la branche fournisseur et en fusionnez les modifications vers /trunk, en résolvant les conflits qui apparaissent entre vos modifications locales et les modifications tierces.

Un exemple va rendre cet algorithme plus clair. Nous allons utiliser un scénario dans lequel votre équipe de développement crée un programme de calcul qui dépend d'une bibliothèque tierce d'arithmétique des nombres complexes, libcomplex. Nous commencerons par la création initiale de la branche fournisseur et l'import de la première livraison fournisseur. Nous appellerons notre dossier contenant la branche fournisseur libcomplex et nos livraisons fournisseur iront dans un sous-dossier de notre branche fournisseur appelé actuel. Et puisque svn import crée tous les dossiers parents intermédiaires dont il a besoin, nous pouvons en fait accomplir ces deux étapes en une seule commande :

$ svn import /chemin/vers/libcomplex-1.0 \
             http://svn.exemple.com/depot/fournisseur/libcomplex/actuel \
             -m 'import initial de la livraison fournisseur 1.0'
…

Nous avons désormais la version actuelle du code source de libcomplex dans /fournisseur/libcomplex/actuel. À présent, nous étiquetons cette version (voir la section intitulée « Étiquettes ») et ensuite nous la copions dans la branche de développement principale. Cette opération de copie crée un nouveau dossier appelé libcomplex au sein du répertoire de notre projet existant calc. C'est dans cette copie des données du fournisseur que nous ferons nos ajustements maison :

$ svn copy http://svn.exemple.com/depot/fournisseur/libcomplex/actuel  \
           http://svn.exemple.com/depot/fournisseur/libcomplex/1.0      \
           -m 'étiquetage de libcomplex-1.0'
…
$ svn copy http://svn.exemple.com/depot/fournisseur/libcomplex/1.0  \
           http://svn.exemple.com/depot/calc/libcomplex        \
           -m 'amène libcomplex-1.0 dans la branche principale'
…

Nous extrayons ensuite la branche principale de notre projet, qui inclut désormais une copie de la première livraison fournisseur, et nous nous mettons au travail pour personnaliser le code de libcomplex. Avant même d'en avoir pris conscience, notre version modifiée de libcomplex est complètement intégrée dans notre programme de calcul [26].

Quelques semaines plus tard, les développeurs de libcomplex publient une nouvelle version de leur bibliothèque, la version 1.1, qui contient des fonctionnalités dont nous avons besoin. Nous aimerions pouvoir utiliser cette nouvelle version, sans toutefois perdre les évolutions que nous avons apportées à la version existante. En gros, ce que nous voudrions faire c'est remplacer notre version actuelle de libcomplex, la 1.0, par une copie de libcomplex 1.1 et ensuite ré-appliquer les modifications que nous avions effectuées précédemment sur cette bibliothèque à la nouvelle version. Mais, en fait, nous allons aborder le problème sous un autre angle, en appliquant les changements apportés à libcomplex entre les versions 1.0 et 1.1 à notre copie modifiée de celle-ci.

Pour effectuer cette mise à niveau, nous allons extraire une copie de notre branche fournisseur et remplacer le code du répertoire actuel par le nouveau code source de libcomplex 1.1. Nous copions en fait littéralement de nouveaux fichiers par-dessus des fichiers existants, par exemple en extrayant le contenu de l'archive de libcomplex 1.1 à l'endroit où se trouvent nos fichiers et dossiers. L'objectif ici est d'aboutir à ce que notre répertoire actuel ne contienne que le code de libcomplex 1.1 et de s'assurer que la totalité de ce code est suivi en versions. Ah, et puis nous voulons faire ceci avec le moins possible de perturbations liées à l'historique de la gestion de versions.

Après avoir remplacé le code de la 1.0 par le code de la 1.1, svn status liste les fichiers ayant des modifications locales et peut-être aussi des fichiers non suivis en versions. Si nous avons fait ce que nous étions censés faire, les fichiers non suivis en versions sont uniquement de nouveaux fichiers introduits par la version 1.1 de libcomplex ; nous lançons donc svn add sur ces fichiers pour les inclure dans la gestion de versions. Si le code de la 1.1 ne contient plus certains fichiers qui étaient dans l'arborescence de la 1.0, il sera peut-être difficile de les repérer ; il vous faudrait comparer les deux arborescences avec un outil extérieur et ensuite faire un svn delete sur tous les fichiers présents en 1.0 mais pas en 1.1 (bien qu'il soit peut-être parfaitement acceptable de laisser traîner ces mêmes fichiers, inutilisés, dans l'ombre). Finalement, une fois que notre copie de travail de actuel ne contient plus que le code de libcomplex 1.1, nous pouvons propager les modifications que nous avons faites pour lui donner cet aspect.

Notre branche actuel contient désormais la nouvelle livraison fournisseur. Nous étiquetons donc la nouvelle version en 1.1 (de la même façon que nous avions précédemment étiqueté la livraison fournisseur de la version 1.0) et ensuite nous fusionnons les différences entre les étiquettes de la version précédente et de la nouvelle version vers notre branche de développement principale :

$ cd copies-de-travail/calc
$ svn merge http://svn.exemple.com/depot/fournisseur/libcomplex/1.0      \
            http://svn.exemple.com/depot/fournisseur/libcomplex/actuel  \
            libcomplex
… # résoudre tous les conflits entre leurs modifications et nos modifications
$ svn commit -m 'fusion de libcomplex-1.1 vers la branche principale'
…

Dans le cas le plus trivial, la nouvelle version de notre outil tierce ressemblerait à la version précédente, du point de vue des fichiers et dossiers. Aucun des fichiers sources de libcomplex n'aurait été effacé, renommé, ou déplacé ; la nouvelle version ne contiendrait que des modifications textuelles par rapport à la précédente. Dans l'idéal, nos modifications s'appliqueraient proprement à la nouvelle version de la bibliothèque, sans la moindre complication ou conflit.

Mais les choses ne sont pas toujours aussi simples et, en fait, il arrive assez fréquemment que des fichiers sources changent d'emplacement d'une version à l'autre d'un logiciel. Ceci complique la tâche de s'assurer que nos modifications sont toujours valides pour la nouvelle version du code, et les choses peuvent rapidement dégénérer, jusqu'au point où nous pouvons être forcés de reporter manuellement nos évolutions maison dans la nouvelle version. Une fois que Subversion connaît l'historique d'un fichier source donné, incluant tous ses emplacements précédents, la procédure pour incorporer la nouvelle version de la bibliothèque est assez simple. Mais c'est à nous qu'incombe la responsabilité d'indiquer à Subversion de quelle manière l'agencement des fichiers sources a changé d'une livraison fournisseur à une autre.

svn_load_dirs.pl

Les livraisons fournisseur comportant plus de quelques suppressions, ajouts et déplacements compliquent le processus de mise à niveau à chaque version successive des données tierces. Subversion fournit donc le script svn_load_dirs.pl pour faciliter ce processus. Ce script automatise les étapes importantes que nous avons mentionnées dans la procédure générale de gestion des branches fournisseurs afin de minimiser les erreurs. Vous êtes toujours responsable de l'utilisation des commandes svn merge pour fusionner les nouvelles versions des données tierces vers votre branche de développement principale, mais svn_load_dirs.pl peut vous aider à parvenir à cette étape plus rapidement et plus facilement.

En bref, svn_load_dirs.pl est une version améliorée de svn import qui possède plusieurs caractéristiques importantes :

  • On peut le lancer n'importe quand, dans le but d'amener un répertoire existant du dépôt à refléter exactement un répertoire extérieur, en effectuant toutes les opérations d'ajouts et de suppressions et, en option, de déplacements.

  • Il prend soin de séries compliquées d'opérations entre lesquelles Subversion a besoin d'une propagation intermédiaire, telles qu'avant de renommer un fichier ou un dossier pour la deuxième fois.

  • En option, il peut étiqueter les nouveaux dossiers importés.

  • En option, il peut ajouter des propriétés arbitraires aux fichiers et dossiers qui correspondent à une expression régulière.

svn_load_dirs.pl prend trois paramètres obligatoires. Le premier paramètre est l'URL du répertoire de base de Subversion à modifier. Ce paramètre est suivi par l'URL, relative au premier paramètre, dans laquelle la livraison fournisseur sera importée. Enfin, le troisième paramètre est le dossier local à importer. En utilisant notre exemple précédent, une exécution type de svn_load_dirs.pl donne :

$ svn_load_dirs.pl http://svn.exemple.com/depot/fournisseur/libcomplex \
                   actuel                                              \
                   /chemin/vers/libcomplex-1.1
…

Vous pouvez indiquer que vous aimeriez que svn_load_dirs.pl étiquette la nouvelle livraison fournisseur en passant l'option -t et en spécifiant un nom d'étiquette. Cette étiquette est aussi une URL relative au premier paramètre du programme.

$ svn_load_dirs.pl -t libcomplex-1.1                              \
                   http://svn.exemple.com/depot/fournisseur/libcomplex \
                   actuel                                        \
                   /chemin/vers/libcomplex-1.1
…

Lorsque vous lancez svn_load_dirs.pl, il examine le contenu de votre livraison fournisseur existante, actuel, et le compare à la nouvelle livraison fournisseur. Dans le cas le plus trivial, aucun fichier n'est présent dans une version sans l'être dans l'autre et le script effectue le nouvel import sans incident. Cependant, s'il y a des divergences dans l'agencement des fichiers entre les versions, svn_load_dirs.pl vous demande comment résoudre ces différences. Par exemple, vous avez l'opportunité d'indiquer au script que vous savez que le fichier math.c de la version 1.0 de libcomplex a été renommé en arithmetique.c dans libcomplex 1.1. Toutes les divergences qui ne sont pas liées à des renommages sont traitées comme des ajouts et des suppressions classiques.

Le script peut également prendre en compte un fichier de configuration séparé, permettant de spécifier des propriétés sur des fichiers et dossiers, correspondants à une expression régulière, qui vont être ajoutés au dépôt. Ce fichier de configuration est indiqué à svn_load_dirs.pl en utilisant l'option -p en ligne de commande. Chaque ligne du fichier de configuration est un ensemble de deux ou quatre valeurs délimitées par des espaces : une expression régulière du style Perl à laquelle comparer le chemin ajouté, un mot clé de contrôle (soit break soit cont) et ensuite, en option, un nom de propriété et une valeur.

\.png$              break   svn:mime-type   image/png
\.jpe?g$            break   svn:mime-type   image/jpeg
\.m3u$              cont    svn:mime-type   audio/x-mpegurl
\.m3u$              break   svn:eol-style   LF
.*                  break   svn:eol-style   native

Pour chaque chemin ajouté, les modifications de propriétés configurées dont l'expression régulière correspond au chemin sont appliquées dans l'ordre, sauf si le terme de contrôle est break (ce qui signifie qu'aucune autre modification de propriété ne doit être appliquée à ce chemin). Si le terme de contrôle est cont (abréviation de continuer), la comparaison continue avec la ligne suivante du fichier de configuration.

Toute espace faisant partie de l'expression régulière, du nom de la propriété ou de la valeur de la propriété doit être entourée d'apostrophes ou de guillemets. Vous pouvez banaliser les guillemets et apostrophes qui ne sont pas utilisés pour entourer une espace en les faisant précéder d'une barre oblique inversée (ou « antislash » : \). L'antislash ne banalise que les guillemets et apostrophes pendant le traitement du fichier de configuration, ce n'est donc pas la peine de protéger d'autres caractères au-delà de ce qui est nécessaire pour l'expression régulière.



[26] Et ne contient pas le moindre bogue, cela va de soi !