Petit guide de survie de la gestion de version

Auteur: Guillaume Bouchard
Création:4 Novembre 2009

Sommaire

Introduction

Lors de tout travail sur du code, il arrive souvent que l'on se retrouve face à différentes problématiques :

Pour cela il existe de nombreux outils appelés gestionnaires de versions. Vous avez peut être déjà entendu parler de Subversion, mais celui-ci n'est pas très pratique pour vous car il impose d'avoir un serveur pour stocker les données, ce qui impose une connexion internet et la disponibilité d'un serveur (ou l'inscription à une offre en ligne comme assembla par exemple)

Dans ce document je vais vous apprendre à vous servir de façon basique de Mercurial, ou hg, et vous verrez que quelques minutes d'apprentissage pourrons vous faire gagner beaucoup de temps.

Concepts

Mercurial est un gestionnaire de version décentralisé, cela signifie qu'il n'a pas besoin de serveur pour fonctionner.

Mercurial n'initialise sur un répertoire, dit répertoire de travail. Une fois cette initialisation effectuée, l'outil prend note de toutes les modifications faites dans le répertoire (et ses sous-répertoire) et vous fournit des outils très performants d'analyse du répertoire ainsi qu'un moyen simple de transférer vos modification à un collaborateur.

Lors de vos sessions de travail, il faudra dire à Mercurial quel sont les nouveaux fichiers que vous voulez suivre. Puis de façons régulières, il faudra demander à Mercurial de réaliser un point de sauvegarde, ou commit.

Installation

Mercurial est disponible sur windows, macos ou unix, Je vous renvoie au site de Mercurial. Pour linux il est disponible très facilement dans votre arbre de paquets (apt-get/aptitude/emerge install mercurial).

Il est bien évidement disponible sur les machines de la fac, et si ce n'est pas le cas, allez le demander aux administrateurs.

Il existe de nombreuses interfaces graphiques pour mercurial, la plus connue pour Windows étant TortoiseHG. La suite de ce document ne les traites pas et se focalise sur l'utilisation ligne de commande, mais si vous comprenez les concepts, l'utilisation d'une autre interface ne posera pas de problème.

Utilisation

Supposons que nous travaillons sur un petit projet de programmation python. Ce projet consiste à demander à l'utilisateur des informations personnels et a afficher quelques bêtises basées sur ses informations.

Note

L'exemple se fait sur un programme python car cela m'amuse à vous sensibiliser à l'existence de ce langage, ne vous formaliser pas sur le code, comprenez l'outil !

Début du travail

Commençons par crée notre répertoire de travail et ajoutons un peu de code à l'intérieur :

~ $ mkdir my_project
~ $ cd my_project/
~/my_project $ vim test.py

Le contenu du fichier test.py étant le suivant, celui-ci demande le nom de l'utilisateur et l'affiche :

name = raw_input("Quel est votre nom ? ")
print "Bonjour",name

L'exécution du programme nous donne :

 ~/my_project $ python test.py
Quel est votre nom ? Guillaume
Bonjour Guillaume

Nous voulons maintenant demander à Mercurial de gérer notre code grâce à la commande hg init :

~/my_project $ hg init
~/my_project $ hg status
? test.py

La commande hg status nous donne la liste des fichiers du répertoire et leur état. Ici le "?" nous annonce que Mercurial ne connaît pas le fichier test.py, c'est à dire qu'il ne le gère pas encore. Il faut lui dire de le gérer grâce à hg add :

~/my_project $ hg add test.py
~/my_project $ hg status
A test.py

Ici le A signifie que le fichier a été ajouté.

Note

Par pitié, ne mettez pas de fichier inutile dans vos dépôts (résultats de compilation par exemple). Ceux-ci sont volumineux et inutile.

Note

Votre répertoire de travail, ou dépôt contient maintenant un répertoire .hg. Ne modifier, déplacer, supprimer jamais ce répertoire. Il contient toutes les informations de votre dépôt.

Premier commit

Les modifications ne sont pas encore sauvegardées, hg commit nous crée le premier point de sauvegarde :

~/my_project $ hg commit

Note

Lors du commit, vous serez amenés à entrer un message de commit dans un éditeur de texte. Essayer d'entrer le message le plus précis possible décrivant les modification réalisées depuis le dernier commit.

Note

