Artisan Numérique

/développement/gestionnaire de version/sgv/subversion/ 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.
gastonsvn 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
gastonsvn mkdir http:://mon_site/subversion/mes_projets/mon_projet/tags -m ""
Révision 2 propagée.

création du dossier des branches
gastonsvn mkdir http:://mon_site/subversion/mes_projets/mon_projet/branches -m ""
Révision 3 propagée.
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
gastoncd mon_projet

Création d'un dossier "chapeau" pour notre projet
gastonsvn mkdir http://mon_site/subversion/mes_projets/mon_projet/trunk/mon_projet -m ""
Révision 6 propagée.

Import des sources
gastonsvn 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
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 :

gastonsvn co http:://mon_site/subversion/mes_projets/mon_projet/trunk/mon_projet
...
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 :

gastonsvn up
...
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 :

gastonsvn resolve --accept working
'CHANGELOG&039; conflict resolved
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 :

gastonsvn revert .
'CHANGELOG&039; réinitialisé
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 :

gastonsvn add test.cpp -m "Mon oeuvre"
A     test.cpp
Ajout d'un fichier

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

gastonsvn status
?     truc.cpp
A     test.cpp
M     machin.cpp
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 :

gastonsvn commit -m "Mon Commentaire!!"
Ajout truc.cpp
Ajout test.cpp
Envoi machin.cpp
Transmission des données ......
Révision 10 propagée.
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 :

gastonsvn 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.
Création d'une étiquette

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

Révision 12.
//mon_site/subversion/mes_projets/mon_projet/tags/version_spéciale http

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

gastonsvn 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
gastonsvn 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)
Basculement de la copie locale sur l'étiquette

Pour obtenir la liste des étiquettes :

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

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

on rebascule sur le trunk
gastonsvn switch http:://mon_site/subversion/mes_projets/trunk/mon_projet
Actualisé à la révision 13.

suppression de l'étiquette
gastonsvn rm http:://mon_site/subversion/mes_projets/mon_projet/tags/version_très_spéciale -m "Elle ne sert plus à rien"
Révision 14.
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
Révision 15 propagée.

On bascule dessus
gastonsvn switch http:://mon_site/subversion/mes_projets/mon_projet/branches/version_expérimentale
Actualisé à la révision 15.
//mon_site/subversion/mes_projets/mon_projet/trunk http

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.

gastonsvn merge http:://mon_site/subversion/mes_projets/mon_projet/trunk
--- Fusion de r1806 à r1808 dans '.':
A    machin.cpp
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 :

gastonsvn merge http:://mon_site/subversion/mes_projets/mon_projet/trunk
--- Fusion de r1809 à r1811 dans '.':
U    machin.cpp
A    bidule
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 :) le client SVN est juste suffisament malin aujourd'hui pour stocker dans une variable locale cette fameuse dernière révision de fusion

gastonsvn propget svn:mergeinfo .
/mes_projets/mon_projet/trunk/mon_projet:1806-1811
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
gastonsvn merge http:://mon_site/subversion/mes_projets/mon_projet/trunk

On commit toutes les modifications de notre branche
gastonsvn commit -m "c'est fini !!"

On bascule sur le trunk
gastonsvn 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
gastonsvn merge --reintegrate http:://mon_site/subversion/mes_projets/mon_projet/branches/version_expérimentale
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 .
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 :

gastonexport VISUAL=vi

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:as 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 as. 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


//mon_site/subversion http

Notez bien que seule l'url de base est utilisée, par le nom du projet. Ceci fait, vous pouvez à nouveau commiter vos modifications.

Annuler le dernier commit

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 :

gastoncd 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.
gastonsvn up

On merge avec les deux révisions
gastonsvn merge -r 666:667 .

On commit la revision "patch"
gastonsvn commit -m "annulation des horreurs de la révision 666"
récupération d'erreur