Artisan Numérique

/développement/gestionnaire de version/sgv/cvs/ Prendre en main CVS

CVS (pour Concurrent Versions System) est le gestionnaire de version le plus utilisé au monde. Comme tous logiciel de ce type (mise à par le misérable sourceSafe), il permet à tous les membres d'une équipe intervenant sur un même projet de travailler, ensemble, sur les mêmes sources. CVS est massivement utilisé dans le monde du libre en particulier et de l'openSource en général. Et même s'il vieilli et qu'il est près d'être remplacer par le jeune subversion, CVS reste un outil à connaître...

Un gestionnaire de version garanti : le travail à plusieurs sur les mêmes sources (concurrence), l'intégrité du code et l'historisation de toutes les modifications faites. Il existe de nombreux gestionnaires de version comme RCS, l'ancêtre dont hérite CVS, Subversion, le successeur de CVS (et que je conseille chaudement). Les développeurs du noyau Linux utilisent Git (écrit par Torvald). Il existe aussi des versions non libre de ce type de logiciel comme clearCase, BitKeeper (anciennement utilisé pour le noyau Linux). J'aurais pu citer SourceSafe mais sans méchanceté aucune ce logiciel est une calamité. J'ai vu avec ce "truc" des dépôts détruits pour de bête questions de coupure réseau (chose impardonnable pour un logiciel de ce type), les transactions ne sont pas gérées (mais sur CVS non plus, il faut prendre subversion pour cela), le merge plus qu'optionnel (nous verrons plus loin à quoi cela correspond) et l'on est quasi "obligé" de mettre de ridicules verrous sur les fichiers en cours de modification (donnant lieu à d'interminables "bon, t'as fini sur le fichier ?", "Non, pas encore..."). En bref, il existe suffisamment de solution professionnelles et autrement plus performantes, libre ou pas, pour ne pas avoir à s'acheter des ennuis avec ce "bricolage à la va-vite".

Syntaxe générale

CVS est un logiciel client/serveur. C'est à dire que le nous pouvons lancer la commande cvs en tant que démon qui va gérer le dépôt (ou repository) mais aussi la lancer, et c'est ce qui nous intéresse ici, en tant que client d'un serveur CVS.

Techniquement, c'est le serveur CVS qui contient physiquement les données, le client ne travaille que sur une copie local ramenée du serveur.

La syntaxe générale de CVS client est la suivante :

cvs [options_générales] <sous_commande> [options] [arguments]

Les "options générales" s'appliquent à n'importe quelles sous_commande. Les plus utilisées sont :

-d URL_CVS_REPOSITORY

Ce chemin est de la forme : :protocole:[login@]serveur[:port]/chemin où protocole est générale pserver (un serveur CVS donc ;-), le login est optionnel, le port aussi (par défaut 2401).

Ce qui nous donne par exemple :pserver:gaston@mon_serveur.com:2502/mes_projets.

L'option -d peut être sous-entendue si URL_CVS_REPOSITORY est stocké dans la variable CVSROOT.

-z N Fixe le niveau de compression utilisé lors les échanges réseau, de 1 à 9. Généralement -z 3 est conseillé sur les serveur CVS publiques sur internet pour un bon rapport compression/charge générée. -n Permet de tester les commandes CVS sans qu'aucune modification ne soit réellement effectuées. -q Évite à CVS de nous assommer avec du bavardage innutil.

login et checkout - Première connexion et récupération de projet

Chaque personne travaillant sur un projet hébergé par CVS doit d'abord d'authentifier sur le serveur CVS (login) puis ramener sa propre copie locale (checkout) :

cvs -d :pserver:gaston@mon_serveur.com:2502/mes_projets login
# saisie du mot de passe ici

cvs -d :pserver:gaston@mon_serveur.com:2502/mes_projets checkout mon_projet

A noter que la syntaxe peut être simplifiée par l'utilisation de la variable CVSROOT. Ceci dit ce sont les deux seules fois où vous aurez à utiliser le -d, à vous de voir :

CVSROOT=":pserver:gaston@mon_serveur.com:2502/mes_projets"
cvs login
cvs checkout mon_projet

Enfin, comme beaucoup de commandes CVS, checkout peut être remplacé par sa forme courte : co, ce qui nous donne :

# (...)
cvs co mon_projet

update - Mise à jour d'un projet

Le but de CVS é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 :

cd mon_projet
cvs -q up -Pd