Quand faut il réaliser un commit ? Le plus souvent possible, chaque fois qu'une tache atomique est réalisée, comme l'ajout d'une fonction ou la correction d'un bug. Plus vos commit seront petits et bien dissociables, plus la recherche dans l'historique des commits sera facile à réaliser.

Si vous demander à nouveau le statut du dépôts, celui-ci ne renverra rien puisque depuis le dernier commit, aucune modification n'a été effectuée. Vous pouvez aussi observer la liste des commit grâce à la commande hg log :

 ~/my_project $ hg log
changeset:   0:b1502e612f2d
tag:         tip
user:        Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>
date:        Wed Nov 04 12:59:34 2009 +0100
summary:     Premier point de sauvegarde

Note

Les commits sont accompagnées d'un nom d'utilisateur. Pour bien vous identifier et ainsi permettre d'attribuer facilement les commits à son auteur, il faut modifier le fichier ~/.hgrc de manière a entrer les informations nécessaires :

[ui]
username=Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>

Effectuer des modifications

Nous allons maintenant modifier un peu notre programme, le fichier test.py devient :

#coding: utf8
name = raw_input("Quel est votre nom ? ")
birthyear = int(raw_input("En quelle année etes vous né ?"))

print "Bonjour",name
print "Vous avez",(2009 - birthyear),"ans, félicitations !"

et l'exécution donne :

 ~/my_project $ python test.py
Quel est votre nom ? Guillaume
En quelle année etes vous né ?1986
Bonjour Guillaume
Vous avez 23 ans, félicitations !

Que donne hg status, et hg diff qui permet de voir les modifications depuis le dernier commit ? :

~/my_project $ hg status
M test.py

~/my_project $ hg diff
diff -r b1502e612f2d test.py
--- a/test.py  Wed Nov 04 12:59:34 2009 +0100
+++ b/test.py  Wed Nov 04 13:10:51 2009 +0100
@@ -1,3 +1,7 @@
+#coding: utf8
 name = raw_input("Quel est votre nom ? ")
+birthyear = int(raw_input("En quelle année etes vous né ?"))
+
 print "Bonjour",name
+print "Vous avez",(2009 - birthyear),"ans, félicitations !"

Le M de status indique que le fichier à été modifié. Le diff nous montre les 4 lignes ajoutés en les préfixant d'un +.

Il est temps de sauvegarder cette modification, vous pouvez spécifier directement le message dans la commande :

~/my_project $ hg commit -m "Ajout de la demande de la date et du calcul de l'age"

~/my_project $ hg log
changeset:   1:da5b3591d96c
tag:         tip
user:        Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>
date:        Wed Nov 04 13:13:31 2009 +0100
summary:     Ajout de la demande de la date et du calcul de l'age

changeset:   0:b1502e612f2d
user:        Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>
date:        Wed Nov 04 12:59:34 2009 +0100
summary:     Premier point de sauvegarde

Transférer son travail

Vous pouvez facilement réaliser une archive du dépôt et la transférer par mail grâce à la commande hg bundle:

~/my_project $ hg bundle -a mondepot.bz2
2 changesets found

~/my_project $ ls
mondepot.bz2  test.py

Il vous suffit ensuite d'envoyer le fichier mondepot.bz2 à votre collegue par mail ou tout autre moyen (la clé usb fonctionne très bien).

De son coté, celui-ci crée un répertoire de travail avec hg init et importe vos commits :

~ $ mkdir tp_trop_cool
~ $ cd tp_trop_cool/
~/tp_trop_cool $ hg init

~/tp_trop_cool $ hg unbundle ../mondepot.bz2
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 1 files
(run 'hg update' to get a working copy)

Mercurial vous conseil d'exécuter hg update. En effet, lors de l'importation des modification, votre dépôt reste dans son état précédant, les commits importé ne sont pas appliqués au dépôt. Effectuez l'update, nous verrons par la suite ce que cela signifie :

~/tp_trop_cool $ hg update
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

Les modifications de votre collègue ont étés importées :

~/tp_trop_cool $ hg log
changeset:   1:da5b3591d96c
tag:         tip
user:        Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>
date:        Wed Nov 04 13:13:31 2009 +0100
summary:     Ajout de la demande de la date et du calcul de l'age

changeset:   0:b1502e612f2d
user:        Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>
date:        Wed Nov 04 12:59:34 2009 +0100
summary:     Premier point de sauvegarde
test.py

Travailler chacun de son coté

