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 de base

Désormais, Sally et vous travaillez sur des branches parallèles du projet : vous travaillez sur une branche privée et Sally travaille sur le tronc (trunk en anglais), la branche de développement principale.

Pour les projets qui ont un grand nombre de contributeurs, il est d'usage que la plupart des gens ait des copies de travail du tronc. Dès que quelqu'un doit faire des modifications de longue haleine, susceptibles de perturber le tronc, une procédure standard est qu'il crée une branche privée et qu'il y propage les modifications jusqu'à ce que tout le travail soit terminé.

Bref, la bonne nouvelle est que Sally et vous n'empiétez pas l'un sur l'autre. La mauvaise nouvelle est qu'il est très facile de dériver chacun de son côté. Rappelez-vous qu'un des problèmes lié à la stratégie d'« isolement » est que lorsque vous en aurez fini avec votre branche, il risque d'être quasi impossible de refusionner vos modifications dans le tronc sans avoir à faire face à un grand nombre de conflits.

À la place, Sally et vous pourriez continuer de partager vos changements au fur et à mesure de votre travail. C'est à vous de décider quelles modifications valent la peine d'être partagées ; Subversion vous offre la possibilité de « copier » sélectivement des modifications entre les branches. Et quand vous aurez tout fini dans votre branche, l'ensemble de vos modifications pourra être recopié en entier vers le tronc. Dans la terminologie Subversion, l'action générale de réplication des modifications d'une branche vers une autre s'appelle la fusion et elle s'effectue à l'aide de plusieurs exécutions de la sous-commande svn merge.

Dans les exemples qui suivent, nous supposons que le client et le serveur Subversion sont tous deux en version 1.8 (ou plus récente). Si l'un ou l'autre sont en version plus ancienne que la 1.5, les choses sont plus compliquées : le système ne gére pas les changements de façon automatique et vous devrez utiliser des méthodes manuelles pénibles pour obtenir des résultats similaires. Vous devrez en effet toujours utiliser la syntaxe détaillée de la fusion spécifiant l'éventail des révisions à répliquer (voir la section intitulée « Syntaxe de la fusion : pour tout vous dire » plus loin dans ce chapitre) et penser à garder trace de ce qui a déjà été fusionné et de ce qui ne l'a pas encore été. Pour cette raison, nous recommandons fortement de vous assurer que client et serveur sont au moins en version 1.5.

Ensembles de modifications

Avant que nous n'allions plus loin, nous devons vous avertir que les pages suivantes contiennent de nombreuses discussions portant sur les « modifications ». Beaucoup de gens ayant de l'expérience dans les systèmes de gestion de versions utilisent le terme « modifications » et le terme « ensemble de modifications » de façon interchangeable et nous allons donc clarifier ce que Subversion entend par ensemble de modifications (changeset en anglais).

Chacun semble avoir sa propre définition, variant légèrement, d'un ensemble de modifications, ou tout du moins a une attente différente quant à leur traitement par le système de gestion de versions. En ce qui nous concerne, disons qu'un ensemble de modifications n'est qu'un simple regroupement de modifications identifié par un nom unique. Les modifications peuvent inclure des changements textuels du contenu des fichiers, des modifications de l'arborescence ou des ajustements portant sur les méta-données. En langage plus courant, un ensemble de modifications n'est qu'un correctif avec un nom auquel vous pouvez vous référer.

Dans Subversion, un numéro de révision globale N désigne une arborescence dans le dépôt : c'est ce à quoi le dépôt ressemblait après la N-ième propagation. C'est aussi le nom implicite d'un ensemble de modifications : si vous comparez l'arborescence N avec l'arborescence N-1, vous pouvez en déduire exactement le correctif qui a été propagé. Pour cette raison, il est facile de se représenter une révision N non seulement comme une arborescence, mais aussi comme un ensemble de modifications. Si vous utilisez un système de gestion des incidents pour gérer vos bogues, vous pouvez utiliser les numéros de révision pour vous référer à des correctifs particuliers permettant de résoudre des bogues — par exemple, « cet incident a été corrigé par r9238 ». Quelqu'un peut alors lancer svn log -r 9238 pour obtenir le détail des modifications qui ont corrigé le bogue et lancer svn diff -c 9238 pour voir le correctif lui-même. De plus (comme nous le verrons bientôt), la commande svn merge de Subversion est capable d'utiliser les numéros de révision. Vous pouvez fusionner des listes de modifications spécifiques d'une branche à une autre en les nommant dans les paramètres de la fusion : donner comme argument -c 9238 à svn merge fusionne la liste de modifications r9238 avec votre copie de travail.

Garder une branche synchronisée

