This text is a work in progress—highly subject to change—and may not accurately describe any released version of the Apache™ Subversion® software. Bookmarking or otherwise referring others to this page is probably not such a smart idea. Please visit http://www.svnbook.com/ for stable versions of this book.

Branches fournisseurs

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 obligent à 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 voulez 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 peuvent vouloir apporter des modifications à cette bibliothèque tierce pour leurs propres besoins. Ces modifications incluent 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, par exemple en utilisant des correctifs de type 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 fournisseurs. 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.

Malheureusement, il n'existe pas de « meilleure » façon pour gérer les branches fournisseurs dans Subversion. La flexibilité offerte par le système permet plusieurs approches différentes, chacune ayant ses avantages et ses inconvénients, et aucune ne peut être considérée comme « la méthode qui tue » pour ce problème. Nous allons décrire quelques unes des approches sans rentrer dans les détails dans les paragraphes qui suivent, en prenant comme exemple un projet logiciel qui dépend d'une bibliothèque tierce.

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

Gérer des modifications personnalisées d'une bibliothèque tierce met en jeu trois sources de données : la version de la bibliothèque tierce sur laquelle vos modifications ont porté la dernière fois, la version personnalisée (c'est-à-dire la branche fournisseur actuelle) de cette bibliothèque qui est utilisée par votre projet et toute nouvelle version de la bibliothèque externe dont vous espérez sûrement effectuer la mise à niveau. Gérer la branche fournisseur (qui doit résider dans le dépôt de votre code source, par définition) consiste alors essentiellement à effectuer des fusions (au sens général du terme). Mais chaque équipe peut choisir sa méthode pour ce qui concerne les autres sources de données : les versions originales du code source de la bibliothèque tierce. Donc, nous avons plusieurs façons pour effectuer les fusions requises.

Stricto sensu, il existe deux façons de faire ces fusions. Afin de simplifier et de rester concret dans ce paragraphe, nous supposons qu'il n'y a qu'une seule branche fournisseur qui est mise à niveau lors de chaque mise à jour des bibliothèques tierces (qui décrivent les différences entre les versions courantes et les nouvelles versions des sources de la bibliothèque).

[Note] Note

Une autre approche est de créer une nouvelle branche fournisseur pour chaque version de la bibliothèque, en appliquant les différences entre la bibliothèque originale courante et la version personnalisée vers la nouvelle branche. Cette approche n'est pas mauvaise, nous ne nous sentons juste pas obligés de documenter ici toutes les façons de faire.

Les paragraphes suivants décrivent comment créer et gérer une branche fournisseur suivant quelques scénarios différents. Dans les exemples qui suivent, nous supposons que la bibliothèque tierce s'appelle libcomplex et que nous allons implémenter une branche fournisseur basée sur libcomplex 1.0.0 qui est stockée dans notre dépôt à l'emplacement ^/vendor/libcomplex-perso. Nous allons voir comment nous pouvons mettre à niveau vers libcomplex 1.0.1 tout en préservant nos personnalisations de cette bibliothèque.

Branches fournisseurs depuis des dépôts externes

Dans un premier temps, regardons comment gérer une branche fournisseur lorsque la bibliothèque originale est accessible par Subversion. Pour les besoins de cet exemple, nous allons considérer que la bibliothèque libcomplex dont nous avons parlé est développée dans un dépôt Subversion librement accessible et que les développeurs utilisent une procédure de publication de bon aloi qui comporte la création d'une étiquette pour chaque version stable publiée.

Depuis Subversion 1.5, la sous-commande svn merge est capable d'effectuer ce que l'on appelle des fusions avec un dépôt externe, où les sources de la fusion sont stockées dans un dépôt différent du dépôt dont la copie de travail, cible de la fusion, a été extraite. Et dans Subversion 1.8, le comportement de svn copy a été modifié de manière à ce que l'arborescence résultante d'une copie depuis un dépôt externe vers une copie de travail existante soit incorporée dans cette copie de travail et placée pour ajout lors de la prochaine propagation. C'est cette fonctionnalité de copie à partir d'un dépôt externe que nous allons utiliser pour initier notre branche fournisseur.

Créons donc notre branche fournisseur. Nous commençons par créer un dossier d'accueil pour toutes les branches fournisseurs dans notre dépôt puis nous extrayons une copie de travail à cet emplacement.

