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.

Fusions : pratiques avancées

Ici finit la magie automatisée. Tôt ou tard, une fois que vous maîtrisez bien la gestion des branches et les fusions, vous allez vous retrouver à demander à Subversion de fusionner des modifications spécifiques d'un endroit à un autre. Pour faire cela, vous allez devoir commencer à passer des paramètres plus compliqués à svn merge. Le paragraphe suivant décrit la syntaxe complète de la commande et aborde un certain nombre de scénarios d'utilisation courants qui exploitent la commande.

Sélection à la main

De la même façon que le terme « ensemble de modifications » est utilisé couramment dans les systèmes de gestion de versions, le terme « sélectionner à la main » l'est aussi. Il désigne l'action de choisir une liste de modifications particulière au sein d'une branche et de la recopier dans une autre. Sélectionner à la main peut aussi faire référence à l'action de dupliquer un ensemble de modifications (pas nécessairement contiguës !) d'une branche vers une autre. Ceci est en opposition avec des scénarios de fusion plus courants, où l'ensemble de révisions contiguës « suivant » est dupliqué automatiquement.

Pourquoi voudrait-on ne recopier qu'une modification unique ? Cela arrive plus souvent qu'on ne croit. Par exemple, imaginons que vous ayez créé une branche pour une nouvelle fonctionnalité /calc/branches/ma-branche-nouvelle-calc copiée à partir de /calc/trunk :

$ svn log ^/calc/branches/ma-branche-nouvelle-calc -v -r403
------------------------------------------------------------------------
r403 | user | 2013-02-20 03:26:12 -0500 (mer. 20 fév. 2013) | 1 ligne
Chemins modifiés :
   A /calc/branches/ma-branche-nouvelle-calc (de /calc/trunk:402)

Création d'une nouvelle branche calc pour la fonctionnalité 'X'.
------------------------------------------------------------------------

À la machine à café, vous apprenez par hasard que Sally a apporté une modification intéressante à main.c dans le tronc. Vous reportant à l'historique des propagations du tronc, vous vous apercevez qu'elle a corrigé un bogue crucial en révision 413, qui impacte directement la fonctionnalité sur laquelle vous êtes en train de travailler. Vous n'êtes peut-être pas encore prêt à fusionner toutes les modifications du tronc dans votre branche, mais vous avez certainement besoin de ce correctif pour continuer votre travail.

$ svn log ^/calc/trunk -r413 -v
------------------------------------------------------------------------
r413 | sally | 2013-02-21 01:57:51 -0500 (jeu. 21 fév. 2013) | 3 lignes
Chemins modifiés :
   M /calc/trunk/src/main.c

Corrige le problème #22 'Passer une valeur null dans l'argument truc
de machin() devrait être toléré, mais cela produit un plantage'.
------------------------------------------------------------------------

$ svn diff ^/calc/trunk -c413
Index: src/main.c
===================================================================
--- src/main.c  (revision 412)
+++ src/main.c  (revision 413)
@@ -34,6 +34,7 @@
…
Détails de la correction
…

De la même façon que vous avez utilisé svn diff dans l'exemple précédent pour examiner la révision 413, vous pouvez passer le même paramètre à svn merge :

$ cd ma-branche-nouvelle-calc

$ svn merge ^/calc/trunk -c413
--- Fusion de r413 dans '.':
U    src/main.c
-- Stockage des informations de fusion (mergeinfo) de r413 dans '.' :
 U   .

$ svn st
 M      .
M       src/main.c

Vous pouvez à présent lancer les procédures habituelles de tests, avant de propager cette modification à votre branche. Après la propagation, Subversion met à jour la propriété svn:mergeinfo de votre branche pour indiquer que r413 a été fusionnée dans la branche. Cela empêche qu'une future fusion automatique de synchronisation ne tente d'appliquer une nouvelle fois r413 (fusionner une même modification dans une même branche aboutit presque toujours à un conflit !). Notez aussi les informations de fusion /calc/branches/my-calc-branch:341-379. Cela a été enregistré lors de la précédente fusion de réintégration vers /calc/trunk depuis la branche /calc/branches/ma-branche-calc que nous avons effectué en r380. Quand nous avons créé la branche ma-branche-nouvelle-calc en r403, les informations de fusion sont venues avec le reste de la copie.

$ svn pg svn:mergeinfo -v
Propriétés sur '.' :
  svn:mergeinfo
    /calc/branches/ma-branche-calc:341-379
    /calc/trunk:413

Vous pouvez remarquer que mergeinfo ne marque pas r413 comme « éligible » pour une fusion puisqu'elle a été déjà fusionnée :

$ svn mergeinfo ^/calc/trunk --show-revs eligible
r404
r405
r406
r407
r409
r410
r411
r412
r414
r415
r416
…
r455
r456
r457

Ce qui précède signifie que, quand le temps sera venu de faire une fusion de synchronisation automatique, Subversion séparera la fusion en deux parties : d'abord il fusionnera toutes les révisions éligibles jusqu'à r412, puis il fusionnera toutes les révisions éligibles depuis r414 jusqu'à la révision HEAD. Comme nous avons déjà sélectionné à la main r413, cette modification est sautée :

$ svn merge ^/calc/trunk 
--- Fusion de r403 à r412 dans '.':
U    doc/INSTALL
U    src/main.c
U    src/bouton.c
U    src/entier.c
U    Makefile
U    LISEZMOI
--- Fusion de r414 à r458 dans '.':
G    doc/INSTALL
G    src/main.c
G    src/entier.c
G    Makefile
--- Stockage des informations de fusion (mergeinfo) de r403 à r458  dans '.' :
 U   .