Continuons avec notre exemple précédent et imaginons qu'une semaine a passé depuis que vous avez commencé à travailler sur votre branche privée. Votre nouvelle fonctionnalité n'est pas encore terminée, mais en même temps vous savez que d'autres personnes de votre équipe ont continué à faire des modifications importantes sur l'arborescence /trunk du projet. Vous avez intérêt à recopier ces modifications dans votre propre branche, juste pour vous assurer qu'elles se combinent bien avec vos propres modifications. Cette opération s'effectue par fusion automatique de synchronisation (une opération de fusion destinée à garder votre branche synchronisée avec les modifications faites dans l'arborescence « ancestrale » de création de ladite branche). Une fusion automatique est simplement une fusion pour laquelle vous ne fournissez que le minimum d'informations requis (c'est-à-dire une seule source et une copie de travail pour destination) et que vous laissez Subversion déterminer quels modifications doivent être fusionnées — dans une fusion automatique, aucun ensemble de modifications n'est passé à la commande svn merge par l'option -r ou -c.

[Astuce] Astuce

En fait, c'est là une bonne pratique : synchroniser fréquemment votre branche avec la ligne de développement principale permet d'éviter les conflits « surprises » le jour où vous reversez vos modifications dans le tronc.

Subversion connaît l'historique de votre branche et sait à quel moment elle s'est séparée du tronc. Afin de récupérer les modifications du tronc les plus récentes et les plus importantes, assurez-vous en premier lieu que votre copie de travail est « propre », c'est-à-dire que svn status ne liste aucune modification locale. Puis lancez juste :

$ pwd
/home/user/ma-branche-calc

$ svn merge ^/calc/trunk
--- Fusion de r341 à r351 dans '.':
--- Stockage des informations de fusion (mergeinfo) de r345 à r356 dans '.' :
U    bouton.c
U    entier.c
$

La syntaxe de base, svn merge URL, indique à Subversion qu'il doit fusionner toutes les modifications récentes depuis l'URL vers le répertoire de travail actuel (qui est bien souvent la racine de votre copie de travail). Remarquez que nous utilisons la syntaxe circonflexe (^)[33] afin d'éviter d'avoir à taper l'URL complète jusqu'au trunk. Remarquez également la notification de Subversion « Recording mergeinfo for merge … ». Ceci vous indique que la fusion met à jour la propriété svn:mergeinfo. Nous aborderons cette propriété et les notifications plus loin dans ce chapitre, dans la section intitulée « Mergeinfo et aperçus ».

[Astuce] Astuce

Dans ce chapitre et en général (listes de diffusion de Subversion, articles sur le suivi de fusions, etc.), vous rencontrerez souvent le terme mergeinfo (informations de fusion en français). C'est simplement un raccourci pour désigner la propriété svn:mergeinfo

À la fin de cet exemple, votre copie de travail de la branche contient de nouvelles modifications locales qui correspondent à toutes les modifications qui ont eu lieu sur le tronc depuis la création de votre branche :

$ svn status
 M      .
M       Makefile
M       doc/INSTALL
M       src/bouton.c
M       src/reel.c

Maintenant, le plus sage consiste à examiner attentivement chaque modification avec svn diff, puis à compiler et tester votre branche. Notez que le répertoire de travail actuel (« . ») a aussi été modifié. La commande svn diff indique que sa propriété svn:mergeinfo a été créée.

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

Modification de propriétés sur .
___________________________________________________________________
Ajouté : svn:mergeinfo
   Fusionné /calc/trunk:r341-351

Cette nouvelle propriété contient d'importantes métadonnées relatives à la fusion que vous ne devez pas modifier, car elles sont nécessaires aux futures commandes svn merge (nous en apprendrons plus sur ces métadonnées plus loin dans ce chapitre).

Après cette fusion, vous êtes susceptible de devoir résoudre quelques conflits (de même que lorsque vous effectuez une mise à jour avec svn update) ou d'effectuer des corrections à la main pour que les choses fonctionnent correctement : rappelez-vous que l'absence de conflits syntaxiques ne veut pas dire l'absence de conflits sémantiques ! Si vous rencontrez de sérieux problèmes, vous pouvez toujours abandonner vos modifications locales en lançant la commande svn revert . -R et ouvrir une conversation qui promet d'être longue avec vos collaborateurs sur le thème « c'est quoi ce truc ? ». Mais si les choses se passent bien, vous pouvez propager les modifications dans le dépôt :


$ svn commit -m "Synchronisation des dernières modifications du tronc avec ma-branche-calc."
Envoi              .
Envoi              Makefile
Envoi              doc/INSTALL
Envoi              src/bouton.c
Envoi              src/reel.c
Transmission des données .
Révision 352 propagée.

À ce stade, votre branche privée est « en phase » avec le tronc et vous pouvez dormir tranquille car vous savez que vous pouvez continuer à travailler dans votre coin tout en ne dérivant pas trop par rapport au reste de l'équipe.

Supposons qu'une autre semaine s'est écoulée. Vous avez propagé des modifications supplémentaires dans votre branche et vos camarades ont également continué à améliorer le tronc. Une fois encore, vous aimeriez répercuter les dernières modifications du tronc vers votre branche et ainsi être en phase. Lancez juste la même commande svn merge à nouveau !

$ svn merge ^/calc/trunk 
svn: E195020: Cannot merge into mixed-revision working copy [352:357]; try updating first
$

Ça par exemple, nous ne attendions pas à ça ! Après avoir fait des modifications dans votre branche cette semaine, vous vous retrouvez avec une copie de travail à révisions mélangées (voir la section intitulée « Copies de travail mixtes, à révisions mélangées »). Avec Subversion 1.7 ou plus récent, la sous-commande svn merge interdit par défaut les fusions dans les copies de travail à révisions mélangées. Sans rentrer dans les détails, cela résulte de la façon dont la trace des fusions est conservée dans la propriété svn:mergeinfo (lisez la section intitulée « Mergeinfo et aperçus » pour les détails). Des fusions dans les copies de travail à révisions mélangées peuvent créer des conflits textuels ou d'arborescence[34]. Nous ne voulons pas conflit inutile, c'est pourquoi nous mettons à jour la copie de travail et nous réessayons la fusion.

$ svn up
Mise à jour de '.' :
Actualisé à la révision 361.

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

Subversion sait quelles sont les modifications du tronc que vous avez déjà répercutées vers votre branche, il ne répercute donc que les modifications que vous n'avez pas encore. Une fois de plus, vous devrez compiler, tester et propager avec svn commit les modifications locales à votre branche.

Fusions de sous-arborescences et mergeinfo

Dans la plupart des exemples de ce chapitre, la cible de la fusion est le répertoire racine d'une branche (voir la section intitulée « Définition d'une branche »). Bien que ce soit une bonne pratique, vous aurez peut-être l'occasion de devoir fusionner avec un enfant de la racine de votre branche. Ce type de fusion est appelé une fusion de sous-arborescence et les informations de fusions (« mergeinfo ») stockées pour décrire cela s'appellent les informations de fusion de sous-arborescence ou mergeinfo de sous-arborecences. Il n'y a rien de particulier à signaler pour les fusions de sous-arborescences et les mergeinfo de sous-arborescences. En fait, il n'y a vraiment qu'un seul point à retenir pour ces concepts : l'enregistrement complet des fusions pour une branche peut ne pas être contenu uniquement dans le mergeinfo de la racine de la branche. Vous pouvez avoir à prendre en compte les mergeinfos des sous-arborescences pour obtenir le décompte total. Heureusement, Subversion le fait pour vous et vous n'aurez que rarement l'occasion de vous en préoccuper personnellement. Un court exemple vaut mieux qu'un long discours :

# We devons fusionner r958 depuis le tronc vers branches/proj-X/doc/INSTALL,
# mais cette révision touche aussi main.c, que nous ne voulons pas fusionner :
$ svn log --verbose --quiet -r 958 ^/
------------------------------------------------------------------------
r958 | bruce | 2011-10-20 13:28:11 -0400 (jeu. 20 oct 2011)
Chemins modifiés :
   M /trunk/doc/INSTALL
   M /trunk/src/main.c
------------------------------------------------------------------------

# Pas de problème, nous allons effectuer une fusion d'arborescence
# directement sur le fichier INSTALL, mais d'abord notons les
# informations de mergeinfo relatives à la racine de la branche :
$ cd branches/proj-X

$ svn propget svn:mergeinfo --recursive
Propriétés sur '.'
  svn:mergeinfo
    /trunk:651-652

# Maintenant nous effectuons la fusion d'arborescence.
# Remarquez que la source et la destination de la fusion pointent sur INSTALL :
$ svn merge ^/trunk/doc/INSTALL doc/INSTALL -c 958
-- Fusion de r958 dans 'doc/INSTALL':
U    doc/INSTALL
-- Stockage des informations de fusion (mergeinfo) de r958 dans 'doc/INSTALL' :
 G   doc/INSTALL

# Une fois la fusion effectuée, l'information de fusion de l'arboresence
# est disponible dans INSTALL :
$ svn propget svn:mergeinfo --recursive
Propriétés sur '.' :
  svn:mergeinfo
    /trunk:651-652
Propriétés sur 'doc/INSTALL' :
  svn:mergeinfo
    /trunk/doc/INSTALL:651-652,958

# Que se passe-t-il si nous décidons maintenant d'avoir l'intégralité de
# r958 ? Facile, nous avons seulement à répéter l'opération de fusion
# de cette révision, mais cette fois à la racine de la branche.
# Subversion prend en compte les informations de fusion sur INSTALL et
# n'essaie pas de fusionner quoi ce soit sur ce fichier ; seuls les
# changements sur main.c sont fusionnés.
$ svn merge ^/subversion/trunk . -c 958
-- Fusion de r958 dans '.':
U    src/main.c
-- Stockage des informations de fusion (mergeinfo) de r958 dans '.' :
 U   .
-- Nettoyage des informations de fusion (mergeinfo) de 'doc/INSTALL' :
 U   doc/INSTALL

Vous devez vous demander pourquoi INSTALL dans l'exemple ci-dessus possède des informations de fusion pour r651-652 alors que nous n'avons fusionné que r958. C'est en raison de l'héritage des informations de fusion, que nous abordons dans l'encart particulier Héritages des informations de fusion. Notez aussi que les informations de fusion de l'arborescence ont été supprimées (ou « nettoyées ») de doc/INSTALL. Ce nettoyage des informations de fusion a lieu quand Subversion détecte des informations redondantes.

[Astuce] Astuce

Avant Subversion 1.7, les fusions mettaient à jour de manière inconditionnelle les informations de fusion sous la destination pour décrire la fusion. Pour les utilisateurs qui ont beaucoup d'informations de fusion sur leurs arborescences, cela voulait dire que même des fusions relativement « simles » (par exemple une fusion qui ne concerne qu'un seul fichier) impliquaient des modifications de mergeinfo dans toutes les sous-arborescences, y compris celles qui n'avaient pas de lien de parenté avec le(s) chemin(s) concerné(s). Cela engendrait de la confusion et de la frustration. Subversion 1.7 et suivants répondent à ce problème en ne mettant à jour que les informations de fusion des arborescences qui ont des liens de parenté avec les chemins modifiés par la fusion (c'est-à-dire les chemins modifiés, ajoutés ou supprimés par application d'un changement, voir la section intitulée « Syntaxe de la fusion : pour tout vous dire »). La cible de la fusion fait exception à ce comportement ; les informations de fusion de la cible de fusion sont toujours mises à jour décrire la fusion, même si l'application de la fusion ne produit aucun changement.

