Introduction à RCS (Revision Control System) Frédéric Couchet
APRIL
v 1.1, 23 septembre 1997 Ce document présente l'utilisation des outils RCS pour le contrôle de version. Introduction

Un problème majeur dans le développement et la maintenance des programmes est le contrôle de version. C'est-à-dire la conservation bien organisée de tous les changements effectués sur les programmes développés. Au final, un programme est constitué de différentes versions. L'un des programmes standard pour faire du contrôle de version est GNU's RCS, qui signifie Revision Control System. RCS est un ensemble de commandes effectuant ce travail. Il automatise le stockage, la récupération, la tenue d'un journal et l'identification des différentes révisions de fichiers de différents types (texte de n'importe quel format, et même binaire si les outils associés, tel que diff, peuvent gérer ce type de fichiers). Cependant, la principale utilisation d'un système de contrôle de version s'inscrit dans le cadre de fichiers sources ou de documentations. RCS ne conserve pas une copie entière de chaque nouvelle version. Il stocke des deltas, c'est à dire les différences entre les révisions successives. Pour cela, les changements au fichier filename sont conservés dans le fichier filename,v. Parmi les principales caractéristiques de RCS, on peut noter qu'il permet d'extraire une version antérieure des fichiers, de conserver des journaux des modifications apportées, de conserver l'identification des personnes ayant fait les modifications. RCS permet également de comparer deux versions et fournit un mécanisme pour fusionner deux branches de développement différentes d'un fichier. RCS permet également le verrouillage (lock) d'un fichier, de telle façon qu'une seule personne puisse apporter des changements (les autres personnes peuvent toujours utiliser le fichier, par exemple pour le compiler). RCS a été principalement développé par Walter F. Tichy et Paul Eggert. La version actuelle est la 5.7, elle est disponible sur ou sur . Nous verrons, sans toutefois entrer dans les détails, le principe général du contrôle de version, et les principales commandes constituant RCS. Notons qu'il existe d'autres outils de ce type.

Principe général

La fonction principale de RCS est de gérer des groupes de révisions. On peut définir une révision comme un ensemble de textes appelés révisions, qui évoluent les uns après les autres. Une nouvelle révision est créée en éditant une révision actuelle. La révision initiale est la racine de l'arbre des révisions. En effet, RCS organise les révisions en un arbre ancestral. La révision initiale de l'arbre (la racine) est normalement numérotée 1.1 et les révisions successives sont numérotées 1.2, 1.3, 1.4 ... Le premier champ du numéro de révision est appelé le release number, et le second champ est appelé le level number. RCS assigne un nouveau numéro de révision en incrémentant le level number de la révision précédente. Le release number doit être incrémenté explicitement (uniquement dans le cas d'une transition majeure dans le développement, par exemple lorsqu'une nouvelle release du produit a été complétée).

Commandes de base