Ce type d'utilisation de la copie (ou rétroportage) de correctifs d'une branche à une autre est peut-être la raison la plus répandue pour sélectionner à la main des modifications ; le cas se présente très souvent, par exemple lorsqu'une équipe gère une « branche de production » du logiciel (ce thème est développé dans la section intitulée « Branches de publication »).

[Avertissement] Avertissement

Avez-vous remarqué la façon dont, dans le dernier exemple, le lancement de la fusion a eu pour effet l'application de deux ensembles distincts de fusions ? La commande svn merge a appliqué deux correctifs indépendants à votre copie de travail, afin de sauter l'ensemble de modifications 413, que votre branche contenait déjà. Il n'y a rien de mal en soi là-dedans, sauf que ça risque de rendre plus délicate la résolution des conflits. Si le premier groupe de modifications engendre des conflits, vous devrez les résoudre de façon interactive pour que la procédure de fusion puisse continuer et appliquer le deuxième groupe de modifications. Si vous remettez à plus tard un conflit lié à la première vague de modifications, la commande de fusion renverra au final un message d'erreur et vous devrez résoudre le conflit avant de lancer la fusion une deuxième fois pour récupérer le reste des modifications.

Avertissement : bien que svn diff et svn merge soient conceptuellement très similaires, leur syntaxe est différente dans de nombreux cas. Pour plus de détails, reportez-vous au Guide de référence de svn : le client texte interactif ou consultez svn help. Par exemple, svn merge demande en entrée, en tant que cible, le chemin d'une copie de travail, c'est-à-dire un emplacement où il va appliquer le correctif généré. Si la cible n'est pas spécifiée, elle suppose que vous essayez d'exécuter l'une des opérations suivantes :

  • Vous voulez fusionner les modifications du dossier dans votre dossier de travail en cours.

  • Vous voulez fusionner les modifications d'un fichier donné dans un fichier du même nom existant dans votre dossier de travail en cours.

Si vous fusionnez un dossier et que vous n'avez pas encore spécifié de cible, svn merge suppose qu'il est dans la première situation et essaie d'appliquer les modifications dans votre dossier en cours. Si vous fusionnez un fichier et que ce fichier (ou un fichier du même nom) existe dans votre dossier de travail en cours, svn merge suppose qu'il est dans la seconde situation et essaie d'appliquer les modifications au fichier local du même nom.

Syntaxe de la fusion : pour tout vous dire

Nous venons de voir des exemples d'utilisation de la commande svn merge et nous allons bientôt en voir plusieurs autres. Si vous n'avez pas bien assimilé le le fonctionnement des fusions, rassurez-vous, vous n'êtes pas un cas isolé. De nombreux utilisateurs (en particulier ceux qui découvrent la gestion de versions) commencent par une phase de perplexité au sujet de la syntaxe de la commande, ainsi que quand et comment utiliser cette fonctionnalité. Mais, en fait, cette commande est bien plus simple que vous ne le pensez ! Il y a une technique très simple pour comprendre comment svn merge agit.

La raison principale de la confusion est le nom de la commande. Le terme merge (« fusionner » en anglais) indique en quelque sorte que les branches vont être combinées, ou qu'un mystérieux mélange des données va avoir lieu. Ce n'est pas le cas. Un nom plus approprié pour cette commande aurait pu être « comparer-et-appliquer », car c'est là tout ce qui se passe : deux arborescences sont comparées et les différences sont appliquées à une copie de travail.

Si vous utilisez svn merge pour effectuer de simples copies de modifications entre branches, elle fait généralement ce qu'il faut automatiquement. Par exemple, une commande telle que :

$ svn merge ^/calc/branches/une-branche

tente de dupliquer toutes les modifications faites dans une-branche vers votre répertoire de travail actuel, qui est sans doute une copie de travail partageant des liens historiques avec la branche. La commande est suffisamment intelligente pour ne copier que les modifications que votre copie de travail ne possède pas encore. Si vous répétez cette commande une fois par semaine, elle ne copie que les modifications « les plus récentes » qui ont eu lieu depuis la dernière fusion.

Si vous choisissez d'utiliser la commande svn merge dans sa version intégrale en lui fournissant les groupes de révisions spécifiques à copier, la commande prend trois paramètres :

  1. une arborescence initiale (souvent appelée côté gauche de la comparaison) ;

  2. une arborescence finale (souvent appelée côté droit de la comparaison) ;

  3. une copie de travail qui reçoit les différences en tant que modifications locales (souvent appelée cible de la fusion).

Une fois ces trois paramètres fournis, les deux arborescences sont comparées et les différences sont appliquées à la copie de travail cible en tant que modifications locales. Une fois que la commande s'est terminée, le résultat est le même que si vous aviez édité les fichiers à la main ou lancé diverses commandes svn add ou svn delete vous-même. Si le résultat vous plaît, vous pouvez le propager. S'il ne vous plaît pas, vous pouvez toujours lancer svn revert pour revenir en arrière sur toutes les modifications.

La syntaxe de svn merge est assez flexible quant à la façon de spécifier les trois paramètres. Voici quelques exemples :

$ svn merge http://svn.exemple.com/depot/branche1@150 \
             http://svn.exemple.com/depot/branche2@212 \
             ma-copie-de-travail

$ svn merge -r 100:200 http://svn.exemple.com/depot/trunk ma-copie-de-travail

$ svn merge -r 100:200 http://svn.exemple.com/depot/trunk

La première syntaxe liste les trois arguments de façon explicite, spécifiant chaque arborescence sous la forme URL@REV et incluant la copie de travail cible. La deuxième syntaxe peut être utilisée comme raccourci pour les cas où vous comparez des révisions différentes de la même URL. Ce type de fusion est appelée (pour des raisons évidentes) une fusion « à 2-URL ». La dernière syntaxe indique que le paramètre copie de travail est optionnel ; s'il est omis, elle utilise par défaut le répertoire en cours.