Supposons pour les besoins que mon collègue s'appelle Jon Snow, il travail dans le répertoire ~/tp_trop_cool sur son ordinateur alors que moi je travail dans le repertoire ~/my_project/.

Jon Snow va effectuer plusieurs modifications du fichier, d'abord une erreur de frappe :

~/tp_trop_cool $ hg diff
diff -r da5b3591d96c test.py
--- a/test.py  Wed Nov 04 13:13:31 2009 +0100
+++ b/test.py  Wed Nov 04 13:31:50 2009 +0100
@@ -1,6 +1,6 @@
 #coding: utf8
 name = raw_input("Quel est votre nom ? ")
-birthyear = int(raw_input("En quelle année etes vous né ?"))
+birthyear = int(raw_input("En quelle année etes vous né ? "))

 print "Bonjour",name
 print "Vous avez",(2009 - birthyear),"ans, félicitations !"

~/tp_trop_cool $ hg commit -m "Ajout d'un espace à la fin de la question" ~/tp_trop_cool $ vim test.py

Puis il va supprimer la date 2009 en dur au profit de la fonction renvoyant la date du jour :

 ~/tp_trop_cool $ hg diff
diff -r d55f343ae46f test.py
--- a/test.py  Wed Nov 04 13:32:04 2009 +0100
+++ b/test.py  Wed Nov 04 13:32:26 2009 +0100
@@ -1,7 +1,9 @@
 #coding: utf8
+import datetime
+
 name = raw_input("Quel est votre nom ? ")
 birthyear = int(raw_input("En quelle année etes vous né ? "))

 print "Bonjour",name
-print "Vous avez",(2009 - birthyear),"ans, félicitations !"
+print "Vous avez",(datetime.datetime.now().year - birthyear),"ans, félicitations !"

~/tp_trop_cool $ hg commit -m "Suppression de 2009 en dur et remplacement par la valeur de l'année en cours"

Au final, son log est le suivant :

~/tp_trop_cool $ hg log
changeset:   3:004cb2580811
tag:         tip
user:        Jon Snow <jon.snow@thewall.7kd.invalid>
date:        Wed Nov 04 13:32:35 2009 +0100
summary:     Suppression de 2009 en dur et remplacement par la valeur de l'année en cours

changeset:   2:d55f343ae46f
user:        Jon Snow <jon.snow@thewall.7kd.invalid>
date:        Wed Nov 04 13:32:04 2009 +0100
summary:     Ajout d'un espace à la fin de la question

changeset:   1:da5b3591d96c
user:        Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>
date:        Wed Nov 04 13:13:31 2009 +0100
summary:     Ajout de la demande de la date et du calcul de l'age

changeset:   0:b1502e612f2d
user:        Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>
date:        Wed Nov 04 12:59:34 2009 +0100
summary:     Premier point de sauvegarde

De mon coté, je fais une modification très simple du dépôt :

~/my_project $ hg diff
diff -r da5b3591d96c test.py
--- a/test.py  Wed Nov 04 13:13:31 2009 +0100
+++ b/test.py  Wed Nov 04 13:36:58 2009 +0100
@@ -1,6 +1,6 @@
 #coding: utf8
 name = raw_input("Quel est votre nom ? ")
-birthyear = int(raw_input("En quelle année etes vous né ?"))
+birthyear = int(raw_input("En quelle année êtes vous né(e) ?"))

 print "Bonjour",name
 print "Vous avez",(2009 - birthyear),"ans, félicitations !"

~/my_project $ hg ci -m "Correction d'une faute d'orthographe + accord feminin"

Plus une autre où j'ajoute un fichier TODO :

~/my_project $ hg add TODO

~/my_project $ hg diff
diff -r d781be5c36b1 TODO
--- /dev/null  Thu Jan 01 00:00:00 1970 +0000
+++ b/TODO     Wed Nov 04 13:39:20 2009 +0100
@@ -0,0 +1,2 @@
+- pleins de trucs à faire
+- penser a utiliser mercurial, c'est sympa

~/my_project $ hg status
A TODO

~/my_project $ hg commit -m "Ajout du TODO"

J'ai donc deux nouveaux commits dans mon dépôt :

~/my_project $ hg log

changeset:   3:06023582b078
tag:         tip
user:        Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>
date:        Wed Nov 04 13:39:25 2009 +0100
summary:     Ajout du TODO