L'interface de RCS est relativement simple et seulement deux commandes sont suffisantes dans la plupart des cas : ci et co. Nous supposons l'existence d'un fichier nommé foo.c. Le source du fichier est le suivant : #include "<stdio.h>" main() { printf("Hello world\n"); } Nous allons créer un nouveau groupe de révisions avec foo.c comme révision initiale (1.1). Le groupe sera stocké dans le fichier nommé foo.c,v. Par défaut, le fichier foo.c sera effacé. Pour cela, nous utilisons la commande de check-in: ci foo.c Cette commande demande également une description pour le groupe. foo.c,v <-- foo.c enter description, terminated with single '.' or end of file: NOTE: This is NOT the log message! >> Par exemple, nous tapons : >> programme hello world. >> . Le texte décrit ce que le programme fait, et ci rappelle que ce n'est pas une entrée dans le journal. Les commandes ci ultérieures demanderont une entrée du journal, laquelle résumera les modifications apportées. Ces messages doivent être brefs, décrivant les changements apportés. Pour extraire la dernière révision dans un groupe, on utilise la commande de check-out : co foo.c Le fichier foo.c est recréé, mais en lecture seule. On peut utiliser le fichier pour le compiler par exemple. En effet, pour installer un logiciel, on extrait toutes les sources en lecture seule, on les compile et on efface les sources. Pour pouvoir modifier le fichier foo.c, nous devons utiliser l'option -l : co -l foo.c Cela permet d'extraire le fichier foo.c mais en posant un verrou sur le fichier. Cela signifie que vous, et vous seul, avez la permission de faire un check-in sur une nouvelle révision du fichier (ce qui est nécessaire dans le cas où plusieurs personnes travaillent sur un même groupe de révisions). Le fichier foo.c en alors en écriture pour vous. Le système peut être configuré avec la caractéristique du 'strict locking'. Tous les fichiers RCS sont initialisés de telle sorte que les opérations de check-in nécessitent un verrou sur la révision précédente. Pour désactiver le strict locking sur un fichier individuel, utilisez la commande : rcs -U foo.c Pour activer le strict locking sur un fichier individuel, utilisez la commande : rcs -L foo.c La notion de locking est abordée dans un chapitre ultérieur. Nous pouvons alors éditer le fichier foo.c, et une fois les modifications effectuées, en faire une nouvelle révision par la commande: ci foo.c La commande nous demande alors le message du journal : foo.c,v <-- foo.c new revision: 1.2; previous revision: 1.1 enter log message, terminated with single '.' or end of file: >> Affichage 10 fois du message >> . La nouvelle révision est la 1.2. Si vous obtenez lors du ci, le message d'erreur suivant (en supposant que le login de l'utilisateur soit mad): ci error: no lock set by mad c'est que vous avez oublié de verrouiller la révision lors du co. On peut noter, que pour des raisons pratiques, tous les fichiers RCS (*,v) peuvent être déplacés dans un sous-répertoire nommé RCS. Donc, prenez l'habitude de créer ce répertoire avant de commencer à créer vos révisions. RCS l'utilisera automatiquement. Si vous ne désirez pas effacer le fichier de travail lors du check-in, vous devez utiliser soit l'option -l, qui entraîne un locking, soit l'option -u qui n'entraîne pas de locking (ces options entraînent un appel à la commande co): ci -l foo.c Les commandes ci et co sont les deux commandes de bases de RCS.

Identification automatique

Avec RCS, vous pouvez marquer vos fichiers sources et objets par des chaines d'identification spéciales. RCS utilise pour ça la substitution de mots-clés. Par exemple, pour obtenir l'identification, placez le mot-clé suivant dans le texte d'une révision (par exemple dans un commentaire en C): $Id$. La commande co remplacera ce mot-clé avec une chaine de la forme : $Id filename revisionnumber date time author state locker$. Cette chaine décrit le nom du fichier, la révision, la date et l'heure du check-out, l'auteur, l'état (par exemple, Exp pour expérimental) et celui qui verrouille le fichier. Cette chaine sera mise à jour automatiquement à chaque fois. Pour obtenir la même chose dans les fichiers objets, utilisez une chaine littérale de caractères, par exemple en C : static char rcsid[]="$Id$"; Cette possibilité est très importante pour la maintenance des programmes. Ainsi, la commande ident extrait ces mots-clés à partir de n'importe quel fichier (donc les fichiers objets). Cela permet de retrouver quelles révisions de quels modules ont été utilisées pour construire le programme. Il existe d'autres mots-clés d'identification ($Author$, $Date$, $Header$, $Locker$ ,$Name$ ,$RCSfile$, $Revision$, $Source$, $State$, voir la page de manuel de co). Un mot-clé important est $Log$. Son fonctionnement est un peu différent, il permet d'accumuler les messages du journal saisis lors du processus de check-in. On utilise ce mot-clé dans un commentaire en début de fichier source. Ainsi le fichier source est auto-documenté. Prenons un exemple concret, voilà le début du fichier foo.c lors de sa création : /* * $Id$ * * $Log$ * */ static char rcsid[] = "$Id$"; Lorsque nous extrayons la révision 1.2, nous obtenons en début du fichier foo.c : /* * $Id: foo.c,v 1.2 1997/08/23 20:33:36 mad Exp mad $ * * $Log: foo.c,v $ * Revision 1.2 1997/08/23 20:33:36 mad * Affichage 10 fois du message * * Revision 1.1 1997/08/23 20:33:05 mad * Initial revision * * */ static char rcsid[] = "$Id: foo.c,v 1.2 1997/08/23 20:33:36 mad Exp mad $"; Si nous compilons foo.c pour obtenir a.out, et que nous utilisons la commande : ident a.out nous obtenons : a.out: $Id: foo.c,v 1.2 1997/08/23 20:33:36 mad Exp $ Ce qui nous permet de savoir que le fichier a.out a été obtenu à partir de la révision 1.2 de foo.c.

