jeudi 3 janvier 2013

Mercurial, le DVCS à l'esprit KISS

Ça c'est du SEO, sérieux !

Git par ci, Github par là... on ne parle que de Git dirait-on ! Toutefois croyez-moi, des alternatives existent.

Mercurial en est une. Ce DVCS écrit en Python m'a étonné par la simplicité de son interface.

romain@lilibox:~/tmp$ hg
Mercurial, système de gestion de sources distribué

commandes de base :

add        add the specified files on the next commit
annotate   show changeset information by line for each file
clone      make a copy of an existing repository
commit     commit the specified files or all outstanding changes
diff       diff repository (or selected files)
export     dump the header and diffs for one or more changesets
forget     forget the specified files on the next commit
init       create a new repository in the given directory
log        show revision history of entire repository or files
merge      merge working directory with another revision
pull       pull changes from the specified source
push       push changes to the specified destination
remove     remove the specified files on the next commit
serve      start stand-alone webserver
status     show changed files in the working directory
summary    summarize working directory state
update     update working directory (or switch revisions)

utiliser "hg help" pour la liste complète des commandes ou "hg -v" pour plus de détails

17 commandes. OK, Git en affiche 21, ce n'est pas flagrant. Mais on retrouve un peu l'esprit Python quelque part.

Voici un exemple d'utilisation (qui suppose que vous avez déjà installé mercurial et configuré votre .hgrc... c'est le plus dur, c'est pourquoi ce n'est pas décrit dans la note !)

Créons un répertoire projet avec un fichier blabla.markdown dedans.

romain@lilibox:~/tmp$ mkdir hermes_project
romain@lilibox:~/tmp$ cat > hermes_project/blabla.markdown
hello you

Initialisons un dépot Mercurial dedans.

romain@lilibox:~/tmp$ cd hermes_project/
romain@lilibox:~/tmp/hermes_project$ hg init

Ajoutons notre fichier.

romain@lilibox:~/tmp/hermes_project$ hg add blabla.markdown

On contrôle que tout semble fonctionner.

romain@lilibox:~/tmp/hermes_project$ hg status
A blabla.markdown

On a A comme added. Oui, c'est sobre... On peut faire notre premier commit.

romain@lilibox:~/tmp/hermes_project$ hg commit -m "initialisation"

On vérifie l'état de notre dépôt avec la commande log :

romain@lilibox:~/tmp/hermes_project$ hg log
changeset:   0:8b1b093873ca
tag:         tip
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:04:03 2013 +0100
summary:     initialisation

On peut cloner le dépôt pour faire nos développement isolément ailleurs sur le disque ou sur le réseau.

romain@lilibox:~/tmp/hermes_project$ cd ..
romain@lilibox:~/tmp$ hg clone hermes_project hermes_project_clone
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
romain@lilibox:~/tmp$ ls
hermes_project  hermes_project_clone  hermes_projectls
romain@lilibox:~/tmp$ rmdir hermes_projectls/

Vérifions que le clone fonctionne.

romain@lilibox:~/tmp$ cd hermes_project_clone/
romain@lilibox:~/tmp/hermes_project_clone$ ls
blabla.markdown
romain@lilibox:~/tmp/hermes_project_clone$ hg log
changeset:   0:8b1b093873ca
tag:         tip
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:04:03 2013 +0100
summary:     initialisation

On fait alors une modification dans le fichier.

romain@lilibox:~/tmp/hermes_project_clone$ echo "Modif importante" >> blabla.markdown 
romain@lilibox:~/tmp/hermes_project_clone$ hg status
M blabla.markdown

On fait notre commit. Celui apparait bien dans le journal.

romain@lilibox:~/tmp/hermes_project_clone$ hg commit -m "ouh là là, j'ai bien travaillé"
romain@lilibox:~/tmp/hermes_project_clone$ hg log
changeset:   1:b4a72dc79003
tag:         tip
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:11:09 2013 +0100
summary:     ouh là là, j'ai bien travaillé

changeset:   0:8b1b093873ca
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:04:03 2013 +0100
summary:     initialisation

Retournons à présent à notre projet initial. On aimerait bien récupérer les informations de notre branche.

romain@lilibox:~/tmp/hermes_project_clone$ cd ../hermes_project
romain@lilibox:~/tmp/hermes_project$ hg pull ../hermes_project_clone/
pulling from ../hermes_project_clone/
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
(run 'hg update' to get a working copy)

Pour l'instant, les modifications ne sont pas appliquées au dépôt hermes_project.

romain@lilibox:~/tmp/hermes_project$ cat blabla.markdown 
hello you

Bah ouais, c'est écrit qu'il faut lancer une nouvelle commande...

romain@lilibox:~/tmp/hermes_project$ hg update
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
romain@lilibox:~/tmp/hermes_project$ cat blabla.markdown 
hello you
Modif importante

Pas besoin de faire un commit, tout est déjà là.

romain@lilibox:~/tmp/hermes_project$ hg log
changeset:   1:b4a72dc79003
tag:         tip
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:11:09 2013 +0100
summary:     ouh là là, j'ai bien travaillé

changeset:   0:8b1b093873ca
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:04:03 2013 +0100
summary:     initialisation

Soyons un peu foufous et codons un conflit dans nos fichiers.

romain@lilibox:~/tmp/hermes_project$ echo "modif branche 1" >> blabla.markdown 
romain@lilibox:~/tmp/hermes_project$ cd ../hermes_project_clone/
romain@lilibox:~/tmp/hermes_project_clone$ echo "modif speciale branche 2" >> blabla.markdown 
romain@lilibox:~/tmp/hermes_project_clone$ hg commit -m "commit sur branche 2"
romain@lilibox:~/tmp/hermes_project_clone$ cd ../hermes_project
romain@lilibox:~/tmp/hermes_project$ hg commit -m "commit sur branche 1"
romain@lilibox:~/tmp/hermes_project$ hg log
changeset:   2:cdfeb308599a
tag:         tip
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:24:28 2013 +0100
summary:     commit sur branche 1