Si par hasard, deux personnes modifient le même fichier, CVS 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), CVS génère un conflit (Ils apparaissent lors de l'update avec la lettre C. Rien n'est maintenant possible tant que ce conflit n'est pas résolu.

Ici, nous utilisons la forme courte de la sous-commande update (up). L'option générale -q permet à CVS de n'afficher que les informations utiles (ce qui a changé). L'option -P demande à CVS de supprimer du dépôt local les dossiers vides, et -d de créer les nouveaux dossiers.

commit - Envoi des modifications au serveur

Lorsqu'une modification est jugée bonne, le développeur va envoyer ses modifications au serveur CVS. On prendra soin de toujours associer un commentaire à ce commit pour que les autres sachent quel était le but de la manoeuvre :

cd mon_projet
cvs -q commit -m "Mon Commentaire!!"

tag - Marquer une configuration de version

Création d'un tag

Un tag représente le coeur d'un logiciel de gestion de version. Chaque fichier contenu a en effet déjà un numéro de version, initialisé à 1.0 lors de la création du fichier, il est incrémenté (1.1, 1.2, etc..) à chaque commit.

Lorsque l'on crée un tag, CVS prends la liste de tous les fichiers auquel le tag s'applique, et pour chaque fichier, note la version courante. Le tag est donc une liste de fichiers associé à leur version à un moment T donné. Vous pouvez l'imaginer comme une photo de l'ensemble des sources. Ainsi, si les versions changent, il est toujours possible à CVS de remettre la copie locale dans l'état exact du tag.

par exemple, lors d'une livraison, il est une bonne pratique qui consiste à tagger le dépôt avec le numéro de la version. Ainsi, il sera possible dans l'avenir de demander à CVS de reconstituer une copie locale identique avec cette version, quelques que soient les modifications qui ai pu être faites après.

cd mon_projet
cvs -q tag -c "version_1_0"

L'option -c permet de bloquer le processus de marquage si CVS détecte un fichier non encore commité. C'est une sécurité importante. A noter que les . et les espaces, sont remplacées par des _ car CVS n'en veux pas...

Récupérer une version pour un tag donné

Pour récupérer une copie locale associée à un tag, il suffit de faire un update en indiquant le nom du tag :

cvs -q up -r "version_1_0"

Revenir à la version courante (sans tags)

Pour revenir à la version courante (sans tag) :

cvs -q up -A

Supprimer un tag

Pour supprimer un tag (attention !!!)

cvs -q tag -d "version_1_0"

Copier/renommer un tag

Pour copier un tag, il suffit de taper :

cvs -q tag -r "ancien_tag" "nouveau_tag"

Du coup pour le renommer, c'est la même chose en détruisant ensuite l'ancien tag :

cvs -q tag -r "ancien_tag" "nouveau_tag"
cvs -q tag -d "ancien_tag"

Pour obtenir la liste des tags, c'est étrangement un peu compliqué. Le plus simple est de choisir un fichier qui a toujours été là et de demander son statut. A notre que l'on peut aussi utiliser cette commande pour savoir sur quel tag nous travaillons à un moment donné :

cvs status -v mon_projet/mon_fichier

Utilisation des branches

Une branche est un des aspect les plus intéressant de CVS, mais aussi un des plus compliqué à saisir. Déjà, une branche, comme un tag, marque une configuration des sources à un moment T donné. La différence avec le Tag est que toute modifications faites dans une branche ne se voient pas d'une autre branche. En réalité, vous avez déjà une branche dans tout dépôt CVS, nommé HEAD. C'est dans cette branche que nous avons travaillé jusqu'à maintenant. Voyons pourquoi en créer d'autres...

L'utilisation des branches peut se comprendre dans le cycle de vie classique d'un développement. Dans les premiers temps tout l'équipe développe dans HEAD et ce jusqu'à ce que la première version soit finalisée. Fort satisfait (ou soulagé), le "chef de projet" (titre ronflant signifiant "humain à tout faire") va proprement poser un tag v1 dans CVS.

cd mon_projet
cvs -q tag "v1"

Cette version est alors livrée au client qu'il va le transmettre à ses équipes pour recette (les testeurs)... L'équipe de développement quant à elle continue ce qu'elle aime le plus, à savoir bosser sur les "nouveautés qu'il faut tout casser parce que c'était tout pourri avant" de la future v2. Tout se fait dans la branche HEAD, on ne touche donc à rien.

Et un matin, alors que votre café fume encore dans sa tasse, le client téléphone, généralement hystérique (il faut le comprendre, il peut se le permettre ;-), car il a détecté une ignominieuse anomalie qualifiez aussitôt de "bloquante" ! Petite aparté, c'est à se demander pourquoi on se casse les pied avec des anomalies "cosmétique", "mineurs" ou "évolutives" (les plus rares !) vu qu'elles sont finalement systématiquement bloquantes... Bref, vu que la v1 est déjà en production (Ah oui, là vous vous comprenez qu'une part de l'hystérie de votre client tient à ce qu'il a tout passé en production en oubliant la phase recette...), il n'est pas possible de corriger dans HEAD. En effet cela impliquerait d'envoyer en production du code non stable (et c'est peu dire) appartenant à la futur v2...

La solution est donc de créer une branche v1.0 qui "prends racine" sur le tag v1 (que l'on se bénit d'avoir créer) et de consacrer à partir de maintenant cette nouvelle branche aux corrections d'anomalies de la v1. Il faut aussi à ce stade choisir le développeur qui va râler pendant la semaine à venir :

cd mon_projet
cvs -q tag -b -r "v1" "v1_0"

L'option -b indique une création de branche (et non plus un tag) et -r indique à la commande tag de pendre v1 comme racine de la branche. L'option -q est juste là parce que vous vous êtes assez pris d'insultes pour la journée.

Il suffit après cela de demander à CVS de faire "régresser" la copie locale vers la branche v1.0 et d'y (faire) faire les correction et les commit (avec les commentaires) :

cvs -q up -r "v1_0"
# (...) mes modifications correctives
cvs commit -m "Mes corrections pour la v1.1"

Une fois les correction validées et envoyées au client (apaisé). Il suffit de mettre un tag v1_1 sur la branche v1_0.

cvs -q tag "v1.1"

Le travail terminé, le développeur va pouvoir revenir à sa future version 2 en utilisant update pour rétablir la version courante (option -A) :

cvs -q up -A

Mais comme vous êtres quelqu'un de prévoyant, vous prenez vite conscience que l'anomalie trouvée dans la 1.0, corrigée dans la 1.1, existe encore très certainement dans la future 2. Et comme vous n'avez aucune envie d'éplucher les historiques CVS à la dernière minute lorsque la v2 sortira, vous allez fusionner les modifications dés maintenant (entre la 1.0 et la 1.1) dans la copie locale qui se trouve être la version courante de développement (cf commande juste au dessus) :

cvs -q up -j "v1.0" -j "v1.1"
# commit de la fusion (qui pour l'instant n'est que dans la copie locale)
cvs commit -m "Fusion des deltas v1.0 - v1.1"

Voilà qui est fait, ne reste plus qu'à reproduire l'opération (hors la création de branche faite une fois pour toute) à chaque nouvelle anomalie... La branche en elle-même vivra tant que la v1 restera en production, ensuite il sera possible (mais est-ce souhaitable) de la supprimer.

Il arrive que certaines fois les nouveaux fichiers crées dans la branche ne soit pas créés correctement lors du merge... En revanche, lorsqu'ils sont créés dans un tag de branche, cela semble marcher. La seule solution que j'ai trouvé est de copier à la main le nouveau fichier (cool !) et de faire un cvs add dessus. Si quelqu'un a plus intelligent je suis preneur.

history - La liste des fichiers modifiés

On utilise ici la sous-commande diff et pipé dans un peu de barbarie pour en extraire la liste des fichiers modifiés :

Depuis une certaine date (exprimée au format anglo-saxon AAAA-MM-JJ), cela donne :

cvs diff -N -D "2006-10-01" 2> /dev/null | grep "Index:" | cut -d":" -f2 | awk '{ print substr($0,2,255) }'

Entre deux tag/branch, cela donne :

cvs diff -N -r tag1 -r tag2 2> /dev/null | grep "Index:" | cut -d":" -f2 | awk '{ print substr($0,2,255) }'

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. Cela permet pourtant tellement plus de clarté... Remarquez c'est peut-être aussi pour cela qu'on a inventé le C... Toujours est t-il que pour obtenir la liste de ces commentaires depuis une certaine date sur un fichier donnée, nous avons :

cvs log -d ">2006-10-01" nom_fichier

La même chose entre deux tags (inclus, mettre :: pour que tag2 soit exclus) :

cvs log -rtag1:tag2 nom_fichier

Revenir en arrière

Cas classique, vous avez commité une version (ex. 1.5) et vous le regrettez déjà. La solution est de détruire la version 1.5 (et celle qui éventuellement suivent) par la commande :

cvs admin -o1.5: monFichier