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:gastonMot de passe:mon_secretRévision 1 propagée.# création du dossier des étiquettesgaston$svn mkdir http:://mon_site/subversion/mes_projets/mon_projet/tags -m ""Révision 2 propagée.# création du dossier des branchesgaston$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.
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 projetgaston$cd mon_projet# Création d'un dossier "chapeau" pour notre projetgaston$svn mkdir http://mon_site/subversion/mes_projets/mon_projet/trunk/mon_projet -m ""Révision 6 propagée.# Import des sourcesgaston$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ôtrm -rf mon_projetgaston$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 resolvedgaston$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.cppgaston$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.cppA test.cppM machin.cppgaston$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.cppAjout test.cppEnvoi machin.cppTransmission des données ......Révision 10 propagée.gaston$Envoi des modifications
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_projetA la révision 13.# avec la commande "info" nous pouvons savoir où nous sommesgaston$svn infoChemin : .URL : http:://mon_site/subversion/mes_projets/mon_projet/tags/version_très_spéciale/mon_projetRacine du dépôt : http:://mon_site/subversionUUID du dépôt : ec020c13-8278-425d-81d2-3c431b2256e0Révision : 1806Type de nœud : répertoireTâche programmée : normaleAuteur de la dernière modification : gastonRévision de la dernière modification : 1805Date 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/tagsversion_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 trunkgaston$svn switch http:://mon_site/subversion/mes_projets/trunk/mon_projetActualisé à la révision 13.# suppression de l'étiquettegaston$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 branchegaston$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 dessusgaston$svn switch http:://mon_site/subversion/mes_projets/mon_projet/branches/version_expérimentaleActualisé à 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.cppgaston$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.cppA bidulegaston$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
gaston$svn propget svn:mergeinfo ./mes_projets/mon_projet/trunk/mon_projet:1806-1811gaston$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 trunkgaston$svn merge http:://mon_site/subversion/mes_projets/mon_projet/trunk# On commit toutes les modifications de notre branchegaston$svn commit -m "c'est fini !!"# On bascule sur le trunkgaston$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 IMPORTANTEgaston$svn merge --reintegrate http:://mon_site/subversion/mes_projets/mon_projet/branches/version_expérimentalegaston$Création d'une branche
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=vigaston$
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évisionsgaston$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