changeset:   1:b4a72dc79003
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:11:09 2013 +0100
summary:     ouh là là, j'ai bien travaillé

changeset:   0:8b1b093873ca
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:04:03 2013 +0100
summary:     initialisation

romain@lilibox:~/tmp/hermes_project$ cd ../hermes_project_clone/
romain@lilibox:~/tmp/hermes_project_clone$ hg log
changeset:   2:4787e4dc850c
tag:         tip
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:24:16 2013 +0100
summary:     commit sur branche 2

changeset:   1:b4a72dc79003
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:11:09 2013 +0100
summary:     ouh là là, j'ai bien travaillé

changeset:   0:8b1b093873ca
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:04:03 2013 +0100
summary:     initialisation

Sur le pull, ça coince

romain@lilibox:~/tmp/hermes_project_clone$ cd ../hermes_project
romain@lilibox:~/tmp/hermes_project$ hg pull ../hermes_project_clone/
pulling from ../hermes_project_clone/
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)

La commande hg heads nous dit que nous avons un nouveau changeset :

romain@lilibox:~/tmp/hermes_project$ hg heads
changeset:   3:4787e4dc850c
tag:         tip
parent:      1:b4a72dc79003
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:24:16 2013 +0100
summary:     commit sur branche 2

changeset:   2:cdfeb308599a
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:24:28 2013 +0100
summary:     commit sur branche 1

Fusionnons tout ça :

romain@lilibox:~/tmp/hermes_project$ hg merge

À ce moment là, hg m'ouvre vim pour que j'édite le conflit. Je garde le contenu ci-dessous :

hello you
Modif importante
modif branche 1
modif speciale branche 2

Mercurial me dit ça :

0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
romain@lilibox:~/tmp/hermes_project$ cat blabla.markdown 
hello you
Modif importante
modif branche 1
modif speciale branche 2

Très bien, je commite et tout est OK.

romain@lilibox:~/tmp/hermes_project$ hg commit -m "un conflit de canard"
romain@lilibox:~/tmp/hermes_project$ hg log
changeset:   4:2c0cad92cac3
tag:         tip
parent:      2:cdfeb308599a
parent:      3:4787e4dc850c
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:30:17 2013 +0100
summary:     un conflit de canard

changeset:   3:4787e4dc850c
parent:      1:b4a72dc79003
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:24:16 2013 +0100
summary:     commit sur branche 2

changeset:   2:cdfeb308599a
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:24:28 2013 +0100
summary:     commit sur branche 1

changeset:   1:b4a72dc79003
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:11:09 2013 +0100
summary:     ouh là là, j'ai bien travaillé

changeset:   0:8b1b093873ca
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:04:03 2013 +0100
summary:     initialisation

Vous pouvez également créer une branche in place :

romain@lilibox:~/tmp/hermes_project$ hg branch test
marked working directory as branch test
romain@lilibox:~/tmp/hermes_project$ hg commit -m "branch test"

La branche est créée :

romain@lilibox:~/tmp/hermes_project$ hg log
changeset:   5:b707281d46fc
branch:      test
tag:         tip
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:49:17 2013 +0100
summary:     branch test

changeset:   4:2c0cad92cac3
parent:      2:cdfeb308599a
parent:      3:4787e4dc850c
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:30:17 2013 +0100
summary:     un conflit de canard

On ajoute des trucs dans notre fichier :

romain@lilibox:~/tmp/hermes_project$ echo "more" >> blabla.markdown 
romain@lilibox:~/tmp/hermes_project$ hg status
M blabla.markdown
romain@lilibox:~/tmp/hermes_project$ hg commit -m "More on the named branch"
romain@lilibox:~/tmp/hermes_project$ hg log
changeset:   6:94594bfe9c50
branch:      test
tag:         tip
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:50:20 2013 +0100
summary:     More on the named branch

changeset:   5:b707281d46fc
branch:      test
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:49:17 2013 +0100
summary:     branch test

changeset:   4:2c0cad92cac3
parent:      2:cdfeb308599a
parent:      3:4787e4dc850c
user:        Romain TOUZE <romain.touze@exemple.com>
date:        Wed Jan 02 23:30:17 2013 +0100
summary:     un conflit de canard

On fusionne dans notre branche default :

romain@lilibox:~/tmp/hermes_project$ hg update default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
romain@lilibox:~/tmp/hermes_project$ hg merge test
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
romain@lilibox:~/tmp/hermes_project$ hg commit -m "merge test branch to default"

Et voilà, c'est magique.

Le moins que l'on puisse dire, c'est que pour l'habitué du DVCS, ce n'est pas trop dépaysant. Il y aura parfois plus, parfois moins de commande à taper.

Pour aller plus loin, suivez le tutorial sur le site officiel.

Ne vous méprenez pas, Mercurial est une application solide et sérieuse. Elle est utilisée notamment pour le service Bitbucket (concurrent de Github) et est également utilisé par ce petit projet.

PS 1 : pour les ignares, Hg est évidemment le symbole du Mercure, comme je l'ai appris lors de ma première colle de chimie (Z = 80 les enfants !)
PS 2 : une dernière fonctionnalité pour la route :

romain@lilibox:~/tmp/hermes_project$ hg serve
listening at http://lilibox.workgroup:8000/ (bound to *:8000)

Et hop, votre dépôt se trouve accessible via une superbe interface web sur le port 8000. Cool, non ?