Si le premier exemple donne la syntaxe « complète » de svn merge, celle-ci doit être utilisée avec grande prudence ; elle peut en effet aboutir à des fusions qui n'enregistrent pas la moindre méta-donnée svn:mergeinfo. Le paragraphe suivant évoque ceci plus en détail.

Fusions sans mergeinfo

Subversion essaie de générer des métadonnées de fusion dès qu'il le peut, afin de rendre plus intelligentes les invocations suivantes de svn merge. Néanmoins, il reste des situations où les données svn:mergeinfo ne sont ni créées ni modifiées. Pensez à être prudent avec les scénarios suivants :

Fusionner des sources sans lien de parenté

Si vous demandez à svn merge de comparer deux URLs qui n'ont pas de lien entre elles, un correctif est quand même généré et appliqué à votre copie de travail, mais aucune métadonnée de fusion n'est créée. Il n'y a pas d'historique commun aux deux sources et les futures fusions « intelligentes » dépendent de cet historique commun.

Fusionner avec des dépôts extérieurs

Bien qu'il soit possible de lancer une commande telle que svn merge -r 100:200 http://svn.projetexterieur.com/depot/trunk, le correctif résultant ne comporte aucune métadonnée historique de fusion. À la date d'aujourd'hui, Subversion n'est pas capable de représenter des URL de dépôts différents au sein de la propriété svn:mergeinfo.

Utiliser --ignore-ancestry

Si ce paramètre est passé à svn merge, il force la logique de fusion à générer les différences sans réfléchir, de la même façon que svn diff les génère, en ignorant toute considération historique. Nous traitons ce point plus loin dans ce chapitre dans la section intitulée « Prise en compte ou non de l'ascendance ».

Appliquer des fusions inversées à l'historique naturel de la cible

Précédemment dans ce chapitre (dans la section intitulée « Retour en arrière sur des modifications »), nous avons vu comment utiliser svn merge pour appliquer un « correctif inversé », comme moyen de revenir en arrière sur des modifications. Si cette technique est utilisée pour revenir sur une modification faite à l'historique propre d'un objet (par exemple, propager r5 au tronc, puis revenir immédiatement en arrière sur r5 en utilisant svn merge . -c -5), ce type de fusion ne touche pas aux informations de fusion (mergeinfo) enregistrées [41].

Plus de détails sur les conflits liés aux fusions

Tout comme la commande svn update, svn merge applique les modifications à votre copie de travail. Elle est donc aussi susceptible de créer des conflits. Cependant, les conflits engendrés par svn merge sont parfois différents et ce paragraphe va expliquer ces différences.

Pour commencer, supposons que votre copie de travail n'a pas de modification locale en cours. Quand vous lancez svn update pour la mettre à jour à une révision particulière, les modifications envoyées par le serveur s'appliquent toujours « proprement » à votre copie de travail. Le serveur génère le delta en comparant deux arborescences : d'une part un instantané virtuel de votre copie de travail, d'autre part l'arborescence de la révision qui vous intéresse. Parce que la partie gauche de la comparaison est parfaitement égale à ce que vous avez déjà, il est garanti que le delta va convertir correctement votre copie de travail en l'arborescence de droite.

Mais svn merge ne dispose pas de telles garanties et peut être bien plus chaotique : l'utilisateur avancé peut demander au serveur de comparer n'importe quelle paire d'arborescences, même des arborescences n'ayant aucun rapport avec la copie de travail ! Cela laisse potentiellement beaucoup de place à l'erreur humaine. Les utilisateurs vont parfois comparer deux arborescences qui ne sont pas les bonnes, créant ainsi un delta qui ne s'appliquera pas proprement. La sous-commande svn merge fera de son mieux pour appliquer la plus grande partie possible du delta, mais ça risque d'être impossible pour certains morceaux. Un conflit d'arborescences inattendu est un bon indice pour détecter que vous avez fusionné un mauvais delta 

$ svn merge ^/calc/trunk -r104:115
--- Fusion de r105 à r115 dans '.' :
   C doc
   C src/bouton.c
   C src/entier.c
   C src/reel.c
   C src/main.c
-- Stockage des informations de fusion (mergeinfo) de r105 à r115 dans '.' :
 U   .  
Résumé des conflits :
  Arborescences en conflit : 3

$ svn st
 M      .
!     C doc
      >   local dir missing, incoming dir edit upon merge
!     C src/button.c
      >   local file missing, incoming file edit upon merge
!     C src/integer.c
      >   local file missing, incoming file edit upon merge
!     C src/main.c
      >   local file missing, incoming file edit upon merge
!     C src/real.c
      >   local file missing, incoming file edit upon merge
Résumé des conflits :
  Arborescences en conflit : 5

Dans l'exemple précédent, il est possible que doc et les quatre fichiers *.c existent dans les deux instantanés de la branche en question. Le delta résultant tente de modifier le contenu des fichiers dans votre copie de travail mais ces fichiers n'existent pas dans la copie de travail. Quoi qu'il en soit, un nombre élevé de conflits d'arborescences signifie généralement que l'utilisateur compare les mauvaises arborescences ou qu'il essaie de fusionner vers une mauvaise copie de travail ; c'est le signe classique d'une erreur de l'utilisateur. Quand ça arrive, il est facile de revenir en arrière de manière récursive sur toutes les modifications créées par la fusion (svn revert . --recursive), d'effacer tout fichier ou dossier non suivi en versions restant après le retour en arrière et de relancer svn merge avec des paramètres différents.

Soyez aussi conscient qu'une fusion dans une copie de travail sans modification locale peut produire également des conflits textuels.

$ svn st

$ svn merge ^/paint/trunk -r289:291
--- Fusion de r290 à r291 dans '.' :
C    Makefile
-- Stockage des informations de fusion (mergeinfo) de r290 à r291 dans '.' :
 U   .  
