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.
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 » pourrait l'être 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, remontons le temps et imaginons que vous
n'avez pas encore réintégré votre branche de développement
privée dans le tronc. À la machine à café, vous apprenez par
hasard que Sally a apporté une modification intéressante à
entier.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 355, 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 diff -c 355 http://svn.exemple.com/depot/calc/trunk Index: entier.c =================================================================== --- entier.c (revision 354) +++ entier.c (revision 355) @@ -147,7 +147,7 @@ case 6: sprintf(info->operating_system, "HPFS (OS/2 ou NT)"); break; case 7: sprintf(info->operating_system, "Macintosh"); break; case 8: sprintf(info->operating_system, "Z-System"); break; - case 9: sprintf(info->operating_system, "CP/MM"); + case 9: sprintf(info->operating_system, "CP/M"); break; case 10: sprintf(info->operating_system, "TOPS-20"); break; case 11: sprintf(info->operating_system, "NTFS (Windows NT)"); break; case 12: sprintf(info->operating_system, "QDOS"); break;
De la même façon que vous avez utilisé svn diff dans l'exemple précédent pour examiner la révision 355, vous pouvez passer le même paramètre à svn merge :
$ svn merge -c 355 http://svn.exemple.com/depot/calc/trunk U entier.c $ svn status M entier.c
Vous pouvez à présent lancer les procédures habituelles de tests, avant de propager cette modification à votre branche. Après la propagation, Subversion marque r355 comme ayant été fusionnée dans la branche, afin qu'une future fusion « magique » synchronisant votre branche avec le tronc sache qu'elle doit sauter r355 (fusionner une même modification dans une même branche aboutit presque toujours à un conflit !).
$ cd ma-branche-calc $ svn propget svn:mergeinfo . /trunk:341-349,355 # Remarquez que r355 n'est pas listée comme "éligible" à la fusion, # parce qu'elle a déjà été fusionnée. $ svn mergeinfo http://svn.exemple.com/depot/calc/trunk --show-revs eligible r350 r351 r352 r353 r354 r356 r357 r358 r359 r360 $ svn merge http://svn.example.com/depot/calc/trunk --- Fusion de r350 à r354 dans '.': U . U entier.c U Makefile --- Fusion de r356 à r360 dans '.': U . U entier.c U bouton.c
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 | |
---|---|
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 355, 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 [23]. |
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 Chapitre 9, Références complètes de Subversion 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, il 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.
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 » 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 http://svn.exemple.com/depot/calc/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 :
une arborescence initiale (souvent appelée côté gauche de la comparaison) ;
une arborescence finale (souvent appelée côté droit de la comparaison) ;
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. 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.
Subversion essaye 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 :
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.
Bien qu'il soit possible de lancer une commande telle
que svn merge -r 100:200
,
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é
http://svn.projetexterieur.com/depot/trunk
svn:mergeinfo
.
--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 ».
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
[24].
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. svn merge fera de son mieux pour appliquer la plus grande partie possible du delta, mais ça risque d'être impossible pour certains morceaux. De la même façon que la commande Unix patch se plaint parfois de « morceaux ratés » (failed hunks), svn merge se plaint de « cibles manquantes omises » (skipped targets) :
$ svn merge -r 1288:1351 http://svn.exemple.com/depot/branche U truc.c U bidule.c Cible manquante omise : 'baz.c' U blob.c U machin.h Conflit découvert dans 'glorb.h'. Sélectionner : (p) report, (df) diff complet, (e) édite, (h) aide pour plus d'options :
Dans l'exemple précédent, il est possible que
baz.c
existe dans les deux instantanés
de la branche en question et que le delta résultant
tente de modifier le contenu du fichier, mais que le fichier
n'existe pas dans la copie de travail. Quoi qu'il en soit,
le message « Cible manquante omise » signifie que
l'utilisateur compare probablement les mauvaises
arborescences ; 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.
Remarquez également que l'exemple précédent indique un
conflit sur glorb.h
. Nous avons déjà
mentionné que la copie de travail n'a pas de modifications
locales en cours : comment peut-il donc y avoir
conflit ? Encore une fois, parce que l'utilisateur peut
utiliser svn merge pour définir et
appliquer n'importe quel vieux delta à la copie de travail,
ce delta risque de contenir des modifications textuelles qui
ne s'appliquent pas proprement à un fichier de la copie de
travail, même si ce fichier n'a pas de modifications locales
en cours.
Une autre petite différence entre
svn update et svn merge
concerne les noms des fichiers textes créés quand un conflit
a lieu. Dans la section intitulée « Résoudre les conflits (fusionner des modifications) »,
nous avons vu qu'une mise à jour génère des fichiers appelés
nomdufichier.mine
,
nomdufichier.rOLDREV
et
nomdufichier.rNEWREV
. Cependant,
quand svn merge cause un conflit, il crée
trois fichiers appelés
nomdufichier.working
,
nomdufichier.left
et
nomdufichier.right
. Dans ce cas,
les termes « left » et « right »
décrivent de quel côté de la double comparaison d'arborescences
le fichier venait. Dans tous les cas, ces noms de fichiers
différents vous aideront à distinguer les conflits résultant
d'une mise à jour de ceux résultant d'une fusion.
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.
Dans Subversion 1.5, la seule manière de bloquer une
liste de modifications est de faire croire au système que
cette modification a déjà été fusionnée. Pour cela, il est
possible de lancer la commande de fusion avec l'option
--record-only
:
$ cd ma-branche-calc $ svn propget svn:mergeinfo . /trunk:1680-3305 # Marquons l'ensemble de modifications r3328 comme déjà fusionné. $ svn merge -c 3328 --record-only http://svn.exemple.com/depot/calc/tronc $ svn status M . $ svn propget svn:mergeinfo . /trunk:1680-3305,3328 $ svn commit -m "Empêche r3328 d'être fusionnée vers la branche." …
Cette technique fonctionne, mais elle est un petit peu dangereuse. 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 ». 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 de 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. Dans Subversion 1.5, malheureusement, c'est la seule façon de gérer les révisions bloquées ; dans les versions futures il est prévu d'en améliorer l'interface.
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 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 commencent à se compliquer. Par exemple, si vous interrogiez svn log sur l'historique de votre branche fonctionnelle, il renverrait exactement toutes les révisions qui ont touché cette branche :
$ cd ma-branche-calc $ svn log -q ------------------------------------------------------------------------ r390 | utilisateur | 2002-11-22 11:01:57 -0600 (ven. 22 nov. 2002) | 1 ligne ------------------------------------------------------------------------ r388 | utilisateur | 2002-11-21 05:20:00 -0600 (jeu. 21 nov. 2002) | 2 lignes ------------------------------------------------------------------------ r381 | utilisateur | 2002-11-20 15:07:06 -0600 (mer. 20 nov. 2002) | 2 lignes ------------------------------------------------------------------------ r359 | utilisateur | 2002-11-19 19:19:20 -0600 (mar. 19 nov. 2002) | 2 lignes ------------------------------------------------------------------------ r357 | utilisateur | 2002-11-15 14:29:52 -0600 (ven. 15 nov. 2002) | 2 lignes ------------------------------------------------------------------------ r343 | utilisateur | 2002-11-07 13:50:10 -0600 (jeu. 07 nov. 2002) | 2 lignes ------------------------------------------------------------------------ r341 | utilisateur | 2002-11-03 07:17:16 -0600 (dim. 03 nov. 2002) | 2 lignes ------------------------------------------------------------------------ r303 | sally | 2002-10-29 21:14:35 -0600 (mar. 29 oct. 2002) | 2 lignes ------------------------------------------------------------------------ r98 | sally | 2002-02-22 15:35:29 -0600 (ven. 22 fev. 2002) | 2 lignes ------------------------------------------------------------------------
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 390, 381 et 357 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 -v -r 390 ------------------------------------------------------------------------ r390 | utilisateur | 2002-11-22 11:01:57 -0600 (ven. 22 nov. 2002) | 1 ligne Chemins modifiés : M /branches/ma-branche-calc/bouton.c M /branches/ma-branche-calc/LISEZMOI Fusion finale des 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 -v -r 390 -g ------------------------------------------------------------------------ r390 | utilisateur | 2002-11-22 11:01:57 -0600 (ven. 22 nov. 2002) | 1 ligne Chemins modifiés : M /branches/ma-branche-calc/bouton.c M /branches/ma-branche-calc/LISEZMOI Fusion finale des modifications du tronc dans ma-branche-calc. ------------------------------------------------------------------------ r383 | sally | 2002-11-21 03:19:00 -0600 (jeu. 21 nov. 2002) | 2 lignes Chemins modifiés : M /branches/ma-branche-calc/bouton.c Fusion via: r390 Corrige l'erreur d'inversion graphique sur le bouton. ------------------------------------------------------------------------ r382 | sally | 2002-11-20 16:57:06 -0600 (mer. 20 nov. 2002) | 2 lignes Chemins modifiés : M /branches/ma-branche-calc/LISEZMOI Fusion via: r390 Documente mon dernier correctif dans LISEZMOI.
En forçant l'opération svn log à utiliser l'historique des fusions, nous obtenons non seulement la révision que nous avions demandé (r390), 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 bouton.c … 390 utilisateur retval = inverse_func(button, path); 390 utilisateur return retval; 390 utilisateur } …
Et bien qu'il soit vrai que vous avez propagé ces trois lignes lors de la révision 390, deux d'entre elles ont en fait été écrites par Sally auparavant, en révision 383 :
$ svn blame bouton.c -g … G 383 sally retval = inverse_func(button, path); G 383 sally return retval; 390 utilisateur } …
À présent, nous savons qui doit réellement être tenu responsable pour ces deux lignes de code !
Quand vous discutez avec un développeur Subversion, il est très possible 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, il 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 fournisseur »). 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).
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, en particulier parce que le fonctionnement est le même pour les fichiers renommés. 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 pendant que vous travaillez
sur votre branche privée, vous renommez
entier.c
en tout.c
.
Ce faisant, vous avez en fait créé un nouveau fichier dans
votre branche, qui est une copie du fichier original, et
vous avez supprimé le fichier original. Pendant ce temps, dans
le tronc, Sally a propagé des améliorations
d'entier.c
. C'est alors que vous décidez
de fusionner votre branche vers le tronc :
$ cd calc/trunk $ svn merge --reintegrate http://svn.exemple.com/depot/calc/branches/ma-branche-calc --- Fusion des différences des URLs du dépôt vers '.' : D entier.c A tout.c U .
Ceci n'a pas l'air si mal à première vue, mais ce n'est
probablement pas ce à quoi Sally ou vous vous attendiez.
L'opération de fusion a supprimé la version la plus récente
du fichier entier.c
(celle qui contenait
les dernières modifications de Sally) et a ajouté machinalement
votre nouveau fichier tout.c
, qui est une
copie de l'ancien fichier entier.c
. Le
résultat final est que la fusion de votre
« renommage » a supprimé de la dernière révision
les modifications récentes de Sally !
Il ne s'agit pas d'une vraie perte de données. Les modifications de Sally font toujours partie de l'historique du dépôt, mais le déroulement exact des événements n'est peut-être pas immédiatement évident. La morale de l'histoire est que jusqu'à ce que Subversion ne s'améliore, vous devrez faire très attention aux fusions de copies et de renommages d'une branche à une autre.
Si vous venez 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. 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. Nous
en apprendrons plus sur les procédures automatiques dans le
chapitre suivant ; voir
la section intitulée « Mise en place des procédures automatiques » et
start-commit
pour les détails.
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. Parce que cette fonctionnalité est
relativement nouvelle, un certain nombre de cas litigieux et
de comportements potentiellement inattendus risquent d'être
rencontrés.
Par exemple, mergeinfo
sera parfois
générée lors d'une simple commande svn copy
ou svn move. Parfois
mergeinfo
apparaîtra sur des fichiers dont
vous n'auriez pas imaginé qu'ils soient touchés par une
opération. Parfois mergeinfo
ne sera pas du
tout générée, contrairement à ce que vous attendiez. De plus,
la gestion de la métadonnée mergeinfo
a tout
un ensemble de taxonomies et de comportements qui lui sont
associés, tels que des mergeinfo
« explicites » par opposition à
« implicites », des révisions
« opérationnelles » par opposition à
« non-opérationnelles », des mécanismes spécifiques
d'« élision » de mergeinfo
et
même d'« héritage » de répertoires parents à
sous-répertoires.
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, au fur et à mesure que Subversion s'améliore, nous estimons que l'utilisateur moyen ne devrait pas avoir à comprendre ces concepts ; en tant que détails d'implémentation agaçants, ils finiront par disparaître à l'arrière-plan. Malgré tout ce qui vient d'être dit, 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 : http://www.collab.net/community/subversion/articles/merge-info.html.
Pour le moment, si vous voulez rester à l'écart des bogues et autres comportements inattendus des fusions automatiques, l'article de Collabnet recommande que vous vous en teniez 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 ».
Pour les branches de publication à longue durée de vie (comme décrites dans la section intitulée « Modèles courants de gestion des branches »), ne pratiquez de fusions que sur la racine de la branche, pas sur des sous-répertoires.
Ne pratiquez jamais de fusion vers des copies de travail contenant un mélange de numéros de révisions de travail, ou ayant des sous-répertoires « déportés » (comme décrit par la suite dans la section intitulée « Parcours des branches »). La cible d'une fusion doit être une copie de travail qui représente un emplacement unique à un moment unique dans le dépôt.
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 »).
Assurez-vous de toujours avoir l'accès complet en lecture à toutes vos sources de fusion, et vérifiez que votre copie de travail cible n'a pas de dossiers clairsemés.
[23] Ceci est au vrai pour Subversion 1.5 au moment où ces lignes sont écrites. Ce fonctionnement sera sans doute amélioré dans les versions futures de Subversion.
[24] À 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 mergeinfo incluraient 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 !