Verrouillage (locking)

Le problème posé est le suivant : au moins deux personnes veulent déposer des modifications d'une même révision. Si nous supposons deux programmeurs apportant des modifications à une même révision (par exemple la 2.5). Le programmeur A fait un ci sur sa révision avant le programmeur B. Le programmeur B n'a pas vu les modifications de A, donc l'effet est que les changements de A sont couverts par les modifications de B. RCS prévient ce conflit par le verrouillage. Lorsque quelqu'un veut éditer une révision pour la modifier, la révision doit être extraite et verrouillée, en utilisant l'option -l de co. La prochaine opération de check-in effacera le verrou. Au plus un programmeur à la fois peut verrouiller une révision, et seulement ce programmeur peut la déverrouiller. Par exemple, supposons que le programmeur mad a mis un verrou par la commande co -l foo.c. Maintenant, si le programmeur gunsman veut extraire la révision pour la modifier par la même commande, il aura le message d'erreur suivant : foo.c,v --> foo.c co: foo.c,v: Revision 1.2 is already locked by mad. De plus, chaque fichier RCS possède également une liste d'accès, qui spécifie quels utilisateurs peuvent effectuer des opérations de mise à jour.

L'arbre de révision

Nous avons vu que les numéros de révisions démarraient à 1.1 et, qu'ensuite, la commande ci incrémentait uniquement le level number. Pour incrémenter le release number il faut le faire explicitement avec l'option -r de ci, par exemple : ci -r2.1 foo.c assigne le numéro 2.1 à la nouvelle révision. Un nouveau check-in donnera ensuite 2.2. Un arbre de révision est constitué normalement d'une branche unique appelée tronc. On peut néanmoins créer des branches latérales. Par exemple, supposons l'arbre de révision suivant : 1.1 -> 1.2 -> 1.3 -> 1.4 -> 2.1 -> 2.2 -> 2.3 ... Cet arbre a 7 révisions groupées en 2 releases. La release 1.4 est en activité sur un site client, tandis que la release 2 est en développement. Imaginons maintenant que le client demande une correction dans la révision 1.4. Nous allons alors créer une branche à la révision 1.4, et insérer les corrections sur cette branche. La première branche démarrant à 1.4 est numérotée 1.4.1 et les révisions sur cette branche sont numérotées 1.4.1.1, 1.4.1.2, 1.4.1.3 ... Cette notation permet de créer d'autres branches à partir de 1.4. Les étapes nécessaires sont les suivantes : co -r1.4 foo.c editer foo.c pour apporter les corrections ci -r1.4.1 foo.c Nous obtenons alors l'arbre suivant : 1.1 -> 1.2 -> 1.3 -> 1.4 -> 2.1 -> 2.2 -> 2.3 ... \ \ \> 1.4.1.1 -> ... Il peut être nécessaire d'incorporer les différences entre 1.4 et 1.4.1.1 dans une révision de la release 2. Pour cela, il faut utiliser la commande rcsmerge qui automatise le processus. Une autre raison de créer une branche est liée au problème posé par un programmeur ayant verrouillé une révision pour la modifier et qui n'a pas encore effectué le dépôt de ses modifications. Si un autre programmeur désire modifier cette révision, il ne peut pas le faire tant que le verrou n'est pas supprimé. La solution est de créer une branche, et ensuite, une fois que le premier programmeur a effectué son check-in, utiliser la commande rcsmerge pour incorporer les deux révisions.

Autres commandes utiles