Résumé des conflits :
  Text conflicts: 1
Conflit découvert dans le fichier 'Makefile'.
Select: (p) postpone, (df) diff-full, (e) edit, (m) merge,
        (mc) mine-conflict, (tc) theirs-conflict, (s) show all options: p
$ svn st
 M      .
C       Makefile
?       Makefile.merge-left.r289
?       Makefile.merge-right.r291
?       Makefile.working
Résumé des conflits :
  Text conflicts: 2

Comment un conflit peut-il se produire ? Encore une fois, parce que l'utilisateur peut demander à svn merge de calculer et appliquer n'importe quel vieux delta sur la copie de travail, ce delta pouvant contenir des modifications textuelles qui ne s'appliquent pas proprement à un fichier de la copie de travail, même si ce fichier ne comporte aucune modification locale.

Une autre petite différence entre svn update et svn merge réside dans le nom des fichiers textuels créés lorsqu'un conflit survient. Dans la section intitulée « Résolution des conflits », nous avons vu qu'une mise à jour produit des fichiers nommés nom_du_fichier.mine, nom_du_fichier.rANCIENNE_REV et nom_du_fichier.rNOUVELLE_REV. Quand svn merge génère un conflit, elle crée trois fichiers dont les noms sont nom_du_fichier.working, nom_du_fichier.merge-left.rANCIENNE_REV et nom_du_fichier.merge-right.rNOUVELLE_REV. Dans ce cas, les termes « merge-left » (fusion-gauche en anglais) et « merge-right » (fusion-droite en anglais) indiquent de quel côté de la comparaison d'arborescences provient le fichier en question ; « rANCIENNE_REV » indique la révision du côté gauche et « rNOUVELLE_REV » indique la révision du côté droit. Dans tous les cas, ces noms différenciés vous aident à distinguer les conflits qui résultent d'une mise à jour de ceux qui résultent d'une fusion.

Blocage de modifications

Il peut parfois y avoir un ensemble de modifications particulier dont vous ne voulez pas qu'il soit fusionné automatiquement. Par exemple, peut–être que l'habitude dans votre équipe est d'effectuer tout nouveau travail de développement dans /trunk, mais d'être plus conservateur en ce qui concerne le rétroportage des modifications vers une branche stable que vous utilisez pour la publication. À l'extrême, vous pouvez sélectionner à la main des ensembles de modifications individuels du tronc à porter vers la branche : juste les changements qui sont suffisamment stables pour être acceptables. Peut-être que les choses ne sont pas aussi strictes après tout ; peut-être que la plupart du temps vous aimeriez juste laisser svn merge fusionner automatiquement la plupart des modifications du tronc vers la branche. Dans ce cas, il vous faudrait une façon de masquer quelques modifications particulières, c'est-à-dire d'empêcher qu'elles ne soient fusionnées automatiquement.

Pour bloquer une liste de modifications, vous devez faire croire à Subversion que la modification a déjà été fusionnée. Pour cela, il est possible de lancer la sous-commande de fusion avec l'option --record-only. Cette option demande à Subversion d'enregistrer les informations de fusion comme s'il avait réellement effectué la fusion, mais aucun changement n'est effectivement appliqué :

$ cd ma-branche-calc

$ svn merge ^/calc/trunk -r386:388 --record-only 
--- Stockage des informations de fusion (mergeinfo) de r387 à r388 dans '.' :
 U   .
 
# Seules les informations de fusion sont modifiées.
$ svn st
 M      .

$ svn pg svn:mergeinfo -vR
Propriétés sur '.' :
  svn:mergeinfo
    /calc/trunk:341-378,387-388


$ svn commit -m "Bloque r387-388 vis-à-vis d'une fusion vers ma-branche-calc."
Envoi              .

Révision 461 propagée.

Depuis Subversion 1.7, les fusions avec --record-only sont transitives. Cela veut dire que, en plus d'enregistrer les informations de fusion décrivant la ou les révisions bloquées, toute modification de la propriété svn:mergeinfo dans la source de la fusion est aussi appliquée. Par exemple, supposons que nous voulions bloquer la fonctionnalité "paint-python-wrapper" d'être fusionnée depuis ^/paint/trunk vers la branche ^/paint/branches/paint-1.0.x. Nous savons que le travail sur cette fonctionnalité a été effectué dans sa propre branche, qui a été réintégrée dans /paint/trunk au moment de la révision 465 :

$ svn log -v -r465 ^/paint/trunk
------------------------------------------------------------------------
r465 | joe | 2013-02-25 14:05:12 -0500 (lun. 25 fév. 2013) | 1 ligne
Chemins modifiés :
   M /paint/trunk
   A /paint/trunk/python (de /paint/branches/paint-python-wrapper/python:464)

Réintégration de Paint Python Wrapper.
------------------------------------------------------------------------

Comme la révision 465 était une fusion de réintégration, nous savons que les informations de fusion ont été enregistrées pour décrire cette fusion :

$ svn diff ^/paint/trunk --depth empty -c465
Index: .
===================================================================
--- .   (revision 464)
+++ .   (revision 465)

Modification de propriétés sur .
___________________________________________________________________ 
Added: svn:mergeinfo
   Fusionné /paint/branches/paint-python-wrapper:r463-464

Maintenant, simplement bloquer les fusions de la révision 465 de /paint/trunk n'est pas suffisant car quelqu'un pourrait fusionner r462:464 directement depuis /paint/branches/paint-python-wrapper. Heureusement, grace à la transitivité de --record-only, les fusions seront interdites. La fusion --record-only applique le delta calculé à partir de svn:mergeinfo à la révision 465, bloquant par conséquent les fusions de cette modification directement depuis /paint/trunk et indirectement depuis /paint/branches/paint-python-wrapper :

