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.
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.
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 | |
---|---|
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.
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 :
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. 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.
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 :
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
[41].
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.
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.
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 !
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 | |
---|---|
L'option |
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.
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 ».
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 .