La commande rcsdiff permet de comparer la version actuelle avec la plus récente version qui est sauvegardée : rcsdiff -u foo.c L'option -u de rcsdiff permet d'utiliser un diff unifié (unified diff). En fait, rcsdiff accepte toutes les options que la commande diff accepte. Par exemple, pour générer un context diff, on utilisera l'option -c. Par exemple, nous extrayons la révision 1.2 de foo.c, nous effectuons une petite modification (correction d'un bug), et rcsdiff foo.c nous donne: =================================================================== RCS file: foo.c,v retrieving revision 1.2 diff -u -r1.2 foo.c --- foo.c 1997/08/23 20:33:36 1.2 +++ foo.c 1997/08/24 20:36:20 @@ -24,7 +24,7 @@ main() { int i = 0; - for(i = 0; i <= 10; i++) + for(i = 0; i < 10; i++) printf("Hello world\n"); } Les lignes qui n'ont pas été modifiées sont affichées avec une simple espace en début de ligne. Les lignes effacées dans la version la plus récente ont un - en début de ligne, et les lignes qui ont été ajoutées ont un + en début de ligne. Comme on peut le remarquer, les lignes modifiées sont considérées comme effacées dans l'ancienne version, et les changements sont considérés comme ajoutés dans la nouvelle. Dans le cas d'un context diff, les lignes marquées avec un ! indiquent un changement, et les lignes marquées avec un + indiquent des lignes ajoutées. On peut également comparer deux versions : rcsdiff -u -r1.2 -r1.4 foo.c Une utilisation de rcsdiff est la génération de mises à jour appliquées par des patch. Par exemple, une fois un programme terminé, effectuez un check-in sur tous les fichiers avec un nouveau numéro de release, par exemple 2.0. Puis, pour la release suivante (par exemple la 3.0), lancez rcsdiff sur la révision 2.0 pour tous les fichiers : rcsdiff -c -r2.0 RCS/* > monprog-3.0.patch 2>&1 Vous obtiendrez ainsi un fichier patch que vous pourrez distribuer aux personnes possédant la version 2.0. Une autre possibilité intéressante est de consulter le journal des modifications effectuées. Pour cela on utilise la commande rlog, qui donne l'historique des changements effectués : rlog foo.c Ce qui nous donne : RCS file: foo.c,v Working file: foo.c head: 1.3 branch: locks: strict mad: 1.3 access list: symbolic names: keyword substitution: kv total revisions: 3; selected revisions: 3 description: programme hello world ---------------------------- revision 1.3 date: 1997/08/23 20:36:20; author: mad; state: Exp; lines: +6 -1 Correction d'un petit bug ---------------------------- revision 1.2 date: 1997/08/23 20:33:36; author: mad; state: Exp; lines: +8 -3 Affichage 10 fois du message ---------------------------- revision 1.1 date: 1997/08/23 20:33:05; author: mad; state: Exp; Initial revision ============================================================================= Le plus intéressant ce sont les commentaires saisis lors d'un ci. Par exemple, on peut noter que pour la révision 1.3, le commentaire est Correction d'un petit bug. A noter que les dates et heures sont en UTC, et non pas dans la zone locale. Cela permet à des développeurs de différentes zones géographiques de pouvoir collaborer. La commande rcs est utilisée pour modifier l'état des fichiers RCS. Par exemple, pour bloquer un fichier qui ne l'est pas ou pour casser un verrou.

Ce que ne fait pas RCS

Une des choses que RCS ne permet pas de gérer est la modification simultanée de mêmes fichiers par plusieurs utilisateurs. Un autre outil est disponible pour effectuer ce travail, c'est CVS (Concurrent Version System). CVS, qui est basé sur RCS, permet également le management des différents release d'un logiciel. CVS est disponible sur ou sur .

Emacs et RCS

L'éditeur GNU Emacs est capable de reconnaître automatiquement un fichier géré par RCS lorsque vous l'éditez. Emacs fournit alors une interface aux commandes de contrôle de version. Vous n'avez donc qu'à apprendre seulement quelques commandes (la plupart débutent par "C-x v") pour faire du contrôle de version, ou alors utiliser le menu Tools, puis le sous-menu Version Control. Le manuel Emacs contient de la documentation dans le noeud version control.

Copyright

Copyright (c) 1997 Frederic Couchet, association APRIL. Ce document peut être reproduit et distribué dans son intégralité ou partiellement, par quelque moyen physique que ce soit. Il reste malgré tout sujet aux conditions suivantes : La mention du copyright doit être conservée, et la présente section préservée dans son intégralité sur toute copie intégrale ou partielle. Si vous distribuez ce travail en partie, vous devez mentionnez comment obtenir une version intégrale de ce document et être en mesure de la fournir. De petites portions de ce document peuvent être utilisées comme illustrations d'une présentation ou comme remarques sans autorisation préalable si les citations d'usage sont réalisées.