$ cd paint/branches/paint-1.0.x

$ svn merge ^/paint/trunk --record-only -c465
--- Fusion de r465 dans '.' :
 U   .  
--- Stockage des informations de fusion (mergeinfo) de r465 dans '.' :
 G   .

$ svn diff --depth empty
Index: .
===================================================================
--- .   (révision 462)
+++ .   (copie de travail)

Modification de propriétés sur  .
___________________________________________________________________
Added: svn:mergeinfo
   Fusionné /paint/branches/paint-python-wrapper:r463-464
   Fusionné /paint/trunk:r465

$ svn ci -m "Blocage du wrapper Python dans la version 1.0 de paint."
Envoi           .

Révision 466 propagée.

Maintenant, toute tentative de fusionner la fonctionnalité vers /paint/trunk est inopérante :

$ svn merge ^/paint/trunk -c465
--- Stockage des informations de fusion (mergeinfo) de r456 dans '.' :
 U   .

$ svn st # Rien n'a changé !

$ svn merge ^/paint/branches/paint-python-wrapper -r462:464
---  Stockage des informations de fusion (mergeinfo) de r463 bis r464 in '.' :
 U   .

$ svn st # Rien n'a changé !
$

Si, plus tard, vous vous rendez compte que vous avez besoin de fusionner la fonctionnalité bloquée vers /paint/trunk , vous avez deux possibilités. Vous pouvez rejouer à l'envers la fusion r466 (la révision dans laquelle vous avez bloqué la fonctionnalité), comme cela a été montré dans la section intitulée « Retour en arrière sur des modifications ». Une fois que vous avez propagé cette modification, vous pouvez recommencer la fusion de r465 de /paint/trunk. L'autre façon, vous pouvez simplement rejouer la fusion de r465 depuis /paint/trunk avec l'option --ignore-ancestry. Cela forcera la fusion à ignorer les informations contenues dans mergeinfo et appliquera simplement les changements demandés, comme indiqué dans la section intitulée « Prise en compte ou non de l'ascendance ».

$ svn merge ^/paint/trunk -c465 --ignore-ancestry
--- Fusion de r465 in '.' :
A    python
A    python/paint.py
 G   .

Bloquer des modifications avec l'option --record-only fonctionne, mais cela peut s'avérer dangereux.Le problème principal est que nous ne faisons pas clairement la différence entre « J'ai déjà cette modification » et « Je n'ai pas cette modification, mais je n'en veux pas pour le moment. ». En fait, nous mentons au système, en lui faisant croire que la modification a déjà été fusionnée. Ce qui transfère vers vous, l'utilisateur, la responsabilité de vous rappeler que la modification n'a en fait pas été fusionnée, qu'elle n'était tout simplement pas voulue. Il n'y a pas moyen de demander à Subversion la liste des « listes des modifications bloquées ». Si vous voulez en conserver la trace (afin de pouvoir les débloquer un jour), vous devrez les consigner dans un fichier texte quelque part, ou peut-être dans une propriété inventée de toutes pièces pour l'occasion.

Historiques et annotations tenant compte des fusions passées

Une des fonctionnalités principales de tout système de gestion de versions est de conserver la trace de qui a modifié quoi et quand ils l'ont fait. Les sous-commandes svn log et svn blame sont les outils adaptés pour cela : quand on les applique à des fichiers individuels, ils renvoient non seulement l'historique des ensembles de modifications qui ont touché le fichier, mais aussi exactement quel utilisateur a écrit quelle ligne de code et quand il l'a fait.

Cependant, quand des modifications commencent à être copiées entre des branches, les choses se compliquent. Par exemple, si vous interrogiez svn log sur l'historique de votre branche fonctionnelle, elle renverrait exactement toutes les révisions qui ont touché cette branche :

$ cd ma-branche-calc

$ svn log -q
------------------------------------------------------------------------
r461 | utilisateur | 2013-02-25 05:57:48 -0500 (lun. 25 fév. 2013)
------------------------------------------------------------------------
r379 | utilisateur | 2013-02-18 10:56:35 -0500 (lun. 18 fév. 2013)
------------------------------------------------------------------------
r378 | utilisateur | 2013-02-18 09:48:28 -0500 (lun. 18 fév. 2013)
------------------------------------------------------------------------
…
------------------------------------------------------------------------
r8 | sally | 2013-01-17 16:55:36 -0500 (jeu. 17 janv. 2013)
------------------------------------------------------------------------
r7 | bill | 2013-01-17 16:49:36 -0500 (jeu. 17 janv. 2013)
------------------------------------------------------------------------
r3 | bill | 2013-01-17 09:07:04 -0500 (jeu. 17 janv. 2013)
------------------------------------------------------------------------

Mais est-ce bien là une description adéquate de tous les changements qui ont eu lieu sur cette branche ? Ce qui manque ici, c'est le fait que les révisions 352, 362, 372 et 379 résultaient en fait de fusions en provenance du tronc. Si vous regardez plus en détail l'historique d'une de ces révisions, vous ne verrez nulle part les multiples ensembles de modifications du tronc qui ont été reportés sur la branche :

$ svn log ^/calc/branches/ma-branche-calc -r352 -v
------------------------------------------------------------------------ 
r352 | utilisateur | 2013-02-16 09:35:18 -0500 (sam. 16 fév. 2013) | 1 ligne
Chemins modifiés :
   M /calc/branches/ma-branche-calc
   M /calc/branches/ma-branche-calc/Makefile
   M /calc/branches/ma-branche-calc/doc/INSTALL
   M /calc/branches/ma-branche-calc/src/bouton.c
   M /calc/branches/ma-branche-calc/src/reel.c

Synchronisation des dernières modifications du tronc dans ma-branche-calc.