Réintegration d'une branche

Que se passe-t-il quand vous finissez enfin votre travail ? Votre nouvelle fonctionnalité est terminée et vous êtes prêt à fusionner les changements de votre branche avec le tronc (pour que votre équipe puisse bénéficier du fruit de votre travail). La procédure est simple. Premièrement, synchronisez à nouveau votre branche avec le tronc, comme vous le faites depuis le début [35]


$ svn up # (pour être sûr que la copie de travail est à jour)
Mise à jour de '.' :
À la révision 378.

$ svn merge ^/calc/trunk
--- Fusion de r362 à r378 dans '.':
U    src/main.c
--- Stockage des informations de fusion (mergeinfo) de r381 à r385 dans '.' :
 U   .

$ # compiler, tester, ...

$ svn commit -m "Fusion finale des modifications du tronc dans ma-branche-calc."
Envoi              .
Envoi              src/main.c
Transmission des données .
Révision 379 propagée.

À présent, utilisez la sous-commande svn merge pour répercuter automatiquement les modifications de votre branche sur le tronc. Ce type de fusion est appelée une fusion de « réintégration automatique ». Vous aurez besoin d'une copie de travail de /calc/trunk. Vous pouvez vous la procurer soit en effectuant un svn checkout, soit en reprenant une vieille copie de travail du tronc, soit en utilisant svn switch (voir la section intitulée « Parcours des branches »).

