Les hooks mercurial à ton service

Ven 08 janvier 2010

Voici une recette pour mettre à jour un site statique, en utilisant simplement mercurial et ses mécanismes appelés "hooks".

Pour mettre à jour ce genre de site, on peut utiliser rsync ou un simple client FTP, mais il peut parfois apparaître des différences entre les deux copies (reliquats de renommages, suppressions, déplacements...).

La recette peut certainement être étendue à des sites dynamiques (en PHP, par exemple) ; il sera d'autant plus difficile de l'utiliser dans le cas où la structure de données "cible" aura migré (ajout de champs dans la base de données, par exemple). Autant dire que pour la v0.0.1 de Polar Geek \<http://polar-geek.org>, ça marche super bien. Pour une appli Django, ce sera beaucoup plus hasardeux.

Prérequis :

  • Mercurial installé sur le poste local
  • un serveur sur lequel tu as un accès ssh
  • sur ce même serveur, mercurial doit être installé

Pour l'exemple qui suit, j'utilise les serveurs d'alwaysdata \<http://alwaysdata.com>, qui ont tout ce qu'il faut là où il faut.

En distant :

Tout d'abord, se connecter à son serveur via SSH

$ ssh <login>@ssh.alwaysdata.com
Password:
$ mkdir --parents /chemin/vers/mon/projet
$ cd /chemin/vers/mon/projet
$ hg init

Ce répertoire sera le réceptacle du projet, c'est à dire une copie du dépôt. J'utilise le chemin absolu, mais il sera peut-être plus court de passer par le chemin relatif (depuis le "home")

En local :

$ hg init projet
$ cd projet
$ mkdir www
$ cd www
$ echo "coucou" > index.html
$ hg add && hg ci -m "premier commit"

note

Important : les fichiers du site (c'est à dire ceux qui seront exposés sur le virtual host, ceux qui seront effectivement en ligne) doivent se trouver dans un sous-répertoire de "projet". Pourquoi ? parce que s'il en est autrement, n'importe qui pourrait faire

hg clone http://monsite.com/ et récupérer l'intégralité du dépôt. Ça peut être négligeable pour quelqu'un, mais si le site contient du code, des identifiants de BDD, des fichiers "cachés" qui ne doivent pas être révélés, ça peut être gênant. Si le site est effectivement "caché" dans un sous répertoire, on ne pourra pas accéder au dépôt via http.

Il faudra donc, au moment du paramétrage du virtual host, faire pointer ton domaine vers: /chemin/vers/mon/projet/www/

Que se passe-t-il si on "push" le dépôt à ce moment-là ? On pourrait imaginer que, automagiquement, le code distant est synchronisé et les fichiers mis à jour. En fait, non. Ce qu'on met à jour, c'est le dépôt, pas les fichiers en eux-même. De la même manière qu'en faisant hg pull, on ne met pas à jour les fichiers, et qu'en cas de nouveautés, il faut ajouter la commande hg update pour que la copie en local soit le miroir de la copie distante. On peut encore résumer ça à une commande : hg pull -u.

Et quand on fait push, c'est la même chose, malheureusement. Je m'explique -je démarre en local :

$ hg push ssh://<login>@ssh.alwaysdata.com//chemin/vers/mon/projet/
#.... quelques messages concernant la mise a jour
$ ssh <login>@ssh.alwaysdata.com
Password:
$ cd /chemin/vers/mon/projet
$ ls -al
drwxrwxr-x  3 <login>  <login> 4096 2010-01-08 15:06 .
dr-xrwx--- 16 www-data <login> 4096 2010-01-08 15:06 ..
drwxrwxr-x  3 <login> <login> 4096 2010-01-08 15:06 .hg

Mais où est le répertoire "www" ? Physiquement, pas là. En fait, il est enregistré dans l'historique du dépôt, mais il se cache bien dans le dossier caché .hg. Pour le voir apparaître : hg update sur le distant et paf le site est à jour.

Mais c'est nul. Ça veut dire que pour une mise à niveau d'un dépôt, il faut se connecter au serveur ssh après avoir fait un "push". Débile. Bouh. Remboursez. C'est gratos ? Ah ben remboursez quand même.

Heureusement qu'on peut paramétrer les "hooks" dans Mercurial. Les hooks, ce sont des actions qui seront exécutées dès lors qu'un "signal" sera apparu sur un dépôt donné. Sur le serveur distant, il suffit de créer (ou modifier) le fichier .hg/hgrc dans le dépôt et ajouter les deux lignes qui suivent.

[hooks]
changegroup = hg update

En français, ça signifie: "dès qu'un changement intervient sur le dépôt, lance la commande 'update'."

Pour s'assurer que ce "hook" fonctionne, il suffit de le tester. Depuis la copie locale, d'abord :

$ echo " les gens" >> index.html
$ hg ci -m "et on fait coucou les gens"
$ hg push ssh://<login>@ssh.alwaysdata.com//chemin/vers/mon/projet/
# ... Messages...

Si tu regardes sur le serveur en distant, le fichier index.html doit être synchrone (ainsi que tous les autres fichiers qui ont été modifiés, déplacés, supprimés, etc.). Bref, le dépôt est l'image fidèle de la copie locale.

Youpi.

Bonus track - je suis feignant

La feignantise, c'est notre moteur. Notre credo. Alors, taper sans arrêt l'adresse du serveur pour les push/pull, etc, c'est la plaie. Il se trouve que si on tape juste la commande hg push ou hg pull, l'adresse du dépôt sur lequel on veut pousser ou tirer est celle définie par le dépôt "default".

Mais comment définit-on ce dépôt, me demandes-tu ? Rien de plus simple. Sur la copie locale de ton dépôt, ouvre ton éditeur de texte favori et ajoute les lignes suivantes au fichier .hg/hgrc:

[paths]
default = ssh://<login>@ssh.alwaysdata.com//chemin/vers/mon/projet/

C'est ainsi qu'on définit le chemin du dépôt par défaut. On peut bien évidemment définir autant de "chemins" qu'on veut, si on aime avoir une copie chez soi et une copie sur bitbucket, sur une clé USB et sur un autre répertoire, etc... par exemple :

[paths]
default = ssh://<login>@ssh.alwaysdata.com//chemin/vers/mon/projet/
bb = ssh://hg@bitbucket.org/<login>/monprojet/
usb = file:///media/disk/monprojet/
one = file:///home/<login>/Ubuntu One/monprojet/
db = file:///home/<login>/Dropbox/monprojet/

Pour pousser sur chacun de ces chemins, une commande:

$ hg push bb # pour pousser sur bitbucket
$ hg push usb # pour pousser sur la clé USB

Oui, des fois je suis un peu paranoïaque, mais au moins, mon "talk" du Lugradio Live se trouvait à sept endroits différents, sur des supports physiques et distants.

Conclusion

Cette manière de faire n'est peut-être pas la meilleure, et est loin d'être la seule. En tous cas, sur des projets "non-sensibles", concernant des mises à jour de media simples (pages HTML, CSS, images, etc), c'est la plus directe: de la même manière que je commite offline, je peux propager mes modifications sur mon serveur de production avec la même commande. On peut bien évidemment rendre la chose plus amusante, en faisant en sorte que le dépôt distant ait sa branche active appelée "prod", et qu'on merge sur cette branche une fois que les modifications sur la branche "default" ont été torture-testée en amont.

Après, tout est affaire de goût. On peut aussi préférer l'excellent Fabric pour les cas où la mise à niveau d'une application nécessite un peu plus qu'une synchronisation de fichiers (et encore... mixer Fabric et Mercurial, ça peut encore être un ticket gagnant)