changeset:   2:d781be5c36b1
user:        Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>
date:        Wed Nov 04 13:37:28 2009 +0100
summary:     Correction d'une faute d'orthographe + accord feminin

changeset:   1:da5b3591d96c
user:        Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>
date:        Wed Nov 04 13:13:31 2009 +0100
summary:     Ajout de la demande de la date et du calcul de l'age

changeset:   0:b1502e612f2d
user:        Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>
date:        Wed Nov 04 12:59:34 2009 +0100
summary:     Premier point de sauvegarde

Je décide d'envoyer mes commits par mail à Jon :

~/my_project $ hg bundle -a mes_nouveaux_commits.bz2
4 changesets found

Jon les reçoit et les charges dans son dépôt :

~/tp_trop_cool $ hg unbundle ../mes_nouveaux_commits.bz2
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 2 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)

Voila peut être la pire des situation, Jon a travaillé pendant que je travaillais aussi, résultat nos codes sont différentes. Mercurial ne tolère pas cela et ainsi crée une nouvelle tête, c'est ce qu'il nous informe dans son message.

Note

Nous voyons ici tout de suite le cas particulier où les deux participants ont travaillés chacun de leur coté. Dans le cas ou cela ne serait pas le cas, hg unbundle ne créerait pas les nouvelles têtes et les commits de Guillaume serait directement intégrés aux commits de Jon.

Mercurial est plutôt sympathique et nous donne la solution, il faut exécuter hg merge. Cette commande va regrouper ensemble les deux têtes de commits :

~/tp_trop_cool $ hg merge
merging test.py
warning: conflicts during merge.
merging test.py failed!
1 files updated, 0 files merged, 0 files removed, 1 files unresolved
use 'hg resolve' to retry unresolved file merges or 'hg up --clean' to abandon

Drame, une des modification de Guillaume dans "test.py" n'est pas compatible avec celles de Jon, il va donc falloir intervenir manuellement. En ouvrant le fichier test.py on peut trouver :

<<<<<<< local
birthyear = int(raw_input("En quelle année etes vous né ? "))
=======
birthyear = int(raw_input("En quelle année êtes vous né(e) ?"))
>>>>>>> other

Ces marques sont la preuve d'un conflit dans la zone en question. La zone du haut correspond à votre code, celle du bas au code que l'on vient d'importer. Il est de VOTRE responsabilité de remplacer cette zone par du code prenant en compte les modifications des deux personnes, ici la faute d'orthographe, la correction de genre et l'espace en fin de ligne :

birthyear = int(raw_input("En quelle année êtes vous né(e) ? "))

Vous devez ensuite exécuter un commit pour sauvegarder les modifications du merge, mais avant il faut prévenir Mercurial que vous avez résolu le conflit dans le fichier test.py :

~/tp_trop_cool $ hg resolve -m test.py
~/tp_trop_cool $ hg commit -m "merge avec Guillaume"

Le log commence à devenir conséquent :

~/tp_trop_cool $ hg log
changeset:   6:b9f31a715b66
tag:         tip
parent:      3:004cb2580811
parent:      5:06023582b078
user:        Jon Snow <jon.snow@thewall.7kd.invalid>
date:        Wed Nov 04 13:53:58 2009 +0100
summary:     merge avec Guillaume

changeset:   5:06023582b078
user:        Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>
date:        Wed Nov 04 13:39:25 2009 +0100
summary:     Ajout du TODO

changeset:   4:d781be5c36b1
parent:      1:da5b3591d96c
user:        Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>
date:        Wed Nov 04 13:37:28 2009 +0100
summary:     Correction d'une faute d'orthographe + accord feminin

changeset:   3:004cb2580811
user:        Jon Snow <jon.snow@thewall.7kd.invalid>
date:        Wed Nov 04 13:32:35 2009 +0100
summary:     Suppression de 2009 en dur et remplacement par la valeur de l'année en cours

changeset:   2:d55f343ae46f
user:        Jon Snow <jon.snow@thewall.7kd.invalid>
date:        Wed Nov 04 13:32:04 2009 +0100
summary:     Ajout d'un espace à la fin de la question

changeset:   1:da5b3591d96c
user:        Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>
date:        Wed Nov 04 13:13:31 2009 +0100
summary:     Ajout de la demande de la date et du calcul de l'age