$ svn mkdir http://svn.exemple.com/projets/vendor \
            -m "Création d'un conteneur pour les branches fournisseurs."
Révision 1160 propagée.
$ svn checkout http://svn.exemple.com/projets/vendor \
               /chemin/vers/fournisseur
Révision 1160 extraite.
$

Maintenant nous allons profiter de la capacité de Subversion à copier un dépôt externe pour obtenir une copie exacte de libcomplex 1.0.0 (y compris les propriétés Subversion stockées dans ces fichiers et dossiers) à partir du dépôt du fournisseur.

$ cd /chemin/vers/fournisseur
$ svn copy http://svn.monfournisseur.fr/depot/libcomplex/tags/1.0.0 \
           libcomplex-perso
--- Copying from foreign repository URL 'http://svn.monfournisseur.fr/depot/libcomplex/tags/1.0.0' :
A    libcomplex-perso
A    libcomplex-perso/README
A    libcomplex-perso/LICENSE
…
A    libcomplex-perso/src/code.c
A    libcomplex-perso/tests
A    libcomplex-perso/tests/TODO
$ svn commit -m "Initialisation de la branche fournisseur libcomplex avec libcomplex 1.0.0."
Ajout         libcomplex-custom
Ajout         libcomplex-custom/README
Ajout         libcomplex-custom/LICENSE
…
Ajout         libcomplex-custom/src
Ajout         libcomplex-custom/src/code.h
Ajout         libcomplex-custom/src/code.c
Transmission des données .......................................
Révision 1161 propagée.
$
[Note] Note

Si vous êtes amené à utiliser une vieille version de Subversion, la meilleure façon d'approcher cette nouvelle fonctionnalité de svn copy est d'importer une copie de travail (avec svn import) de la version étiquetée du fournisseur, en spécifiant bien les options --no-auto-props et --no-ignore pour que l'arborescence complète et les propriétés suivies en versions soient correctement répliquées dans votre propre dépôt.

Maintenant que nous avons la branche fournisseur basée sur libcomplex 1.0.0, nous pouvons commencer à personnaliser libcomplex pour satisfaire nos besoins, en propageant les modifications directement vers la branche fournisseur que nous avons créée. Et bien sûr, nous pouvons commencer à utiliser libcomplex dans notre propre application.

Quelques temps plus tard, les développeurs de libcomplex publient une nouvelle version de leur bibliothèque, la version 1.0.1. Après avoir passé en revue les modifications, nous décidons de mettre à niveau notre branche fournisseur vers cette nouvelle version. Et c'est ici que l'opération de fusion à partir d'un dépôt externe de Subversion est utile. Nous avons dans notre branche fournisseur la libcomplex 1.0.0 originale plus nos personnalisations. Nous avons besoin maintenant d'y insérer l'ensemble des modifications que le fournisseur a effectué entre 1.0.0 et 1.0.1, idéalement sans fracasser nos personnalisations. C'est précisément ce que la forme de svn merge à 2-URL sait faire.

$ cd /chemin/vers/fournisseur
$ svn merge http://svn.autrefournisseur.com/depot/libcomplex/tags/1.0.0 \
            http://svn.autrefournisseur.com/depot/libcomplex/tags/1.0.1 \
            libcomplex-perso
-- Fusion des différences des URLs du dépôt externe dans '.':
U    libcomplex-perso/src/code.h
C    libcomplex-perso/src/code.c
U    libcomplex-perso/README
Résumé des conflits :
  Text conflicts: 1
Conflit découvert dans le fichier 'libcomplex-custom/src/code.c'.
Select: (p) postpone, (df) diff-full, (e) edit, (m) merge,
        (mc) mine-conflict, (tc) theirs-conflict, (s) show all options:

Comme vous pouvez le constater, svn merge a fusionné les modifications pour obtenir libcomplex 1.0.1 à partir de libcomplex 1.0.0 dans votre copie de travail. Dans cet exemple, il a même découvert et marqué un fichier comme étant en conflit. Il semble que le fournisseur a modifié une zone d'un des fichiers que nous avons personnalisé. Subversion détecte ce conflit et nous donne la possibilité de le résoudre de manière sécurisée, c'est-à-dire que nos modifications à ce qui est maintenant libcomplex 1.0.1 puissent continuer à faire sens. Pour plus d'informations sur la résolution des conflits de ce type, reportez-vous à la section intitulée « Résolution des conflits ».

