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.
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.
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.