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.