Une fois que nous avons résolu les conflits et effectué nos tests ou passé en revue ce que est nécessaire, nous pouvons propager les modifications vers notre branche fournisseur.

 
$ svn status libcomplex-perso
M       libcomplex-perso/src/code.h
M       libcomplex-perso/src/code.c
M       libcomplex-perso/README
$ svn commit -m "Mise à niveau de la branche fournisseur vers libcomplex 1.0.1." \
             libcomplex-perso
Sending        libcomplex-perso/README
Sending        libcomplex-perso/src/code.h
Sending        libcomplex-perso/src/code.c
Transmission des données ...
Révision 1282 propagée.
$

Voilà, à grosses mailles, comment gérer des branches fournisseurs quand les sources originales sont accessible par Subversion. Il faut noter quand même quelques manques. D'abord, les fusions de dépôts externes ne sont pas automatiquement tracées par Subversion lui-même comme le sont les fusions internes au dépôt. Cela veut dire que c'est à l'utilisateur de savoir quels changements ont été appliqués sur la branche fournisseur et de construire lui-même la prochaine fusion pour mettre à niveau la branche. De plus, comme c'est le cas pour toutes les fusions faites par Subversion, les renommages à l'intérieur des sources de la fusion peuvent entrainer certaines complications et frustrations. Malheureusement, à l'heure actuelle, nous n'avons pas de recommandation particulière réellement valable pour vous soulager dans ce cas.

Branches fournisseurs à partir de sources mirroirs

Dans le paragraphe précédent (la section intitulée « Branches fournisseurs depuis des dépôts externes ») nous avons vu comment implémenter et maintenir une branche fournisseur quand celui-ci fournit un accès via Subversion, ce qui est le scénario idéal pour les branches fournisseurs. Subversion se distingue particulièrement lorsqu'il s'agit de fusionner des contenus gérés par Subversion. Malheureusement, ce n'est pas toujours le cas. Souvent, un projet dépend d'une bibliothèque qui n'est accessible que via des mécanismes non-Subversion, tels que des archives compressées de code source. Dans ces circonstances, nous recommandons fortement de faire tout ce que vous pouvez pour insérer ces données non-Subversion dans Subversion de la manière la plus propre possible. Examinons donc une approche de branche fournisseur dans laquelle les différentes versions de la bibliothèque tierce sont répliquées dans votre propre dépôt.

Configurer la branche fournisseur pour la première fois est très simple, vraiment. Dans notre exemple, nous considérons que libcomplex 1.0.0 est fournie dans une archive compressée classique. Pour créer notre branche fournisseur, nous allons dans un premier temps copier le contenu de l'archive dans notre dépôt comme une sorte d'arborescence étiquetée fournisseur en lecture seule (par convention uniquement).

$ tar xvfz libcomplex-1.0.0.tar.gz
libcomplex-1.0.0/
libcomplex-1.0.0/README
libcomplex-1.0.0/LICENSE
…
libcomplex-1.0.0/src/code.c
libcomplex-1.0.0/tests
libcomplex-1.0.0/tests/TODO
$ svn import libcomplex-1.0.0 \
             http://svn.exemple.com/projets/vendor/libcomplex-1.0.0 \
             --no-ignore --no-auto-props \
             -m "import des sources de libcomplex 1.0.0."
Ajout         libcomplex-custom
Ajout         libcomplex-custom/README
Ajout         libcomplex-custom/LICENSE
…
Ajout         libcomplex-custom/src
Ajout         libcomplex-custom/src/code.h
Ajout         libcomplex-custom/src/code.c
Transmission des données .......................................
Révision 1160 propagée.
$

Notez que dans l'exemple, nous avons utilisé l'option --no-ignore pour l'import de manière à ce que Subversion récupère bien tous les fichiers de la livraison. Nous avons également utilisé l'option --no-auto-props afin que notre client n'affecte pas de lui-même des propriétés qui ne seraient pas présentes dans l'archive officielle[44].