Il se trouve que nous savons que cette fusion vers la branche n'était qu'une fusion de modifications du tronc. Comment pouvons-nous également voir ces modifications du tronc ? La réponse est d'utiliser l'option --use-merge-history (-g). Cette option donne le détail des modifications « filles » qui faisaient partie de la fusion.

$ svn log ^/calc/branches/my-calc-branch -r352 -v -g
------------------------------------------------------------------------ 
r352 | utilisateur | 2013-02-16 09:35:18 -0500 (sam. 16 fév. 2013) | 1 ligne
Chemins modifiés :
   M /calc/branches/ma-branche-calc
   M /calc/branches/ma-branche-calc/Makefile
   M /calc/branches/ma-branche-calc/doc/INSTALL
   M /calc/branches/ma-branche-calc/src/bouton.c
   M /calc/branches/ma-branche-calc/src/reel.c

Synchronisation des dernières modifications du tronc dans ma-branche-calc.
------------------------------------------------------------------------ 
r351 | sally | 2013-02-16 08:04:22 -0500 (sam. 16 fév. 2013) | 2 lignes
Chemins modifiés :
   M /calc/trunk/src/real.c
Fusion via : r352

Travail sur le tronc du projet calc.
------------------------------------------------------------------------
…
------------------------------------------------------------------------ 
r345 | sally | 2013-02-15 16:51:17 -0500 (ven. 15 fév. 2013) | 2 lignes
Chemins modifiés :
   M /calc/trunk/Makefile
   M /calc/trunk/src/entier.c
Fusion via : r352

Travail sur le tronc du projet calc.
------------------------------------------------------------------------
r344 | sally | 2013-02-15 16:44:44 -0500 (ven. 15 fév. 2013) | 1 ligne
Chemins modifiés :
   M /calc/trunk/src/entier.c
Fusion via : r352

Réusinage des fonctions trucmuches.

En forçant l'opération svn log à utiliser l'historique des fusions, nous obtenons non seulement la révision que nous avions demandé (r352), mais aussi les deux révisions qui l'accompagnaient — deux modifications du tronc faites par Sally. C'est une image bien plus complète de l'historique !

La commande svn blame accepte également l'option --use-merge-history (-g). Si cette option est omise, quelqu'un qui regarderait un relevé annoté ligne par ligne pour bouton.c risquerait d'avoir l'impression erronée que vous êtes responsable des lignes qui ont corrigé une certaine erreur :

$ svn blame src/button.c
…
   352    utilisateur  retval = inverse_func(button, path);
   352    utilisateur  return retval;
   352    utilisateur  }
…

Et bien qu'il soit vrai que vous avez propagé ces trois lignes lors de la révision 352, deux d'entre elles ont en fait été écrites par Sally auparavant, en révision 348, et ont été injectées dans votre branche lors d'une fusion de synchronisation :

$ svn blame button.c -g
…
G    348    sally        retval = inverse_func(button, path);
G    348    sally        return retval;
     352    utilisateur  }
…

À présent, nous savons qui doit réellement être tenu responsable pour ces deux lignes de code !

Prise en compte ou non de l'ascendance

Si vous discutez avec un développeur Subversion, il est probable qu'il fasse référence au terme d'ascendance. Ce mot est utilisé pour décrire la relation entre deux objets dans un dépôt : s'ils sont liés l'un à l'autre, un des objets est alors qualifié d'ancêtre de l'autre.

Par exemple, supposons que vous propagiez la révision 100 qui contient une modification d'un fichier truc.c. Dès lors, truc.c@99 est un « ancêtre » de truc.c@100. En revanche, supposons que vous propagiez la suppression de truc.c en révision 101 et ensuite l'ajout d'un nouveau fichier du même nom en révision 102. Dans ce cas, truc.c@99 et truc.c@102 pourraient sembler apparentés (ils ont le même chemin), mais en fait ce sont des objets complètement différents au sein du dépôt. Ils ne partagent aucun historique ou « ascendance ».

Nous abordons ce point pour mettre en évidence une différence importante entre svn diff et svn merge. La première commande ignore toute ascendance, tandis que la seconde y est particulièrement sensible. Par exemple, si vous demandez à svn diff de comparer les révisions 99 et 102 de truc.c, vous obtenez des différences basées sur les lignes ; la commande svn diff compare deux chemins à l'aveugle. Mais si vous demandez à svn merge de comparer les deux mêmes objets, elle remarque qu'ils ne sont pas liés et essaie d'abord de supprimer l'ancien fichier, puis d'ajouter le nouveau fichier ; le résultat indique une suppression puis un ajout :

D    truc.c
A    truc.c