changeset:   0:b1502e612f2d
user:        Guillaume Bouchard <guillaume.bouchard@insa-lyon.fr>
date:        Wed Nov 04 12:59:34 2009 +0100
summary:     Premier point de sauvegarde

Jon peut ainsi renvoyer ses modification à Guillaume :

~/tp_trop_cool $ hg bundle -a bundle_apres_merge.bz2
7 changesets found

Qui peut les intégrer :

~/my_project $ hg unbundle ../bundle_apres_merge.bz2
adding changesets
adding manifests
adding file changes
added 3 changesets with 3 changes to 2 files
(run 'hg update' to get a working copy)

~/my_project $ hg update
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

Travailler avec l'historique

Guillaume vient de recevoir 3 changement de la part de Jon, les deux effectués par Jon ainsi que le merge effectué par Jon de son travail. Celui-ci peut effectuer différentes opérations sur son dépôt pour consulter ces changements.

hg update N lui permettra de modifier son dépôt pour qu'il reflète l'état de la révision N (N étant le premier nombre dans la ligne changeset du hg log). hg update revient à la toute dernière version. :

~/my_project $ hg update 0
1 files updated, 0 files merged, 1 files removed, 0 files unresolved

~/my_project $ python test.py
Quel est votre nom ? Guillaume
Bonjour Guillaume

~/my_project $ hg update 1
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

~/my_project $ python test.py
Quel est votre nom ? Guillaume
En quelle année etes vous né ?1986
Bonjour Guillaume
Vous avez 23 ans, félicitations !

~/my_project $ hg update 3
2 files updated, 0 files merged, 0 files removed, 0 files unresolved

~/my_project $ python test.py
Quel est votre nom ? Guillaume
En quelle année êtes vous né(e) ?1986
Bonjour Guillaume
Vous avez 23 ans, félicitations !

~/my_project $ hg update
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

Vous pouvez consulter les différences entre deux versions avec hg diff -rN:M :

~/my_project $ hg diff -r2:4
diff -r d781be5c36b1 -r d55f343ae46f test.py
--- a/test.py  Wed Nov 04 13:37:28 2009 +0100
+++ b/test.py  Wed Nov 04 13:32:04 2009 +0100
@@ -1,6 +1,6 @@
 #coding: utf8
 name = raw_input("Quel est votre nom ? ")
-birthyear = int(raw_input("En quelle année êtes vous né(e) ?"))
+birthyear = int(raw_input("En quelle année etes vous né ? "))

 print "Bonjour",name
 print "Vous avez",(2009 - birthyear),"ans, félicitations !"

Résumé

Les commandes de mercurial commencent par hg et sont suivie d'une action, qui peut etre raccourci :

init
Initialise un répertoire comme dépôt
add
Ajoute un fichier au dépôt
commit (ci)
Sauvegarde les modifications dans le dépôt
log
Affiche la liste des commits. hg -v log donne en plus la liste des fichiers modifiés par les commits
status (st)

Affiche l'état du dépôt depuis le dernier commit, plusieurs possibilités :

A Fichier Ajouté
M Fichier Modifié
? | Fichier non pris en compte
merge
Permet de prendre en compte les modifications faites par un collègue
bundle -a/unbundle nom_de_fichier.bz2
Export/Import des modifications dans le dépôt

Note

hg bundle -a stocke TOUT le dépôt dans l'archive, cela peut être lourd pour le transfert par mail, mais à l'avantage que tout le dépôt peut être recré à partir d'un unique bundle en cas ou vous perdiez les données de votre ordinateur.

Conclusion

Mercurial peut vous faire gagner beaucoup de temps pour un temps d'apprentissage très cours. Bien évidement cette introduction ne dispense en rien de lire le site de Mercurial où vous pourrez en apprendre plus sur son fonctionnement et sur tous les petits service qu'il peut rendre.

Note

Une des première chose à faire que je vous conseil est de vous renseignez sur l'utilisation du .hgignore.

Note

Dernière remarque, Mercurial est utile pour tout ce qui est texte, vous pouvez donc aussi y stocker vos liste de course, vos sites web ou vos rapport si ceux-ci sont fait dans un langage texte (comme LaTeX ou RestructuredText). Ce document est d'ailleurs actuellement géré avec Mercurial.

Note

La note précédente suppose que le document est sous gestion de version, c'est à dire qu'il peut évoluer. N'hésitez pas à me mailer tous ce qui vous semble pertinent.