[Astuce] Astuce

Le terme « réintégration » provient de l'option --reintegrate de la sous-commande merge. Cette option est obsolète dans Subversion 1.8 (qui détecte automatiquement quand une fusion de réintégration est nécessaire), mais elle est demandée par les clients des versions 1.5 à 1.7 de Subversion lorsque vous effectuez des fusions de réintégration.

Votre copie de travail du tronc ne doit avoir aucune modification locale, aucun chemin qui ne pointe vers une autre branche et ne pas comporter de mélange de révisions (voir la section intitulée « Copies de travail mixtes, à révisions mélangées »). Bien que ce soient de bonnes pratiques pour les fusions de toute façon, c'est particulièrement obligatoire pour une fusion de réintégration automatique.

Une fois que vous avez une copie de travail propre du tronc, vous êtes prêt pour y fusionner votre branche :

$ pwd
/home/utilisateur/calc-trunk

$ svn update
Mise à jour '.' :
À la révision 390.


$ svn merge ^/calc/branches/ma-branche-calc
--- Fusion des différences des URLs du dépôt vers '.' :
U    src/reel.c
U    src/main.c
U    Makefile 
-- Stockage des informations de fusion (mergeinfo) des URLs du dépôt vers '.' :
 U   .

$ # compiler, tester, vérifier, ...

$ svn commit -m "ma-branche-calc réintégrée dans le tronc !"
Envoi          .
Envoi          Makefile
Envoi          src/main.c
Envoi          src/reel.c
Transmission des données ...
Révision 380 propagée.

Félicitations, votre branche a maintenant réintégré la ligne de développement principale. Notez que la fusion de réintégration automatique a effectué un travail différent de ce que vous avez fait jusqu'à maintenant. Auparavant, nous demandions à svn merge de récupérer le « prochain lot de modifications » d'une ligne de développement (le tronc en l'occurence) et de l'appliquer à une autre (votre branche). C'est assez simple à réaliser et à chaque fois Subversion sait reprendre là où il s'était arrêté. Dans nos exemples précédents, vous pouvez constater qu'il fusionne en premier les modifications 341:351 de /calc/trunk vers /calc/branches/ma-branche-calc ; ensuite il continue en fusionnant l'intervalle immédiatement suivant, 351:361. Quand il effectue la synchronisation finale, il fusionne l'intervalle 361:378.

Cependant, quand il fusionne /calc/branches/ma-branche-calc vers /calc/trunk, la logique sous-jacente est assez différente. Votre branche dédiée est à présent un amoncellement de modifications provenant à la fois du tronc et de votre branche privée et il n'y a donc pas d'intervalle de révisions contigues à recopier. En utilisant la fusion automatique, vous demandez à Subversion de ne recopier que les modifications spécifiques à votre branche (et en fait il le fait en comparant la version la plus récente de l'arborescence du tronc avec la version la plus récente de l'arborescence de la branche : la différence qui en résulte constitue exactement les modifications de votre branche !).

Gardez à l'esprit que les fusions de réintégration automatiques ne fonctionnent que dans le cas cité ci-dessus. En raison de cette configuration particulière et des autres prérequis annoncés précédemment (une copie de travail à jour[36] sans révisions mélangées, sans chemins qui pointent vers d'autres branches ou modifications locales), elles ne fonctionneront pas en combinaison avec la plupart des autres options de la sous-commande svn merge. Vous obtiendrez une erreur si vous utilisez n'importe laquelle des options non-globales autres que celles-ci : --accept, --dry-run, --diff3-cmd, --extensions ou --quiet.

Maintenant que votre branche privée a réintégré le tronc, vous voudrez peut-être la supprimer du dépôt :

$ svn delete ^/calc/branches/ma-branche-calc \
      -m "Supprime ma-branche-calc, réintégrée dans le tronc à r391."
…