La plupart des fusions impliquent de comparer des arborescences qui ont une relation d'ascendance de l'une à l'autre ; c'est pourquoi svn merge a ce comportement par défaut. Cependant, à l'occasion, vous pourriez vouloir que la commande svn merge compare deux arborescences sans relation d'ascendance entre elles. Par exemple, vous avez peut-être importé deux arborescences de code source représentant des publications différentes de deux fournisseurs d'un projet logiciel (voir la section intitulée « Branches fournisseurs »). Si vous demandez à svn merge de comparer les deux arborescences, vous verrez la première arborescence complètement supprimée, puis l'ajout de la seconde arborescence toute entière ! Dans ce genre de situations, vous attendez de svn merge qu'il effectue une comparaison basée sur les chemins uniquement, en ignorant toute relation entre les fichiers et les dossiers. Ajoutez l'option --ignore-ancestry à votre commande svn merge et elle se comportera comme svn diff (et inversement, l'option --notice-ancestry fera se comporter svn diff comme la commande svn merge).

[Astuce] Astuce

L'option --ignore-ancestry désactive le suivi des fusions (voir Suivi de fusions). Das bedeutet, dass weder svn:mergeinfo berücksichtigt wird, wenn svn merge ermittelt, welche Revisionen zusammengeführt werden sollen, noch svn:mergeinfo aufgezeichnet wird, um die Zusammenführung zu beschreiben.

Fusions, copies et renommages

Il arrive souvent qu'on veuille réorganiser le code source, en particulier dans les projets logiciels en Java. Les fichiers et les répertoires sont déplacés et renommés, causant souvent de grandes perturbations pour tous ceux qui travaillent sur le projet. Ceci semble être le cas idéal où utiliser une branche, n'est-ce pas ? Créer une branche, réorganiser les choses, et ensuite fusionner la branche vers le tronc, non ?

Hélas, ce scénario ne fonctionne pas si bien pour le moment et est même considéré comme l'une des faiblesses de Subversion. Le problème est que la commande svn update de Subversion n'est pas aussi robuste qu'elle le devrait, en particulier en ce qui concerne les opérations de copies et de déplacements.

Quand vous utilisez svn copy pour dupliquer un fichier, le dépôt se souvient d'où venait le nouveau fichier, mais il ne transmet pas cette information au client qui lance svn update ou svn merge. Au lieu de dire au client « Copie ce fichier que tu as déjà vers ce nouvel emplacement », il envoie un fichier entièrement nouveau. Ceci peut engendrer des problèmes, notamment des conflits d'arborescences dans le cas de renommage de fichiers, pour ce qui concerne la nouvelle copie et la suppression de l'ancien emplacement. Un fait peu connu à propos de Subversion est qu'il lui manque de « vrais renommages » — la commande svn move n'est rien de plus que l'agrégation de svn copy et svn delete.

Par exemple, supposons que vous travaillez sur votre branche privée /calc/branches/ma-branche-calc. D'abord, vous effectuez une fusion automatique de synchronisation avec /calc/trunk et vous la propagez dans r470 :

$ cd calc/trunk

$ svn merge ^/calc/trunk 
--- Fusion des différences des URLs du dépôt vers '.' :
U    doc/INSTALL
A    FAQ
U    src/main.c
U    src/bouton.c
U    src/entier.c
U    Makefile
U    LISEZMOI
 U   .
-- Stockage des informations de fusion (mergeinfo) des URLs du dépôt dans '.' :
 U   .

$ svn ci -m "Synchronisation des changements de ^/calc/trunk jusqu'à r469."
Envoi         .
Envoi         Makefile
Envoi         LISEZMOI
Envoi         FAQ
Envoi         doc/INSTALL
Envoi         src/main.c
Envoi         src/bouton.c
Envoi         src/entier.c
Transmission des données ....
Révision 470 propagée.

Puis vous renommez entier.c en tout.c dans r471 et vous effectuez des modifications dans ce même fichier dans r473. Concrètement, vous avez créé un nouveau fichier dans votre branche (c'est une copie du fichier original avec quelques modifications) et vous avez effacé le fichier original. Pendant ce temps, sur /calc/trunk, Sally a propagé des améliorations de son cru au fichier entier.c lors de la r472 :

$ svn log -v -r472 ^/calc/trunk
------------------------------------------------------------------------
r472 | sally | 2013-02-26 07:05:18 -0500 (dim. 26 fév. 2013) | 1 ligne
Chemins modifiés :
   M /calc/trunk/src/entier.c

Travail d'amélioration de entier.c sur le tronc.
------------------------------------------------------------------------

C'est alors que vous décidez de fusionner votre branche vers le tronc. Comment Subversion combine-t-il le renommage et les modifications que vous avez faits avec les modifications de Sally ?

$ svn merge ^/calc/branches/ma-branche-calc
--- Fusion des différences des URLs du dépôt dans '.' :
   C src/entier.c
 U   src/reel.c
A    src/tout.c
--- Stockage des informations de fusion (mergeinfo) des URLs du dépôt dans '.' :
 U   .
Résumé des conflits:
  conflits d'arborescences : 1

$ svn st
 M      .
      C src/entier.c
      >   local file edit, incoming file delete upon merge
 M      src/reel.c
A  +    src/tout.c
Résumé des conflits:
  conflits d'arborescences : 1

La réponse est que Subversion ne combinera pas les modifications, mais générera un conflit d'arborescences[42]parce qu'il a besoin de votre aide pour déterminer quelle part de vos modifications et quelle part des modifications de Sally doivent finalement se retrouver dans tout.c,ou même si le renommage a tout simplement lieu d'être !

Vous devrez résoudre le conflit d'arborescences avant de pouvoir propager la fusion, ce qui requiert un minimum d'intervention manuelle de votre part, voir la section intitulée « Gestion des conflits d'arborescences ». La morale de cette histoire, c'est que tant que Subversion ne se sera pas amélioré, soyez vigilant lors des fusions avec copies et renommages d'une branche vers une autre et, lorsque vous le faites, préparez-vous à effectuer des résolutions manuelles.

Blocage des clients qui ne prennent pas en compte les fusions

Si vous venez juste de mettre à niveau votre serveur Subversion à la version 1.5 ou plus, il existe un risque significatif que les clients Subversion pré-1.5 sèment la pagaille dans votre suivi automatique des fusions. Pourquoi ? Quand un client Subversion pré-1.5 exécute svn merge, il ne modifie pas du tout la valeur de la propriété svn:mergeinfo. La propagation qui s'ensuit, bien qu'elle soit le résultat d'une fusion, n'envoie donc aucune indication au dépôt au sujet des modifications dupliquées — ces informations sont perdues. Par la suite, lorsque des clients « qui prennent en compte les fusions » tentent d'effectuer une fusion automatique, ils rencontreront probablement toutes sortes de conflits résultant des fusions répétées.

Si votre équipe et vous dépendez des fonctionnalités de suivi des fusions de Subversion, vous voudrez peut-être configurer votre dépôt pour qu'il empêche les anciens clients de propager des modifications. La méthode la plus simple est d'examiner le paramètre « capabilities » dans la procédure automatique de début de propagation (start-commit). Si le client indique être capable de gérer les mergeinfo, la procédure automatique peut l'autoriser à commencer la propagation. Si le client n'indique pas en être capable, la procédure automatique doit lui refuser la propagation. Exemple 4.1, « Procédure automatique de vérification des capacités de suivi des fusions avant une propagation » donne un exemple d'une telle procédure automatique.

Exemple 4.1. Procédure automatique de vérification des capacités de suivi des fusions avant une propagation

# -*- coding: utf-8 -*-
#!/usr/bin/env python
import sys

# La procédure automatique start-commit est appelée après la création
# de la transaction Subversion et peuplée avec propriétés de révision
#
#
# [1] CHEMIN-DEPOT  (le chemin du dépôt)
# [2] UTILISATEUR   (l'utilisateur authentifié qui tente la propagation)
# [3] CAPACITES     (liste des capacités qu'annonce le client,
#                    les éléments sont séparés par des ':'
#                    voir ci-dessous)
# [4] NOM-TRANSACTION  (nom de la transaction qui vient d'être créée)

capacites = sys.argv[3].split(':')
if "mergeinfo" not in capacites:
  sys.stderr.write("Les propagations depuis un client non capable de"
                   "suivre les fusions sont interdites. "
                   "Veuillez mettre à niveau votre client  à "
                   "Subversion 1.5 ou plus récent.\n")
  sys.exit(1)
sys.exit(0)

Pour plus d'informations sur les procédures automatiques, reportez-vous au la section intitulée « Mise en place des procédures automatiques ».

Recommandations finales sur le suivi des fusions

En fin de compte, la fonctionnalité de suivi des fusions de Subversion possède une mécanique interne extrêmement complexe et la propriété svn:mergeinfo est la seule lorgnette dont l'utilisateur dispose pour observer cette mécanique.

Quand et pourquoi les informations de fusions sont enregistrées par une fusion peut parfois être difficile à comprendre. De plus, la gestion des informations de fusions comporte tout un ensemble de taxonomies et de règles, telles que les informations « explicites » et celles « implicites », les révisions « effectives » et les « non-effectives », le « nettoyage » et « l'héritage » d'un dossier parent vers les dossiers enfants.

Nous avons choisi de ne pas couvrir en détail ces sujets dans ce livre pour plusieurs raisons. Premièrement, l'utilisateur moyen serait totalement submergé par le niveau de détail disponible. Deuxièmement, et c'est le plus important, nous estimons que l'utilisateur moyen ne doit pas avoir à comprendre ces concepts ; en tant que détails d'implémentation, ils restent à l'arrière-plan. Malgré tout, si vous appréciez ce genre de choses, vous en trouverez une formidable vue d'ensemble dans un article posté sur le site internet de Collabnet (aujourd'hui recopié sur le site Web de Subversion) : https://subversion.apache.org/blog/2008-05-06-merge-info.html.

Pour le moment, si vous voulez rester à l'écart de la complexité du suivi de fusions, nous vous recommandons de vous en tenir simplement aux bonnes pratiques suivantes :

  • Pour les branches fonctionnelles à courte durée de vie, suivez la procédure simple décrite dans la section intitulée « Fusions : pratiques de base ».

  • Evitez les fusions sous les sous-arborescences et de générer des informations de fusions sur les sous-dossiers. Ne pratiquez de fusions que sur la racine de la branche, pas sur des sous-répertoires. la section intitulée « Fusions de sous-arborescences et mergeinfo »).

  • Ne modifiez jamais la propriété svn:mergeinfo directement ; utilisez svn merge avec l'option --record-only pour appliquer une modification désirée à cette métadonnée (comme expliqué dans la section intitulée « Blocage de modifications »).

  • Votre cible de fusion devrait toujours être une copie de travail qui est la racine d'une arborescence complète représentant une seule position dans le dépôt à un moment bien précis dans le temps :

    • mettez à jour avant de fusionner ! N'utilisez pas l'option --allow-mixed-revisions pour fusionner vers des copies de travail à révisions mélangées.

    • ne fusionnez pas vers des cibles dont des dossiers pointent vers d'autres branches (tels que décrits dans la section intitulée « Parcours des branches »).

    • évitez les fusions vers des cibles avec des répertoires clairsemés. De la même manières, ne fusionnez pas pour des profondeurs autres que --depth=infinity

    • Assurez-vous de toujours avoir l'accès complet en lecture à toutes vos sources de fusion et l'accès en lecture/écriture à l'ensemble de la cible de la fusion.

Bien sûr, vous pouvez être amené parfois à devoir violer certaines bonnes pratiques. Dans ce cas, ne vous inquietez pas, soyez seulement conscient des conséquences que cela engendre.



[41] À noter qu'après être revenu en arrière sur une révision de cette manière, nous ne serions plus capables de ré-appliquer cette révision avec svn merge . -c 5, puisque les informations de fusion marqueraient déjà r5 comme ayant été appliquée. Nous serions alors obligés d'utiliser l'option --ignore-ancestry pour forcer la commande de fusion à ignorer le contenu de mergeinfo !

[42] Si Sally n'avait pas propagé ses modifications dans r472, alors Subversion aurait remarqué que entier.c dans la copie de travail cible était identique à entier.c du côté gauche de la fusion et aurait permis le succès de votre renommage sans conflit d'arborescence :

$ svn merge ^/calc/branches/ma-branche-calc
--- Fusion des différences entre les URLs du dépôt dans '.' :
 U   src/reel.c
A    src/tout.c
D    src/entier.c
--- Stockage des informations de fusion (mergeinfo) des URLs du dépôt dans '.' :
 U   .