Maintenant que la première archive du fournisseur est présente dans notre dépôt, nous pouvons nous en servir comme point de départ pour créer notre branche fournisseur en utilisant svn copy comme pour toute autre branche.

 
$ svn copy http://svn.exemple.com/projets/vendor/libcomplex-1.0.0 \
           http://svn.exemple.com/projets/vendor/libcomplex-perso \
           -m "Initialisation de la branche fournisseur libcomplex à partir de libcomplex 1.0.0."
Révision 1161 propagée.
$

Parfait. Ici, nous avons une branche fournisseur basée sur libcomplex 1.0.0. Nous sommes maintenant aptes à personnaliser libcomplex pour nos besoins propres, en propageant directement les modifications vers la branche fournisseur que nous venons de créer. Puis, nous pouvons utilisé notre libcomplex personnalisée dans nos applications.

Quelques temps plus tard, libcomplex 1.0.1 est publiée. Après avoir passé en revue les modifications, nous décidons de mettre à niveau notre branche fournisseur vers cette nouvelle version. Afin d'effectuer cette mise à niveau, nous devons essentiellement appliquer le même ensemble de modifications à notre branche fournisseur que le fournisseur a fait entre la version 1.0.0 et la version 1.0.1 de sa bibliothèque, ceci sans écraser nos propres personnalisations. La façon la plus sûre de le faire est d'insérer libcomplex 1.0.1 dans notre dépôt, comme un delta vis-à-vis du code de libcomplex 1.0.0 de notre dépôt. Ensuite, nous utilisons la forme 2-URL de la sous-commande svn merge pour répliquer ces mêmes modifications vers notre branche fournisseur.

Il existe plusieurs manières d'insérer correctement libcomplex 1.0.1 dans notre dépôt[45]. L'approche que nous décrivons ici est relativement rudimentaire, mais elle convient pour illustrer notre exemple.

Rappelez-vous que nous voulons que notre replique de la livraison libcomplex-1.0.1 partage des ancêtres avec notre livraison 1.0.0, afin de produire plus tard les meilleurs résultats quand nous devrons fusionner les modifications entre les archives vers la branche fournisseur. Nous allons commencer par créer une branche libcomplex-1.0.1 comme copie de notre branche « étiquetée fournisseur » libcomplex-1.0.0. Cette copie sera destinée à devenir une réplique de libcomplex 1.0.1.

$ svn copy http://svn.exemple.com/projets/vendor/libcomplex-1.0.0 \
           http://svn.exemple.com/projets/vendor/libcomplex-1.0.1 \
           -m "Construction de la zone pour accueillir libcomplex 1.0.1."
Révision 1282 propagée.
$

Nous avons besoin maintenant d'avoir une copie de travail de notre branche libcomplex-1.0.1 et de la faire ressembler à libcomplex 1.0.1. Pour ce faire, nous allons profiter du fait que svn checkout peut agir en superposition dans un répertoire existant et, si l'option --force est fournie, alors les différences entre l'arborescence extraite et l'arborescence cible sur laquelle est appliquée l'extraction sont marquées comme modifications locales de la nouvelle copie de travail.

$ tar xvfz libcomplex-1.0.1.tar.gz
libcomplex-1.0.1/
libcomplex-1.0.1/README
libcomplex-1.0.1/LICENSE
…
libcomplex-1.0.1/src/code.c
libcomplex-1.0.1/tests
libcomplex-1.0.1/tests/TODO
$ svn checkout http://svn.exemple.com/projets/vendor/libcomplex-1.0.1 \
               libcomplex-1.0.1 \
               --force
E    libcomplex-1.0.1/README
E    libcomplex-1.0.1/LICENSE
E    libcomplex-1.0.1/INSTALL
…
E    libcomplex-1.0.1/src/code.c
E    libcomplex-1.0.1/tests
E    libcomplex-1.0.1/tests/TODO
Révision 1282 extraite.
$ svn status libcomplex-1.0.1
M       libcomplex-1.0.1/src/code.h
M       libcomplex-1.0.1/src/code.c
M       libcomplex-1.0.1/README
$

Comme vous pouvez le constater, après avoir extrait ce qu'était réellement libcomplex 1.0.0 « par dessus » l'archive décompressée de libcomplex 1.0.1, nous obtenons une copie de travail avec des modifications locales (les modifications qui correspondent aux différences entre la précédente version du fournisseur que nous avions intégrée et la nouvelle).