Mais attendez ! L'historique de votre branche ne possède-t-il pas une certaine valeur ? Et si un beau jour quelqu'un voulait auditer l'évolution de votre fonctionnalité et examiner toutes les modifications de votre branche ? Pas la peine de s'inquiéter. Souvenez-vous que, même si votre branche n'est plus visible dans le dossier /calc/branches, son existence demeure une partie immuable de l'historique du dépôt. Une simple commande svn log appliquée à l'URL /calc/branches vous renverra l'historique complet de votre branche. Votre branche pourrait même ressusciter un jour ou l'autre, si vous le désirez (voir la section intitulée « Résurrection des éléments effacés »).

Si vous décidez de ne pas détruire votre branche après réintégration dans le tronc, vous pouvez continuer à effectuer des fusions de synchronisation depuis le tronc puis réintégrer la branche à nouveau [37]. Si vous adoptez ce comportement, seules les modifications effectuées sur votre branche après la réintégration seront fusionnées vers le tronc.

Mergeinfo et aperçus

Le mécanisme de base que Subversion utilise pour gérer les ensembles de modifications, c'est-à-dire quelles modifications ont été fusionnées dans quelles branches, est l'enregistrement de données dans des propriétés suivies en versions. Plus précisément, les informations de fusion sont conservées dans la propriété svn:mergeinfo qui est associée aux fichiers et aux dossiers (si les propriétés de Subversion ne vous sont pas familières, c'est le moment de lire la section intitulée « Propriétés »).

Vous pouvez examiner cette propriété comme n'importe quelle autre propriété suivie en versions :

$ cd ma-branche-calc

$ svn pg svn:mergeinfo -v
Propriétés sur '.'
  svn:mergeinfo
    /calc/trunk:341-378
[Avertissement] Avertissement

Bien qu'il soit possible de modifier soi-même svn:mergeinfo comme n'importe qu'elle autre propriété suivie en versions, nous déconseillons vivement de le faire à moins de réellement savoir ce que vous faites.

[Astuce] Astuce

La quantité de svn:mergeinfo sur un simple chemin peut être assez grande, de même que la sortie produite par svn propget --recursive ou svn proplist --recursive lorsque vous ciblez de grosses quantités d'arborescences, comme l'indique la section intitulée « Fusions de sous-arborescences et mergeinfo ». Dans ces cas, le formatage de la sortie produit par l'option --verbose avec ces deux commandes est souvent bien utile.

La propriété svn:mergeinfo est manipulée automatiquement par Subversion à chaque fois que vous lancez svn merge. Sa valeur indique quelles modifications (pour un chemin donné) ont été recopiées dans le dossier en question. Dans le cas présent, le chemin d'origine de la fusion des modifications est /calc/trunk et le dossier qui a reçu les modifications spécifiées est /calc/branches/ma-branche-calc. Les vieilles versions de Subversion tenaient à jour la propriété svn:mergeinfo silencieusement. Vous en détectiez quand même les modifications, après une fusion, lors de l'utilisation des sous-commandes svn diff ou svn status, mais la fusion en elle-même n'indiquait rien de la modification de la propriété svn:mergeinfo. Dans les versions 1.7 et ultérieures de Subversion, ce n'est plus le cas puisque plusieurs notifications vous avertissent de la mise à jour de la propriété svn:mergeinfo par une opération de fusion. Ces notifications commencent toutes par « --- Stockage des informations de fusion (mergeinfo) » et sont indiquées à la fin de l'opération de fusion. Contrairement aux autres notifications de la fusion, elles ne décrivent pas les modifications apportées à la copie de travail (voir la section intitulée « Syntaxe de la fusion : pour tout vous dire »), mais plutôt la conservation des modifications effectuées pour garder la trace de ce qui a été fusionné.

Il existe également une sous-commande, svn mergeinfo, qui peut être utile pour voir les relations de fusions entre deux branches ; particulièrement, quels ensembles de modifications un dossier a absorbés, mais aussi quels ensembles de modifications il est encore susceptible de recevoir. Ceci donne une sorte d'aperçu du prochain ensemble de modifications que svn merge recopiera vers votre branche. Par défaut, svn mergeinfo donne un aperçu graphique de la relation entre les branches. Pour en revenir à notre exemple précédent, nous utilisons la sous-commande pour analyser la relation entre /calc/trunk et /calc/branches/ma-branche-calc  :

$ cd ma-branche-calc

$svn mergeinfo ^/calc/trunk
    youngest common ancestor
    |         last full merge
    |         |        tip of branch
    |         |        |         repository path
    340                382
    |                  |
  -------| |------------         calc/trunk
     \          /
      \        /
       --| |------------         calc/branches/ma-branche-calc
              |        |
              379      382

Le diagramme indique que /calc/branches/ma-branche-calc a été copié à partir de /calc/trunk@340 et que la fusion automatique la plus récente était la fusion de réintégration que nous avons faite depuis la branche vers le tronc à r380. Notez que le diagramme n'indique pas les quatre fusions de synchronisation que nous avons effectuées aux révisions 352, 362, 372 et 379. Seule la fusion automatique la plus récente est indiquée, quelle que soit sa direction [38]. Cet affichage par défaut est utile pour obtenir un aperçu des fusions entre deux branches, mais pour voir les révisions spécifiques qui ont fait l'objet d'une fusion, nous utilisons l'option --show-revs=merged :

$ svn mergeinfo ^/calc/trunk --show-revs merged
r344
r345
r346
…
r366
r367
r368

De la même manière, pour voir quelles modifications sont éligibles pour une fusion depuis le tronc vers la branche, nous pouvons utiliser l'option --show-revs=eligible :

$ svn mergeinfo ^/calc/trunk --show-revs eligible
r380
r381
r382

La sous-commande svn mergeinfo requiert une URL « source » (d'où proviennent les modifications) et prend optionnellement une URL « cible » (où les modifications sont fusionnées). Si aucune URL cible n'est fournie, Subversion suppose que le répertoire courant est la cible. Dans l'exemple précédent, comme nous interrogeons notre copie de travail de la branche, la commande suppose que nous nous intéressons aux modifications que nous souhaitons apporter à /calc/branches/ma-branche-calc depuis l'URL du tronc telle que spécifiée.

Depuis Subversion 1.7, la sous-commande svn mergeinfo peut également traiter les informations de fusion de la sous-arborescence et les informations de fusion non héritables. Elle traite les informations de fusion de sous-arborescence avec les options --recursive ou --depth, et les informations de fusion non héritables sont traitées par défaut.

Considérons que nous avons une branche avec à la fois une sous-arborescence et des informations de fusion non héritables :

$ svn pg svn:mergeinfo -vR 
# informations de fusion non héritables
Propriétés sur '.'
  svn:mergeinfo
    /calc/trunk:354,385-388* 
# sous-arborescence d'informations de fusion
Propriétés sur 'doc/INSTALL'
  svn:mergeinfo
    /calc/trunk/Makefile:354,380

Dans les informations de fusion ci-dessus, nous voyons que r385-388 a été fusionnée seulement à la racine de la branche et dans aucun des enfants de la racine. Nous voyons aussi que r380 a été fusionnée seulement dans Makefile. Quand nous utilisons svn mergeinfo avec l'option --recursive pour voir ce qui a été fusionné depuis /calc/trunk vers cette branche, nous voyons trois révisions qui sont marquées avec * :

$ svn mergeinfo -R --show-revs=merged ^/calc/trunk .
r354
r380*
r385
r386
r387*
r388*

Cette marque * indique des révisions qui ont fait l'objet de fusions partielles vers la cible en question (la signification est la même que lorsque nous cherchons des révisions éligibles). Dans cet exemple, cela signifie que si nous essayons de fusionner r380, r387 ou r388 depuis ^/trunk alors des modifications seront apportées. De la même manière, puisque r354, r385 et r386 ne sont pas marquées avec *, nous savons que fusionner à nouveau ces révisions ne produira aucun changement.[39]

Une autre manière d'obtenir un aperçu plus précis d'une opération de fusion est d'utiliser l'option --dry-run :

$ svn merge ^/paint/trunk ma-branche-peinture --dry-run
--- Fusion de r290 à r383 dans 'ma-branche-peinture':
U    ma-branche-peinture/src/palettes.c
U    ma-branche-peinture/src/brosses.c
U    ma-branche-peinture/Makefile

$ svn status 
#  rien ne s'affiche, la copie de travail n'a pas changé.

L'option --dry-run n'effectue en fait pas de modification locale sur la copie de travail. Elle ne fait qu'indiquer les codes d'état qui seraient affichés par une vraie fusion. Ceci permet d'obtenir un « aperçu général » d'une fusion potentielle, pour les fois où svn diff renvoie trop de détails.

[Astuce] Astuce

Après avoir effectué une opération de fusion, mais avant d'en avoir propagé les résultats, vous pouvez utiliser svn diff --depth=empty /chemin/vers/la/cible/de/la/fusion pour visualiser uniquement les modifications apportées à la cible immédiate de votre fusion. Si la cible de la fusion est un dossier, seules les différences de propriétés sont alors affichées. C'est un moyen très pratique pour voir les modifications de la propriété svn:mergeinfo enregistrées par l'opération de fusion, qui vous rappellera ce que vous venez juste de fusionner.

Bien sûr, la meilleure façon d'avoir un aperçu d'une opération de fusion est tout simplement de la réaliser ! Souvenez-vous que lancer svn merge n'est pas une opération risquée en soi (à moins que vous ayez effectué des modifications locales dans votre copie de travail, mais nous avons déjà souligné que vous ne devriez pas faire de fusion dans de telles circonstances). Si les résultats de la fusion ne vous plaisent pas, lancez juste svn revert . -R pour ôter les modifications de votre copie de travail et réessayez la commande avec des options différentes. La fusion n'est définitive qu'une fois que vous en avez propagé les résultats par svn commit.

Retour en arrière sur des modifications

Un usage très répandu de svn merge est le retour en arrière sur une modification qui a déjà été propagée. Supposons que vous travaillez tranquillement sur une copie de travail de /calc/trunk et que vous découvrez tout à coup que la modification faite il y a longtemps lors de la révision 392, qui affectait plusieurs fichier sources, est complètement incorrecte. Elle n'aurait jamais du être propagée. Vous pouvez utiliser svn merge pour « revenir en arrière » sur ces modifications dans votre copie de travail, puis propager les modifications locales au dépôt. Il vous suffit juste de spécifier une différence inversée (en indiquant soit --revision 392:391, soit --change -392, les deux se valent).

$ svn merge ^/calc/trunk . -c-392 
-- Fusion inverse de r392 dans '.' :
U    src/reel.c
U    src/main.c
U    src/bouton.c
U    src/entier.c
-- Stockage des informations de fusion (mergeinfo) inverse de r392 vers '.' :
 U   .

$ svn st
M       src/bouton.c
M       src/entier.c
M       src/main.c
M       src/reel.c
$ svn diff
… 
# vérifions que les modifications ont été annulées
…

$ svn commit -m "Retour en arrière sur les modifications propagées en r392."
Envoi          src/bouton.c
Envoi          src/entier.c
Envoi          src/main.c
Envoi          src/reel.c
Envoi          entier.c
Transmission des données ....
Révision 399 propagée.

Comme nous l'avons signalé précédemment, une façon de se représenter une révision du dépôt est de la considérer comme un ensemble de modifications spécifique. En utilisant l'option -r, vous pouvez demander à svn merge d'appliquer un ensemble de modifications, ou tout un groupe d'ensembles de modifications, à votre copie de travail. Dans le cas présent, pour revenir en arrière, nous demandons à svn merge d'appliquer dans le sens inverse l'ensemble de modifications r392 à notre copie de travail.

Gardez à l'esprit que revenir en arrière sur une modification de cette façon est similaire à toute autre opération svn merge, vous devez donc ensuite utiliser svn status et svn diff pour vous assurer que votre travail est dans l'état que vous voulez, puis utiliser svn commit pour propager la version finale au dépôt. Après la propagation, cet ensemble de modifications particulier n'est plus présent dans la révision HEAD.

À nouveau vous vous dites : bon, ceci n'a pas vraiment annulé la propagation, n'est-ce pas ? La modification existe toujours en révision 392. Si quelqu'un extrait une version du projet calc entre les révisions 392 et 398, il verra toujours la mauvaise modification, non ?

Oui, c'est vrai. Quand nous parlons de « supprimer » une modification, il s'agit de la supprimer de la révision HEAD. La modification originale existe toujours dans l'historique du dépôt. Dans la plupart des situations, c'est suffisant. La plupart des gens ne s'intéressent d'ailleurs qu'à la révision HEAD du projet. Il y a des cas particuliers, cependant, où l'on voudra vraiment détruire toute preuve de la propagation (quelqu'un a peut-être accidentellement propagé un document confidentiel). Cela ne s'avère pas si facile, parce que Subversion a été conçu délibérément pour ne jamais perdre d'information. Les révisions sont des arborescences immuables qui sont empilées les unes par dessus les autres. Supprimer une révision de l'historique créerait un effet domino, engendrant le chaos dans les révisions ultérieures et invalidant potentiellement toutes les copies de travail [40].

Résurrection des éléments effacés

Ce qu'il y a de formidable dans les systèmes de gestion de versions, c'est que les informations ne sont jamais perdues. Même si vous effacez un fichier ou un dossier, s'il disparaît bien de la révision HEAD, l'objet existe toujours dans les révisions précédentes. Une des questions les plus courantes que posent les nouveaux utilisateurs est : « Comment est-ce que je récupère mon ancien fichier ou dossier ? »

La première étape est de définir exactement quel élément vous essayez de ressusciter. Voici une métaphore utile : vous pouvez imaginer votre objet dans le dépôt comme existant dans une sorte de système à deux dimensions. La première coordonnée est une révision correspondant à une arborescence particulière ; la deuxième coordonnée est un chemin à l'intérieur de cette arborescence. Ainsi, toute version d'un fichier ou d'un dossier peut être définie par une paire de coordonnées qui lui est propre (souvenez-vous de la syntaxe des « révisions pivots » : machin.c@224, mentionnée dans la section intitulée « Révisions pivots et révisions opérationnelles »).

Tout d'abord, vous allez peut-être avoir besoin de svn log pour identifier précisément les coordonnées du fichier ou dossier que vous voulez ressusciter. À cette fin, une bonne stratégie est de lancer svn log --verbose dans un dossier qui contenait votre élément effacé. L'option --verbose (-v) renvoie la liste de tous les éléments modifiés par chaque révision ; il vous suffit alors de trouver la révision dans laquelle vous avez effacé le fichier ou le dossier en question. Vous pouvez accomplir cette recherche soit visuellement soit en utilisant un autre outil pour examiner le résultat de la commande svn log (via grep ou peut-être via une recherche incrémentale dans un éditeur). Si vous savez que l'élément en question a été effacé recemment, vous pouvez utiliser l'option --limit pour conserver un affichage de l'historique suffisamment bref afin d'être exploité manuellement.

$ cd calc/trunk

$ svn log -v --limit 3
------------------------------------------------------------------------ 
r401 | sally | 2013-02-18 23:15:44 -0500 (mar. 19 fév. 2013) | 1 ligne
Chemins modifiés :
   M /calc/trunk/src/main.c

Suite à r400 : corrections de coquilles dans le texte d'aide.
------------------------------------------------------------------------
r400 | bill | 2013-02-19 20:55:08 -0500 (mar. 19 fév. 2013) | 4 lignes
Chemins modifiés :
   M /calc/trunk/src/main.c
   D /calc/trunk/src/reel.c


* calc/trunk/src/main.c: mise à jour du texte d'aide.

* calc/trunk/src/reel.c: fichier supprimé, aucune API de ce fichier
  n'est encore utilisée.
------------------------------------------------------------------------
r399 | sally | 2013-02-19 20:05:14 -0500 (mar. 19 fév. 2013) | 1 ligne
Chemins modifiés :
   M /calc/trunk/src/bouton.c
   M /calc/trunk/src/entier.c
   M /calc/trunk/src/main.c
   M /calc/trunk/src/reel.c

Retour en arrière sur les modifications propagées en r392.
------------------------------------------------------------------------

Dans l'exemple ci-dessus, nous supposons que vous recherchez un fichier effacé nommé reel.c. En examinant le journal du dossier parent, vous avez découvert que ce fichier a été effacé en révision 400. La dernière version du fichier à avoir existé était donc dans la révision précédant celle-ci. Conclusion : vous voulez ressusciter le chemin /calc/trunk/reel.c tel qu'il était en révision 399.

Voilà, c'était la partie difficile : la recherche. Maintenant que vous savez ce que vous voulez récupérer, deux options s'offrent à vous.

Une possibilité serait d'utiliser svn merge pour appliquer la révision 400 « à l'envers » (nous avons déjà parlé de comment revenir sur des modifications dans la section intitulée « Retour en arrière sur des modifications »). Ceci aurait pour effet de ré-ajouter reel.c en tant que modification locale. Le fichier serait alors programmé pour être ajouté et après la propagation le fichier existerait à nouveau dans HEAD.

Cependant, dans cet exemple particulier, ce n'est probablement pas la meilleure stratégie. Appliquer la révision 400 à l'envers programmerait non seulement l'ajout de reel.c, mais le commentaire de propagation indique qu'il reviendrait aussi sur certaines modifications de main.c, ce que vous ne voulez pas. Vous pourriez certainement fusionner à l'envers la révision 808 et ensuite revenir sur les modifications locales faites dans main.c, mais cette technique fonctionne mal à plus grande échelle. Que dire si 90 fichiers avaient été modifiés en révision 400 ?

Une seconde stratégie, plus ciblée, est de ne pas utiliser svn merge du tout, mais plutôt d'utiliser la commande svn copy. Copiez juste la révision et le chemin exacts (vos deux « coordonnées ») du dépôt vers votre copie de travail :

$ svn copy ^/calc/trunk/src/reel.c@399 ./reel.c
A         reel.c
$ svn st
A  +    reel.c
# Propager la résurrection
…

Le symbole plus dans le résultat de la commande svn status indique que l'élément n'est pas simplement programmé pour ajout, mais programmé pour ajout « avec son historique ». Subversion se souviendra d'où il a été copié. Dans le futur, lancer svn log sur ce fichier parcourra tout son historique en passant par la résurrection du fichier ainsi que tout ce qui précédait la révision 399. En d'autres termes, ce nouveau reel.c n'est pas vraiment nouveau ; c'est un descendant direct du fichier original qui avait été effacé. En général c'est une bonne chose, dont l'utilité est avérée. Si cependant vous vouliez récupérer le fichier sans conserver de lien historique avec l'ancien fichier, la technique suivante fonctionnerait tout aussi bien :

$ svn cat ^/calc/trunk/reel.c@399 > ./reel.c

$ svn add reel.c
A         reel.c

# Propager la résurrection
…

Bien que notre exemple ne porte que sur la résurrection d'un fichier, remarquez que ces mêmes techniques fonctionnent tout aussi bien pour ressusciter des dossiers effacés. Remarquez aussi que cette résurrection ne doit pas forcément avoir lieu dans votre copie de travail ; elle peut avoir lieu entièrement dans le dépôt :

$ svn copy ^/calc/trunk/src/reel.c@399 ^/calc/trunk/src/reel.c \
           -m "Ressuscite reel.c depuis la révision 399."
Révision 402 propagée.

$ svn up
Mise à jour de '.' :
A    reel.c
À la révision 402.


[33] Cette notation a été introduite par Subversion 1.6

[34] L'option --allow-mixed-revisions de la sous-commande svn merge vous permet de lever cette interdiction, mais vous ne devriez le faire que si vous comprenez les implications et que vous avez une bonne raison de le faire.

[35] Depuis Subversion 1.7 vous n'avez pas absolument besoin de resynchroniser complètement votre branche avec le tronc comme nous le faisons dans cet exemple. Si votre branche est effectivement synchronisée par une série de fusions d'arborescences alors la réintégration fonctionnera, mais demandez-vous, si la branche est effectivement synchronisée, pourquoi effectuez-vous des fusions d'arborescences ? Le faire est pratiquement toujours inutilement complexe.

[36] les fusions de réintégration automatiques sont autorisées si la cible est une extraction partielle (voir la section intitulée « Répertoires clairsemés »), mais alors chaque chemin concerné par le calcul de différence et qui est « absent » en raison de l'extraction partielle sera ignoré, ce qui n'est probablement pas ce que vous recherchez !

[37] Seul Subversion 1.8 autorise cette réutilisation d'une branche. Les précédentes versions demandaient quelques manipulations préalables afin de pouvoir réintegrer à nouveau une branche. Consultez les versions antérieures de ce chapitre pour plus d'informations : https://svnbook.red-bean.com/fr/1.5/svn.branchmerge.basicmerging.html#svn.branchemerge.basicmerging.reintegrate

[38] Par « direction », nous entendons les fusions soit du tronc vers la branche (synchronisation automatique), soit de la branche vers le tronc (réintégration automatique).

[39] C'est un bon exemple de révisions non-effectives pour la fusion.

[40] Le projet Subversion prévoit néanmoins d'implémenter, un jour, une commande qui accomplirait la tâche de supprimer des informations de façon permanente. En attendant, en guise de palliatif, voir la section intitulée « svndumpfilter ».