Prise en main de SubVersion

Subversion a depuis longtemps pris le relais du vénérable CVS. Et même s'il est aujourd'hui disponible à peu près partout, il reste encore très largement sous-utilisé, soit par une trop large transposition des habitudes liées à CVS, soit à cause des traumas que CVS a pu générer (typiquement les branches et les fusions). Ce qui suit est donc une sorte de guide pratique pour rapidement mettre en oeuvre ce formidable dépôt de versions.

Qu'est-ce qu'un gestionnaire de version ?

Un gestionnaire de version (à ne pas confondre avec une gestion de configuration ) est un outil permettant d'obtenir un certain nombre de garanties concernant le code source d'un projet :

  • La sauvegarde du code dans un dépôt.
  • Le non écrasement de modification lorsque l'on travaille à plusieurs sur le même dépôt, et à fortiori sur les mêmes sources.
  • L'intégrité du code qui est stocké sur le dépôt.
  • La non répudiation du code stocké sur le dépôt (gaston ne peut pas dire que c'est pas lui qui a fait ce vilain bogue...)
  • L'historisation de toutes les modifications dans le dépôt avec possibilité de revenir en arrière.

Aujourd'hui il existe de nombreux gestionnaires de version libre qui peuvent être séparés en deux familles : les systèmes à dépôt centralisé et ceux à dépôts décentralisés.

Les systèmes centralisés se basent, comme on se l'imagine, sur un dépôt unique, généralement hébergé sur un serveur distant, qui contient l'ensemble des sources et leur historique. Les logiciels clients spécialisés vont alors aller récupérer les sources et renvoyer au serveur les modifications à sauvegarder dans le dépôt. Dans cette famille nous avons RCS, CVS ou encore Subversion.

Dans le cas des systèmes décentralisés, le dépôt est cette fois local, sur la machine du développeur. C'est un peu comme si nous avions un serveur local par développeur. L'avantage est que le développeur peut historiser son travail sans avoir accès réseau. Les modifications faites localement pourront ensuite être remonté sur un autre dépôt distant cette fois. Dans le système décentralisé, les dépôts sont donc organisés en réseaux de dépôts. Dans cette famille nous avons GNU Arch, GIT ou encore Bazaar.

Ceci étant dit, voyons plus précisément ce qu'il est possible de faire avec Subversion qui est un projet libre initié par la société Tigris pour remplacer le vieillissant CVS. A cet ancêtre subversion apporte une multitude de nouveautés comme les greffons de stockage, les greffons permettant d'alterner les protocoles de communication, le concept de transaction (si la communication casse pendant un échange, c'est comme si l'échange n'avait pas eu lieu), un temps quasi nul pour les opérations courantes comme la création de branche ou d'étiquettes, et surtout, le versionnage de toutes les opérations, y compris les suppressions de dossiers, les déplacements et les copies.

Organisation des dossiers par projet

A partir de maintenant, je pars du principe que votre dépôt est opérationnel et accessible à travers un serveur apache à l'URL http://mon_site/subversion.

La première chose que l'on a à faire sur un dépôt subversion est de l'organiser. En effet, nous verrons cela en détail plus loin, contrairement à CVS, les étiquettes et branches sont gérées par subversion comme des copies de dossiers. Il nous faut donc une structure capable de les accueillir.

De manière générale, un dépôt peut contenir autant de sous-dossier que désiré jusqu'à arriver à celui d'un projet. Le fait qu'un dossier soit un projet n'est pas le problème de subversion, nous allons donc aussi oublier la notion de modules de CVS avec l'avantage de pouvoir confier au serveur Subversion le soin de gérer les droits de manière fine sur chacun des dossiers.

Classiquement la structure d'un projet est la suivante :

https://mon_site/subversion
  mes_projets
       mon_projet
         trunk
         branches
         tags

Structure que nous allons créer de la manière suivante :

# création de tous les dossiers jusqu'à trunk (--parents). -m permet
# de spécifier un message que nous laissons ici vide.
gaston$svn mkdir --parents http:://mon_site/subversion/mes_projets/mon_projet/trunk -m ""
Utilisateur:gaston
Mot de passe:mon_secret
Révision 1 propagée.
 
# création du dossier des étiquettes
gaston$svn mkdir http:://mon_site/subversion/mes_projets/mon_projet/tags -m ""
Révision 2 propagée.
 
# création du dossier des branches
gaston$svn mkdir http:://mon_site/subversion/mes_projets/mon_projet/branches -m ""
Révision 3 propagée.
gaston$ 
Création de la structure du projet

Ici notre projet commence donc concrètement au dossier mon_projet, avec par défaut trois sous dossier. trunk qui correspond au HEAD de CVS, c'est à dire la version courante des sources. tags qui contiendra les étiquettes. Et branches qui hébergera les différentes branches du projet.

Contrairement à CVS, l'authentification se fait à la première commande sur une URL donnée. Si vous désirez que le mot de passe saisi soit mémorisé, il faut aller modifier votre /etc/subversion/config et changer dans la section [auth] la variable store-passwords à yes.

import - Importation initiale d'un projet

la plupart du temps, l'initialisation du dépôt d'un projet commence par l'import d'un premier jeu de sources. Cela se fait aisément grâce à la commande import de subversion :

# on se place dans le dossier de projet
gaston$cd mon_projet
 
# Création d'un dossier "chapeau" pour notre projet
gaston$svn mkdir http://mon_site/subversion/mes_projets/mon_projet/trunk/mon_projet -m ""
Révision 6 propagée.
 
# Import des sources
gaston$svn import . http:://mon_site/subversion/mes_projets/mon_projet/trunk/mon_projet -m "import initial"
 
# Suppression du projet local maintenant qu'il est dans le dépôt
rm -rf mon_projet
gaston$ 
Import initial de mon_projet

Voilà, le projet est dans la boite comme il est possible de le vérifier avec un navigateur WEB à l'URL http:://mon_site/subversion/mes_projets/mon_projet/trunk/mon_projet.

Petite explication concernant ce dossier "chapeau" qui peut sembler bien inutile. Son intérêt est surtout pratique. En effet lorsque nous allons récupérer notre projet avec l'URL http:://mon_site/subversion/mes_projets/mon_projet/trunk/mon_projet, subversion va commencer par créer un dossier en prenant le dernier élément du chemin, mon_projet. Si nous avions mis nos sources directement dans trunk, il aurait créé un dossier trunk qu'il aurait fallu renommer. C'est aussi bête que cela.

checkout - Récupération d'un projet du dépôt

Maintenant que nous avons importé et détruit nos sources, il est temps de les récupérer. Comme CVS, cette récupération passe par l'opération "checkout" qui peut se contracter en co :

gaston$svn co http:://mon_site/subversion/mes_projets/mon_projet/trunk/mon_projet
...
gaston$ 
Récupération d'un projet

update - Mise à jour d'un projet

Le but de Subversion étant de permettre à plusieurs personnes de travailler en même temps sur le même projet, il est important de faire régulièrement des mises à jour de sa copie locale pour bénéficier du travail des autres :

gaston$svn up
...
gaston$ 
Mise à jour du projet

Si par hasard, deux personnes modifient le même fichier, Subversion gère la fusion des modifications (merge). S'il n'y arrive pas (deux personnes ont pu modifier la même ligne de code par exemple), il génère un conflit (Ils apparaissent lors de l'update avec la lettre G. Rien n'est maintenant possible tant que ce conflit n'est pas résolu. Subversion a alors créé 3 versions du même fichier en conflit, l'une avec l'extension .mine indique votre version, une autre avec un rxxx comme extension pour la version du dépôt et le fichier courant qui contient une tentative de fusion avec les confits marqués par des >>>>> .r206 et des <<<<< .mine. A vous de modifier le fichier courant puis de lancer lorsque c'est faut un :

gaston$svn resolve --accept working
'CHANGELOG' conflict resolved
gaston$ 
Résolution du conflit

working signifie que vous avez bossé pour résoudre le conflit, vous auriez pu aussi coller un mine-full pour dégager le travail des petits copains, mais ce ne serait pas très cool... Pour plus de précisions faites un svn help resolve.

Dans certains cas, nous voulons faire une mise à jour destructive de notre copie locale, ce qui revient à perdre toutes les modifications que nous aurions pu effectuer sur les sources :

gaston$svn revert .
'CHANGELOG' réinitialisé
gaston$ 
Résolution du conflit

commit - Envoi des modifications au serveur

Dans notre copie locale, nous allons créer de nouveaux fichiers ou dossiers. Pour qu'ils soient intégrés au dépôt, nous devons les ajouter :

gaston$svn add test.cpp -m "Mon oeuvre"
A test.cpp
gaston$ 
Ajout d'un fichier

La commande status nous permet de savoir à tout moment ce qui a bougé dans notre copie locale :

gaston$svn status
? truc.cpp
A test.cpp
M machin.cpp
gaston$ 
Qu'est ce qui a changer ?

Nous avons donc oublié d'ajouté l'indispensable truc.cpp (indicateur ?), le fichier test.cpp a lui bien été ajouté (indicateur A) et machin.cpp provenant du dépôt a été modifié.

Lorsqu'une modification est jugée bonne, nous allons envoyer ces modifications au serveur Subversion. On prendra alorssoin de toujours associer un commentaire à ce commit pour que les autres sachent quel était le but de la manoeuvre :

gaston$svn commit -m "Mon Commentaire!!"
Ajout truc.cpp
Ajout test.cpp
Envoi machin.cpp
Transmission des données ......
Révision 10 propagée.
gaston$ 
Envoi des modifications

En plaçant la variable d'environnement export VISUAL=vi, vi s'affichera à chaque fois que vous aurez oublié de spécifier le commentaire.

Marquer une version

Contrairement à CVS, Subversion ne contient aucune notion spécifique concernant les étiquettes (tag) et les branches car il n'en a simplement plus besoin. En effet, l'une des lacunes fondamentales de CVS était son incapacité à historiser les copies et déplacement de fichiers/dossiers. Subversion lui est capable de conserver l'historique sur ces deux opérations sans aucun problème. Du coup, créer une étiquette avec subversion consiste simplement... à copier le projet ailleurs. C'est là que va intervenir le dossier tags que nous avons créé plus haut. Attention, commande très compliquée, création d'une étiquette :

gaston$svn cp //http://mon_site/subversion/mes_projets/mon_projet/trunk http:://mon_site/subversion/mes_projets/mon_projet/tags/version_spéciale -m ""
Révision 11 propagée.
gaston$ 
Création d'une étiquette

Voilà, c'est tout... Pour renommer l'étiquette, il suffit d'utiliser la commande mv

gaston$svn mv http:://mon_site/subversion/mes_projets/mon_projet/tags/version_spéciale http:://mon_site/subversion/mes_projets/mon_projet/tags/version_très_spéciale -m ""
Révision 12.
gaston$ 
Renommage d'une étiquette

Ensuite pour travailler sur cette version "spéciale", il suffit de faire de basculer sur la nouvelle URL :

gaston$svn switch http:://mon_site/subversion/mes_projets/mon_projet/tags/version_très_spéciale/mon_projet
A la révision 13.
 
# avec la commande "info" nous pouvons savoir où nous sommes
gaston$svn info
Chemin : .
URL : http:://mon_site/subversion/mes_projets/mon_projet/tags/version_très_spéciale/mon_projet
Racine du dépôt : http:://mon_site/subversion
UUID du dépôt : ec020c13-8278-425d-81d2-3c431b2256e0
Révision : 1806
Type de nœud : répertoire
Tâche programmée : normale
Auteur de la dernière modification : gaston
Révision de la dernière modification : 1805
Date de la dernière modification: 2009-02-09 00:15:03 +0100 (lun. 09 févr. 2009)
gaston$ 
Basculement de la copie locale sur l'étiquette

Pour obtenir la liste des étiquettes :

gaston$svn ls http:://mon_site/subversion/mes_projets/mon_projet/tags
version_très_spéciale/
gaston$ 
Liste des étiquettes

Enfin, pour détruire notre étiquette, il suffit d'utiliser la commande rm

# on rebascule sur le trunk
gaston$svn switch http:://mon_site/subversion/mes_projets/trunk/mon_projet
Actualisé à la révision 13.
 
# suppression de l'étiquette
gaston$svn rm http:://mon_site/subversion/mes_projets/mon_projet/tags/version_très_spéciale -m "Elle ne sert plus à rien"
Révision 14.
gaston$ 
Suppression de l'étiquette

merge - Fusion de branches

Vous l'aurez maintenant compris, Subversion fonctionne comme un véritable système de fichier qui aurait la particularité d'être historisé. Les étiquettes ne font donc qu'utiliser cette caractéristiques et se manipulent comme des dossiers sous UNIX à coup de mkdir, mv, rm ou ls. Et pour les branches, c'est très exactement la même chose :

# création de la branche
gaston$svn cp http:://mon_site/subversion/mes_projets/mon_projet/trunk http:://mon_site/subversion/mes_projets/mon_projet/branches/version_expérimentale -m "Attention, ici je casse tout !!!"
Révision 15 propagée.
 
# On bascule dessus
gaston$svn switch http:://mon_site/subversion/mes_projets/mon_projet/branches/version_expérimentale
Actualisé à la révision 15.
gaston$ 
Création d'une branche

Finalement ce n'est qu'une vue de l'esprit qui sépare le trunk, des étiquettes et des branches avec Subversion. Il est ainsi possible de transformer une étiquette en branche et vice-versa par simple application de la commande mv. Du coup, la seule chose qui nous reste de sportif est la fameuse "fusion de branches".

Le cas classique est que nous travaillons tranquille sur notre branche et qu'il nous est nécessaire de récupérer ce que les petits camarades ont envoyé dans le trunk.

gaston$svn merge http:://mon_site/subversion/mes_projets/mon_projet/trunk
--- Fusion de r1806 à r1808 dans '.':
A machin.cpp
gaston$ 
Création d'une branche

Là, le vieux routard CVS se dit qu'il y a de la magie dans l'air. Lui qui était habitué à noter consciencieusement la position à laquelle il avait fait sa branche pour l'indiquer à la commande merge sous peine de se voir fusionner HEAD depuis le big-bang, là rien n'est demandé... et ça marche.

Cette magie là est cependant très récente, depuis la version 1.5 de subversion pour être exact. Date à laquelle cet merveilleux outil a pris l'habitude de se baser sur la révision de création de la branche pour ce type de fusion. Mais sachant cela, on se dit que la prochaine fois, ça va forcement merder. Alors on attends que les copains bossent encore un peu et on recommence :

gaston$svn merge http:://mon_site/subversion/mes_projets/mon_projet/trunk
--- Fusion de r1809 à r1811 dans '.':
U machin.cpp
A bidule
gaston$ 
Création d'une branche

Là le vieux briscard se dit "trop, fort!". Non seulement il avait repéré la révision de création de la branche mais on voit un peu comment il a pu faire, mais là.. mystère..

Bon, j'arrête le suspens à trois sesterces, il n'y a aucune magie là dedans Smiling le client SVN est juste suffisament malin aujourd'hui pour stocker dans une variable locale cette fameuse dernière révision de fusion

gaston$svn propget svn:mergeinfo .
/mes_projets/mon_projet/trunk/mon_projet:1806-1811
gaston$ 
Création d'une branche

Ce n'est donc pas magique mais c'est tout de même bien cool. Et après avoir bien bossé, le temps arrive où il faut tout réintégrer dans le trunk. Là aussi, c'est divinement simple

# On fait une derniere synchronisation avec trunk
gaston$svn merge http:://mon_site/subversion/mes_projets/mon_projet/trunk
 
# On commit toutes les modifications de notre branche
gaston$svn commit -m "c'est fini !!"
 
# On bascule sur le trunk
gaston$svn switch http:://mon_site/subversion/mes_projets/mon_projet/trunk
 
# réintégration des modifications de la branche vers le trunk. Notez
# l'option --reintegrate TRES IMPORTANTE
gaston$svn merge --reintegrate http:://mon_site/subversion/mes_projets/mon_projet/branches/version_expérimentale
gaston$ 
Création d'une branche

Si lors de la réintégration vous obtenez l'erreur svn: Récupération des 'mergeinfo' (informations de fusions) non supportée par 'http:://mon_site/subversion', il y a de forte chance que votre dépôt ne soit pas à jour sur le serveur. Demandez à votre administrateur de lancer la commande svnadmin update /svnroot.

Voilà, c'est fini. Ce qui était un petit enfer sous CVS est devenu une véritable promenade de santé avec Subversion. Plus d'excuses donc pour éviter à tout prix de créer des branches.

log - Liste des modifications sur un fichier

Là nous touchons à l'intérêt relativement incompris des développeurs de mettre des COMMENTAIRES dans les commit. Pour en obtenir la liste depuis une certaine date sur un fichier donnée, nous avons :

svn log .
root# 
Liste de commentaires

Modification des méta-données

Une des nouveauté de subversion que nous avons déjà un peu découvert avec merge, est la possibilité de stocker des méta données au niveau des répertoires. Cela peut servir à renseigner la liste des dossiers/fichiers invisibles, à définir la liste des références externes, etc...

éditeur externe

Pour éditer les propriétés, nous allons avoir besoin d'un éditeur externe. Cela peut-être n'importe quelle application capable d'éditer du texte de la manière qui vous convient :

gaston$export VISUAL=vi
gaston$ 

Les commandes de gestion des propriétés

# donne la liste des propriétés dans le dossier courant
svn proplist .

# Affiche le contenu d'une propriété du dossier courant
svn propget ma:propriete .

# Edition du contenu d'une propriété du dossier courant
svn propedit ma:propriete .

# Changer la valeur sans passer par l'éditeur (moins pratique)
svn propset ma:propriete "Sa valeur" .

Déclarer un dossier comme ignoré

La propriété responsable du fait d'ignorer un fichier, un dossier, un ensemble, etc, est svn:ignore. Il suffit donc d'éditer cette propriété pour ajouter une règle par ligne contenant le nom d'un fichier, d'un dossier, ou un "pattern" du type *.bak.

Charger des sous-projets externes

Une des possibilités très utile de subversion est de pouvoir "monter" dans l'arborescence d'un projet des branches (au sens système de fichier du terme) provenant d'autre dépôts. Cela se fait très simplement par la commande d'édition de propriétés. Par exemple, imaginons que dans un dossier mon_projet, nous cherchions à greffer le projet module_ami.

Pour ajouter une référence, il faut éditeur la propriété svn:externals du dossier où l'on désire établir la greffe. Ensuite nous ajoutons la référence comme suit :

module_ami https://site.ami.org/subversion/module_ami/trunk/module_ami

Il peut y avoir ici autant de ligne de référence que désirées. Une fois la propriété fois sauvé, il suffit de faire une mise à jour du dossier mon_projet (svn co) pour mettre à jour ou, si le dossier externe n'existe pas encore, checkouter le dossier greffé automatiquement.

De manière général, une mise à jour (svn up) à la racine, entraîne une mise à jour des dossiers externes. En revanche, les commits (svn co), eux, ne franchissent pas les "barrières" des externals. Pour commiter une version d'un sous-dossier externe, il faut explicitement commiter celui si.

Changer l'url d'un espace de travail

Il arrive parfois que l'URL de base d'un dépôt doive être changée par exemple lorsque le dépôt change de serveur. Dans ce cas, pas la peine de re-checkouter les sources, il suffit d'utiliser la commande switch que nous avons vu plus haut, mais cette fois avec l'option relocate

svn switch --relocate http:://mon_site/subversion http:://nouveau.site/nouveau_chemin
</code>
 
 
Notez bien que seule l'url de base est utilisée, par le nom du projet. Ceci fait, vous pouvez à nouveau commiter vos modifications.
 
 
<h3>Annuler le dernier commit</h3>
Grosse cata, vous avez commité des horreurs et pour votre malheur, la règle absolue de subversion est de ne rien effacer. Pas de panique, voici la marche à suivre.
Imaginons que ces erreurs aient été introduites dans la révision 666 et que vous vouliez revenir à la révision 665. L'astuce est de fusionner la revision 665 sur la 666 et de commiter l'ensemble en 667 qui sera donc un nouveau 666. Pour cela, placez-vous dans votre dossier à restaurer :
<traces type="sh" caption="récupération d'erreur">
gaston$cd mon_projet
 
# au cas où. Personnellement je fait un même un rm -rf * dans ce dossier, puis un svn up
# être certain d'avoir le dernière révision.
gaston$svn up
 
# On merge avec les deux révisions
gaston$svn merge -r 666:667 .
 
# On commit la revision "patch"
gaston$svn commit -m "annulation des horreurs de la révision 666"
gaston$ 
relocalisation de l'url d'une copie local

Vus : 194
Publié par Artisan Numérique : 100