Certes, c'est un exemple particulièrement simple. Les modifications pour effectuer la mise à niveau ne font intervenir que quelques modifications dans des fichiers existants. Dans la réalité, les nouvelles versions des bibliothèques tierces ajoutent ou déplacent sûrement des fichiers ou des dossiers, renomment des fichiers ou des dossiers, etc. Dans ces cas, il sera plus compliqué de calquer la nouvelle version étiquetée avec la livraison qu'elle est censée représenter. Nous laissons en exercice au lecteur la résolution des détails de cette transformation[46].

Quelle que soit la manière dont nous y sommes arrivés, nous disposons maintenant d'une copie de travail de la nouvelle version étiquetée qui reflète exactement la livraison originale du fournisseur. Nous pouvons maintenant propager ces modifications à notre dépôt.

$ svn commit -m "Mise à niveau de la branche fournisseur vers libcomplex 1.0.1." \
             libcomplex-1.0.1
Envoi      libcomplex-1.0.1/README
Envoi      libcomplex-1.0.1/src/code.h
Envoi      libcomplex-1.0.1/src/code.c
Transmission des données ...
Révision 1283 propagée.
$

Nous sommes finalement prêt à mettre à niveau notre branche fournisseur, notre but étant d'obtenir les changements faits par le fournisseur entre les versions 1.0.0 et 1.0.1 de sa bibliothèque dans notre branche fournisseur. C'est là que la forme à 2-URL de svn merge, appliquée à une copie de travail de notre branche fournisseur, entre en scène.

$ svn checkout http://svn.exemple.com/projets/vendor/libcomplex-perso \
               libcomplex-perso
E    libcomplex-perso/README
E    libcomplex-perso/LICENSE
E    libcomplex-perso/INSTALL
…
E    libcomplex-perso/src/code.c
E    libcomplex-perso/tests
E    libcomplex-perso/tests/TODO
Révision 1283 extraite.
$ cd libcomplex-perso
$ svn merge ^/vendor/libcomplex-1.0.0 \
            ^/vendor/libcomplex-1.0.1
--- Fusion des différences entre les URL du dépôt dans '.' :
U    src/code.h
C    src/code.c
U    README
Résumé des conflits :
  Text conflicts: 1
Conflit découvert dans le fichier 'src/code.c'.
Select: (p) postpone, (df) diff-full, (e) edit, (m) merge,
        (mc) mine-conflict, (tc) theirs-conflict, (s) show all options:

Comme vous pouvez le constater, svn merge a fusionné les modifications demandées dans notre copie de travail, marquant un conflit où le fournisseur a modifié la même zone d'un fichier que nous lors de notre personnalisation. Subversion détecte le conflit et nous donne l'opportunité de le résoudre (en utilisant les méthodes décrites dans la section intitulée « Résolution des conflits ») de façon à ce que nos personnalisations relatives à ce qui est maintenant libcomplex 1.0.1 fassent toujours sens. Une fois que nous avons résolu les conflits et effectué les tests et revues nécessaires, nous pouvons propager les modifications à notre branche fournisseur.

$ svn status
M       src/code.h
M       src/code.c
M       README
$ svn commit -m "Mise à niveau de la branche fournisseur vers libcomplex 1.0.1."
Envoi     README
Envoi     src/code.h
Envoi     src/code.c
Transmission des données ...
Révision 1294 propagée.
$

La mise à niveau de notre branche fournisseur est terminée. Et la prochaine fois que nous aurons besoin de mettre à niveau cette branche, nous suivrons la même procédure.



[44] Techniquement, nous pourrions laisser la fonctionnalité auto-props faire son travail mais l'essentiel,c'est que chaque archive du fournisseur subisse exactement le même traitement pour les propriétés automatiques.

[45] Utiliser une autre svn import ne serait pas correct, puisque libcomplex 1.0.1 et 1.0.0 se retrouveraient sans aucun ancêtre commun.

[46] Un début de solution peut être :svn add --force /chemin/vers/copie-de-travail --no-ignore --no-auto-props est super pratique pour ajouter tout nouvel élément de la version fournisseur en suivi de versions dans cette situation.