Drawing clean and thin vertical and horizontal lines on an HTML Canvas

I saw this morning on StackOverflow a question whose lack of answer prompted me to make a detailed one and an explanation.

Canvas positioning is in float. And round values are between pixels.

A consequence is that if you draw a line with a lineWidth of 1, the results will extend over two pixels if you don’t target the middle of the pixel.

On the following schema, the first horizontal line was drawn with a y position of 1. This line is fuzzy and wide. The second horizontal line was drawn with a y position of 4.5. It is thin and precise.

And it gets worse if instead of using round values you use any kind of float values : the result is very irregular.

A consequence is that in order to draw thin lines, you have to adapt the y position before.

I made two functions to draw thin vertical or horizontal lines and I use them on all my canvas based projects :

	function drawThinHorizontalLine(c, x1, x2, y) {
		c.lineWidth = 1;
		var adaptedY = Math.floor(y)+0.5;
		c.beginPath();
		c.moveTo(x1, adaptedY);
		c.lineTo(x2, adaptedY);
		c.stroke();
	}
	function drawThinVerticalLine(c, x, y1, y2) {
		c.lineWidth = 1;
		var adaptedX = Math.floor(x)+0.5;
		c.beginPath();
		c.moveTo(adaptedX, y1);
		c.lineTo(adaptedX, y2);
		c.stroke();
	}

L’en-tête correct pour la compatibilité avec IE9

Curieusement, c’est très peu connu (j’ai trouvé ce truc dans les documentations de Microsoft) mais il est possible de dire à Internet Explorer 9 de fonctionner aussi bien que les autres browsers, au point que je développe maintenant sur Chrome, teste sur Firefox régulièrement et ne lance que rarement une machine virtuelle pour tester sur IE9.

Il faut simplement avoir, en plus du doctype, l’option indiquant à IE qu’il doit utiliser sa dernière version de moteur. Du coup le header qui marche à tous les coups et qu’il suffit de reprendre est celui-ci :

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="X-UA-Compatible" content="IE=edge" />
	<meta http-equiv="content-type" content="text/html; charset=UTF8" />

Et voilà. Comme en plus, maintenant qu’IE9 est répandu on peut se contenter de dire aux clients qui parlent d’IE8- qu’ils doivent mettre à jour leur navigateur, c’est, pour la première fois depuis longtemps, le bonheur…

A small proposal regarding error handling in Go

I’ve mentioned on reddit a few times, very succinctely, an idea I’ve had regarding a small syntaxic sugar aimed at easing the handling of errors. As it gained some (small) attention I think a more readable presentation is needed.

The usage case is the one where typically Go ends to be more verbose that exception based languages while we really have nothing to do with the error at that level. It happens. All Go coders have been a little annoyed by this kind of situation :

func Things(path string) ([]int, error) {
	srcdir, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer srcdir.Close()
	filenames, err := srcdir.Readdirnames(-1)
	if err != nil {
		return nil, err
	}

	...
}

The solution I propose is similar to the one we use to dismiss an unneeded parameter (that is using the _ symbol). I propose to use another symbol to return an error when the value is not nil. I thought about the ^ symbol because it seem to convey the idea to rethrow to the caller but the choice of the symbol is, for now, a detail.

func Things(path string) ([]int, error) {
	srcdir, ^ := os.Open(path)
	defer srcdir.Close()
	filenames, ^ := srcdir.Readdirnames(-1)

	...
}

The code would be more consise, more readable and the logical flow would be preserved. The standard and generally preferable in place error handling and the existing code base would be left untouched.

More precisely:

  • there would be a compilation error if the ^ symbol is used and the function doesn’t have exactly one returned parameter of a type implementing the error interface.
  • at the occurrence of the non null error being returned, all unamed return parameters would be left uninitialized and all named return parameters would keep their value at the moment of the error

Note that this idea could be extended to other types than errors but I’m not proposing that now as I’m seeing only marginally useful use cases and more complex rules arising.

 

Simplicité des goroutines

Tout à l’heure, je me suis penché sur un problème de performance de mon serveur Braldop.

En fait le problème était lié à une lourde opération effectuée lors de certaines requêtes :

for _, ami := range amis {
	log.Println(" enrichissement carte ami ", ami.IdBraldun)
	bra.EnrichitCouchePNG(ms.répertoireCartesBraldun(ami.IdBraldun, ami.Mdpr), couche, PNG_CACHE_SIZE)
}

Il se trouve que cette opération est indispensable, forcément lourde (lecture et surtout écriture de plusieurs fichiers PNG) mais n’a pas du tout besoin d’être faite avant que la réponse soit envoyée au navigateur.

J’ai donc naturellement décidé de l’effectuer en parallèle. Et voici le résultat :

for _, ami := range amis {
	log.Println(" enrichissement carte ami ", ami.IdBraldun, " lancé en goroutine")
	go bra.EnrichitCouchePNG(ms.répertoireCartesBraldun(ami.IdBraldun, ami.Mdpr), couche, PNG_CACHE_SIZE)
}

Et ça marche : la réponse est envoyée tout de suite alors que le serveur continue à travailler.

Il suffit d’ajouter go devant l’opération lourde pour qu’elle soit effectuée en parallèle.

Imaginez la lourdeur du changement de code en Java par exemple. Et notez que contrairement à un thread Java une goroutine ne pèse quasiment rien en mémoire et CPU.

 

Utiliser Git en entreprise

Introduction

Je ne vais pas détailler le pourquoi. Si vous lisez ça, c’est que vous connaissez déjà les avantages de Git et peut-être l’utilisez vous déjà, soit sur des projets open source soit sur des projets en solo dans votre entreprise.

Le vrai problème en entreprise est le comment. En particulier parce que vous avez probablement des collègues qui sont sous Windows et qui n’ont aucune culture linux ni open source. Il est même vraisemblable qu’il s’agisse de la majorité. Malgré tout, en supposant qu’ils soient ouverts et qu’il n’y ait pas de politisation de l’affaire, rien n’est perdu. En me basant sur mon expérience, je décris ici les étapes que j’ai identifiées :

  1. expliquer la motivation, convaincre
  2. définir la structure des repository et le workflow
  3. documenter l’installation
  4. documenter les premiers pas et les manipulations basiques puis les astuces
La première étape, je ne la décrirai pas, car non seulement vous trouverez de nombreuses références sur Internet mais surtout elle est éminémment contextuelle et dépend autant de l’exemple que vous donnez que des ennuis provoqués par votre gestionnaire de sources actuels.
Ce que je vais décrire, car je pense qu’il s’agit d’éléments portables d’une entreprise (ou équipe) à l’autre est la succession des étapes suivantes.
Git offre énormément de liberté mais ceci peut égarer les nouveaux utilisateurs. C’est pourquoi ce document pose comme règles ce qui n’est généralement vu que comme des bonnes pratiques (organisation des branches, repository et workflow), peut-être les plus communes mais absolument pas les seules possibles.
Je vais supposer, pour la suite, que vous disposez d’un serveur linux. Il peut s’agir d’un serveur interne, accessible uniquement sur le réseau local, ou, mieux, d’un serveur hébergé et toujours disponible. Ceci  étant supposé acquis, la suite de cette article peut être vue comme un manuel tout prêt pour tous les développeurs qui dans l’entreprise se mettent à git.
Les exemples de cet article vont être basés sur ces éléments de contexte :
  • notreProjet : nom du projet
  • dev.laboite.com : le serveur linux
  • alain : compte sur dev.laboite.com du développeur initié à Git, à l’origine du projet notreProjet et son intégrateur
  • bernard : compte sur dev.laboite.com d’un développeur Windows
  • christian : compte d’un autre développeur Windows

Workflow et repository

Chaque développeur, pour chaque projet, dispose de deux repository :
  • un repository privé, dans lequel il développe, sur son poste de travail
  • un repository public, hébergé sur le serveur linux, lisible par tous ses collègues mais dans lequel lui seul écrit
L’un des développeurs a, à un moment donné, le rôle d’intégrateur et son repository public sert de référence pour tous les développeurs : ils iront chercher dans ce repository le dernier état de la branche master (notons qu’un autre développeur pourra être l’intégrateur sur une autre branche du projet).
Le workflow le plus basique possible est celui-ci :
  • bernard travaille, comme tous les développeurs, dans son repository de travail.
  • Il effectue une modification relativement simple et courte sur le code commun. Au fur et à mesure de son développement, très fréquemment, il effectue des commit locaux. Il met ainsi à jour sa branche master locale.
  • Lorsque ses développements doivent être partagés, il envoie (push) l’état de son repository vers son repository public et demande à l’intégrateur de récupérer ses travaux.
  • alain, l’intégrateur dans notre exemple, effectue un fetch pour les récupérer. Ces développements apparaissent pour lui sous la branche bernard/master. Il merge cette branche avec son répertoire de travail (éventuellement dans une branche dédiée), teste si nécessaire, et publie (push) dans son répository public.
  • Les autres développeurs peuvent ensuite, lorsqu’ils le souhaitent, récupérer l’état faisant référence via un fetch de alain/master suivi d’un merge (lequel n’est pas destructif : il s’intégrera généralement sans intervention manuelle à leur travail en cours).

 Les développeurs, le plus souvent, ne travaillent pas dans leur branche master mais dans une branche spécifique à la fonctionnalité en cours. Lorsque c’est le cas il peuvent :

  • soit merger cette branche à la branche master avant d’effectuer un push vers leur repository public
  • soit envoyer cette branche sur le repository public

Le premier cas est le plus fréquent et correspond à un développement que tout le monde a intérêt à récupérer rapidement (c’est-à-dire pas une fonctionnalité posant problème, incomplète ou incompatible).
La logique générale est que la branche master correspond à la version en cours de production (on étiquette les versions « stables » ou « releases » en créant des branches spécifiques, ce qui permet par exemple de redéployer ou patcher facilement une vieille version).
Si un sous-groupe des développeurs travaille sur une fonctionnalité particulière, qui n’a pas vocation à être intégrée à la version master générale, l’un d’entre eux pourra, pour la branche associée, faire office d’intégrateur sans passer par l’intégrateur de la branche master.

Remarquons que pour pouvoir sans difficulté intégrer facilement les développements de ses collègues, l’intégrateur plus encore que les autres s’astreint à éviter de réaliser de gros développements dans la branche master.

Mise en place sous Windows

Le développeur sous linux ou mac n’aura pas de difficulté à installer et utiliser Git, par contre une aide est réellement indispensable pour le développeur sous Windows.

Installation de msysgit

Après avoir téléchargé la dernière version de l’installeur de Git for Windows, exécutez là en appliquant les choix par défaut :

  • Use Git Bash only
  • Checkout Windows-style, commit Unix-style line endings
Suite à cette installation vous disposerez deux logiciels :
  • Git Bash
  • Git Gui

Paramétrage et prise en main du bash de msysgit

La première chose à faire est de paramétrer Git Bash afin qu’il soit adapté à votre écran :

  1. Faites un clic droit sur l’icône de Git Bash et sélectionnez Properties
  2. Choisissez l’onglet Layout
  3. Modifier les quatre premiers champs (par exemple 150, 9000, 150, 50)
  4. Cliquez sur OK pour sauvegarder vos modifications

Git Bash met à votre disposition un shell qui conceptuellement ne surprendra pas les habitués de la commande dos mais bien plus puissant et définissant des commandes différentes pour certaines opérations communes sous Windows.
De même que sous Windows vous pouvez compléter les noms de fichiers ou chemins via la touche tab, et naviguer parmi les commandes déjà exécutées via les flèches haut et bas du clavier. Vous pouvez coller ce qui est dans votre presse-papier (ou dans la sélection de la console) en cliquant avec le bouton droit de la souris.
L’invite de commande vous indique en permanence là où vous trouvez.
Je décris ici très très sommairement les commandes indispensables pour l’utilisation de msysgit. Il ne s’agit pas d’une référence : l’appel à l’aide de Google ou d’un linuxien près de chez vous sera indispensable pour en savoir plus.

cd

Cette commande vous permet de changer de répertoire.
Pour aller dans le répertoire c:\code, tapez ainsi

cd /c/code

(sans oublier que la touche tab complète les noms de fichiers et répertoires)

Pour remonter d’un niveau, tapez

cd ..

Notez que si vous vous déplacez vers un répertoire qui est sous Git, l’invite de commande vous précise quelle est la branche courante (initialement master). C’est en effet une information qu’il faut toujours avoir en tête avant de faire des manipulations sur le repository.

mkdir

Cette commande permet de créer un répertoire.

ls

Cette commane commande est l’équivalent de dir. Une variante souvent utile permet d’afficher les fichiers cachés et donne leurs caractéristiques :

ls -al

cp

Cette commande permet de dupliquer un fichier ou répertoire (ajouter dans ce cas l’option -r)

mv

mv permet de renommer ou déplacer un fichier ou répertoire

less

less suivie d’un nom de fichier vous permet de le visualiser rapidement le contenu d’un fichier

Vous pouvez utiliser les flèches du clavier pour faire défiler et la touche q ferme la commande. Notez que d’autres commandes utilisent ce principe, en particulier git log ou bien git show.

rm

rm permet de supprimer un fichier ou répertoire. L’option -r est souvent combinée à l’option -f afin de ne pas devoir confirmer chaque suppression :

rm -rf grosRepertoire

ssh

ssh vous permet une connexion via le protocole sécurisé ssh sur un serveur distant. Vous disposez alors du shell qui est associé à votre compte sur ce serveur.

Exemple de connexion pour christian sur le serveur dev.laboite.com :

ssh christian@dev.laboite.com

Si vous n’avez pas mis en place de clef publique (voir plus loin), un mot de passe vous est demandé.

scp

scp est similaire à cp mais permet de faire une copie par ssh (vous connaissez peut-être l’interface winscp pour le même usage). On donnera un exemple de son utilisation un peu plus loin, pour la copie de votre clef publique.

Installation des clefs sur le serveur linux

Notez que la clef publique que vous allez définir est totalement disjointe de celle que vous avez déjà éventuellement pour putty (et que vous chargez via pageant).

On donne ici l’exemple de manipulation pour bernard.

Dans la console Git Bash, la génération de clef se fait via la commande ssh-keygen :

ssh-keygen -t rsa -C "bernard@laboite.com"

Il n’est pas forcément indispensable en LAN de renseigner la passphrase (tapez juste entrée) mais dans ce cas vous devrez protéger la clef générée. Le plus simple est de simplement faire entrée à chaque question posée par la commande.

Cette commande génère deux fichiers, id_rsa et id_rsa.pub, dans le répertoire .ssh de votre compte utilisateur (sous Windows).

Il faut envoyer le fichier .pub sur le serveur linux. Pour ce faire, on emploie la commande scp :

scp /c/Users/Bernard/.ssh/id_rsa.pub bernard@dev.laboite.com:~

Normalement, vous devrez taper votre mot de passe puisque la clef publique n’est pas encore en place. Cette commande envoie le fichier dans le répertoire .ssh de bernard sous dev.laboite.com.

Vous devez ensuite vous connecter sous dev.laboite.com pour la suite :

ssh bernard@dev.laboite.com

Afin d’assurer que le répertoire .ssh existe, tapez la commande suivante

mkdir -p .ssh

Vous aurez aussi besoin par la suite d’un répertoire git dans votre home :

mkdir -p .git

Et ajoutez la clef publique à la liste des clefs autorisées :

cat id_rsa.pub >> .ssh/authorized_keys

Et supprimez le fichier inutile :

rm id_rsa.pub

Vous pouvez maintenant vous déconnecter de dev.laboite.com :

exit

Configuration générale de Git

Deux paramètres qui seront les mêmes sur tous les projets doivent être renseignés. Adaptez les commandes suivantes et tapez les dans Git Bash :

git config --global user.name "Bernard Sonom"
git config --global user.email "Bernard.Sonom@laboite.com"

Récupération du projet existant

Nous sommes ici dans le cas où bernard rejoint le projet notreProjet, lequel a déjà été publié sur dev.laboite.com par alainBernard a installé Git mais n’a pas encore le code source du projet.

Déplacez vous, sur la console, vers le répertoire dans lequel vous voulez travailler. Par exemple :

cd /c/dev/

(en supposant que vous ayez un répertoire c:\dev\ sur votre disque dur)

Clonez le repository officiel (repository public de l’intégrateur) :

git clone ssh://bernard@dev.laboite.com/home/alain/git/notreProjet.git

Cette commande crée le répertoire notreProjet. Notez qu’il s’agit non seulement de votre répertoire de travail mais également d’un repository complet, contenant l’historique de toutes les branches et dans lequel vous pourrez faire des commits et autres manipulation sans connexion réseau. Les fichiers de Git sont rangés dans le répertoire .git. Les sous répertoires sont exempts de fichiers de Git.

Pour la suite des manipulations, vous devez être dans le répertoire de travail :

cd notreProjet

Il faut maintenant créer le répertoire public du développeur (celui, sur dev.laboite.com qui sera accessible à tous) :

git clone --bare .git notreProjet.git
scp -r notreProjet.git bernard@dev.laboite.com:git/notreProjet.git
rm -rf notreProjet.git

Il est maintenant nécessaire de définir un certain nombre d’alias qui éviteront par la suite de taper les url. Dans notre cas Bernard va devoir désigner au minimum son répertoire public et on va supposer pour l’exemple qu’il veut aussi désigner le répertoire public d’un autre collègue :

git remote add pub ssh://bernard@dev.laboite.com/home/bernard/git/notreProjet.git
git remote add christian ssh://bernard@dev.laboite.com/home/christian/git/notreProjet.git

Par convention, chaque développeur voit sous le nom pub son propre répertoire public (vous pouvez aussi lui donner un autre nom mais s’agissant du répertoire auquel vous vous adresserez le plus souvent, faites en sorte qu’il soit court !).

Le répertoire public de l’intégrateur a déjà un alias défini : origin.

Workflow le plus élémentaire, contribution et mise à jour

On va illustrer les commandes de base au travers d’un exemple. Dans cet exemple tout se fait dans la branche master.

bernard ajoute un fichier (curry.html) et modifie un fichier qui était déjà présent (index.html).

Avant de commiter, il vérifie que l’état de son répertoire de travail correspond bien à ce qu’il pense, via la commande git status.

S’il veut plus de détail sur l’écart entre ses données de travail et le repository, il peut utiliser la commande git diff.

Avant de commiter, il faut spécifier, via git add que le fichier curry.html, jusque là inconnu de Git, doit faire partie de l’ajout. git status permet ensuite de vérifier la prise en compte.

Il s’agit maintenant de faire un commit. Notons qu’il n’est pas possible de ne pas spécifier de message. On utilise l’option -a (all) afin de préciser que les fichiers modifiés (ici le fichier index.html) font partie du commit. La commande ainsi effectuée est la suivante :

git commit -am "description de l'evolution"

(notons que sous Windows en utilisant msysgit et il n’est pas possible de mettre des accents dans les commentaires à la ligne de commande)

On effectue encore ensuite un git status pour voir la modification.

Avant de transmettre ses modifications, un développeur effectue typiquement plusieurs commits. Dans notre exemple le développeur va publier ses travaux et demander à l’intégrateur de les intégrer.

La publication se fait simplement via la commande git push :

git push pub

L’intégrateur, prévenu, récupère les modifications, les merge avec les développements des autres développeurs et, dés que possible, les publie dans son propre répertoire public lequel fait référence.

bernard récupère la dernière version officielle via les commandes suivantes :

git fetch origin
git merge origin/master

(origin est l’alias du repository officiel, et origin/master désigne simplement la branche master provenant de ce repository)

Remarquons que la commande merge indique succinctement les modifications effectuées (fichiers et nombres de lignes impactées).

Si bernard veut connaitre plus en détail les modifications récentes, il peut exploiter la commande git log :

git log --stat

Notons que de nombreuses autres options sont possibles, par exemple pour étudier le graphe des commits et que l’outil Git Gui offre également un outil de visualisation des évolutions.

Utilisation d’une branche

La branche master doit toujours rester partageable, mergeable, déployable (pas en production réelle ou chez le client mais pour les tests courants).

Ceci signifie que dés lors que vous tapez du code qui peut avoir des effets de bord, ou implique des évolutions conjointes d’autres fichiers partagés, ou correspond à une fonctionnalité dont la réalisation peut prendre de longues heures (ou pire), il est nécessaire de créer une branche.

Travail en solo sur une branche

Supposons que vous ayez à développer la fonctionnalité « zoom ». Après avoir commité ce qui traînait dans votre branche actuelle, vous faites simplement appel à la commande git branch :

git branch zoom

Puis vous basculez sur cette branche

git checkout zoom

L’invite de commande (si vous êtes utilisez Git Gui, scm breeze ou un système analogue) vous indique que vous êtes sur la branche checklistgit status aurait pu vous l’indiquer également.

Vous effectuez alors vos développements et vos commits normalement. Ils n’impactent pas la branche master. Lorsque vous avez fini (et commité!) vos travaux, vous revenez sur la branchemaster et vous intégrez (merge) la branche checklist :

git checkout master
git merge zoom

Si vous n’avez jamais publié la branche zoom, l’historique (log) des développements reste linéaire (donc moins verbeux) et n’indique pas cette branche.

Travail sur plusieurs branches

Le principal intérêt de travailler sur des branches est évidemment de pouvoir en changer.

Supposons que vous soyez sur la branche zoom mais que vous ayez une correction à faire qui ne concerne pas particulièrement la fonction de zoom, il vaut mieux la faire dans master.

Vous commencez par sauvegarder votre travail en cours éventuel par un git commit dans votre branche zoom.

Vous passez sur la branche master :

git checkout master

Vous effectuez et commitez votre modification, vous la partagez si nécessaire (via git push).

Vous revenez ensuite sur votre branche zoom :

git checkout zoom

Il est probable que vous souhaitiez disposer tout de suite de cette correction faite dans master, vous effectuez donc un merge (la destination d’un merge est toujours la branche en cours) :

git merge master

La correction est maintenant présente dans les deux branches master et zoom.

Partager une branche

Lorsque l’on hésite à créer une branche, c’est généralement qu’il faut la créer, au cas où. Cela signifie que vous aurez souvent des branches à très courte durée de vie et qu’il n’y a aucune raison de les publier.

Vous publierez une branche dans les deux cas suivants :

  • d’autres que vous doivent consulter cette branche ou y contribuer
  • vous voulez la sauvegarder afin de ne pas la perdre si vous perdez votre disque dur local

Lorsque vous effectuez un git push pub, seules les branches déjà publiées (master à l’origine) sont mises à jour.

Si vous souhaitez qu’une branche soit envoyée sur pub, vous devez simplement la spécifier :

git push pub zoom

Par la suite, si vous modifiez cette branche elle sera automatiquement publiée lorsque vous ferez git push pub.

Elagage

Pour supprimer une vieille branche (ici nommée saucisson), vous tapez simplement

git branch -d saucisson

Branches, versions, déploiements

On utilise communément la logique suivante :

  • la branche master, qui doit toujours rester propre, partageable et déployable (au moins pour tests)
  • une branche dédiée par fonctionnalité non triviale, qu’elle soit partagée ou propre à un développeur
  • une branche par version identifiée (a fortiori une branche pour chaque version donnée à un client)
  • une branche pour chaque hot fix (correction bug bloquant) qui doit être appliquée sur plusieurs branches

Créer une branche pour chaque version identifiée permet de facilement récupérer cette version pour test ou la patcher en cas de bug.

Remarquez que d’autres workflows existent et sont discutés sur internet.

.gitignore

Il est fréquent d’avoir dans son répertoire de travail des fichiers qui n’ont pas à être sous Git, qu’il s’agisse de fichiers de paramétrage contenant des mots de passe, de fichiers automatiquement créés par un IDE, de documents de travail provisoire, etc.

Afin de ne pas alourdir l’affichage de git status par ces fichiers, on les référence dans un fichier nommé .gitignore que l’on place dans le répertoire de travail. Par exemple :

# config confidentielle locale
scripts/config.sh

# fichiers générés par l'IDE
*-bak

# application compilée et objets
*.6
*.8
batteurchoucroute

On placera généralement un fichier .gitignore à la racine du répertoire de travail mais il est possible d’en disposer également dans les sous-répertoires.

Création d’un nouveau projet

Mettre un répertoire de travail sous git est facile. Il suffit de se placer dans le répertoire et d’exécuter la commande

git init

Ceci crée le répertoire .git. Rien n’est ajouté au repository, vous pouvez maintenant effectuer les add et commit.

Vous pouvez ensuite appliquer la manipulation décrite plus haut pour partager ce projet, sous l’alias remote pub sur dev.laboite.com.

Astuces et manipulations avancées

Visualiser l’historique

La commande git log vous donne l’historique des commits du projet. Vous pouvez naviguer avec les flèches du clavier et vous quittez cet affichage avec la touche q.

git help log vous liste toutes les options possibles mais elles sont un peu difficiles à comprendre. Voici certaines variantes utiles :

Listage des fichiers modifiés et nombres de lignes modifiées :

git log --stat

Affichage compact :

git log --oneline

Affichage du log d’un fichier particulier :

git log curry.html

Affichage du graphe des branches :

git log --graph --all --abbrev --color

Notez que les options sont généralement combinables.

Comparaison avec une version précédente

Pour savoir en quoi votre répertoire de travail diffère du dernier commit, tapez :

git diff

Si vous vous intéressez à un fichier particulier (ici sauceoseille.html), tapez ceci

git diff sauceoseille.html

Supposons que vous ayez mergé une modification et vouliez voir en quoi elle consistait, vous voulez donc comparer avec l’avant-dernier commit :

git diff HEAD^1 sauceoseille.html

Visualisation d’une vieille version

La version commitée est visible ainsi :

git show HEAD:cheminFichier

Une version plus ancienne ainsi :

git show HEAD~4:cheminFichier

Récupération d’une vieille version

Récupérer la dernière version commitée du fichier ascenseur.go :

git checkout HEAD ascenseur.go

Effacer tout ce que vous avez fait depuis le dernier commit :

git reset --hard HEAD

Exploiter le hash

Une notion importante de Git est le hash. Les objets du modèle de données de Git (commits, fichiers, répertoires, tags) sont tous identifiés par un hash (SHA1) calculé à partir de leur contenu et permettant :

  • leur indexation pour le moteur de Git
  • le dédoublonnage (si deux fichiers ont le même hash, on peut partir du principe qu’ils sont identiques)
  • la désignation par différents outils de Git

Lorsque vous appelez la commande git log, Git vous donne le hash des commits :

Dans cet exemple, le hash du dernier commit est db5e6c5b44c6a462e87364cf59d5cad66461161f. En pratique, vous exploiterez généralement le début de ce hash (au moins 5 caractères) car il y a peu de chances que vous ayez deux objets dont les hash partagent les même 5 ou 6 premiers caractères (en cas de confusion, Git vous avertit).

Ce hash permet par exemple de consulter un ancien commit :

 

Vous pouvez l’utiliser pour comparer la version de travail d’un fichier avec celle datant de ce vieux commit.

git diff d0b09 restart-mapserver.sh

affiche :

Si vous souhaitez remplacer un fichier de votre répertoire de travail par une vieille version, vous faites ainsi

git reset --hard d0b09 restart-mapserver.sh

 Conclusion

J’ai écrit ce document pour permettre la migration vers Git d’une petite équipe de développeurs, la plupart étant sous Windows et sans culture linux. Il s’agissait de la seconde tentative, la première, basée sur l’auto apprentissage et la débrouillardise de chacun, ayant échoué, en raison principalement de la difficulté pour les développeurs Windows à gérer à la fois Git, la logique unix et les solutions techniques nécessaires pour faire tourner Git sur Windows. Cette fois notre nouveau gestionnaire de sources est entré dans les moeurs de l’équipe qui en apprécie à juste titre les avancées par rapport au système précédent (svn). Je pense qu’un tel guide est une condition, dans ce type d’équipe, d’une migration sans douleur.

Communication Go-Javascript en cross-domain et champs privés

Echanger en json des objets entre un client javascript et un serveur écrit en go est facile grace aux packages http et json. Je décris ici une mise en place simple et cross-domain de cet échange tout en tirant parti de la casse pour séparer les champs privés et publics.

J’utilise jquery dans ce document mais il est aisé de s’en passer.

Côté serveur en go, supposons que vous ayez deux structures de données, une pour les messages entrants et une pour les messages sortants :

type MessageIn struct {
	TrucA	   uint
	TrucB      string
	TrucLourd  *DonnéesComplexes
}

type DonnéesComplexes struct {
	PleinDeChoses  []Chose
	DesBidules     []Bidules
	donnéesPrivées StructureUtiliséeCôtéServeur
}

type MessageOut struct {
	Erreur    string
	Machins   []string
	Text      string
}

Dans cette exemple TrucLourd est un pointeur, ainsi le décodeur json n’alloue-t-il pas une structure (lourde) inutile si le message du client ne la contient pas. La même logique pourrait être appliquée si necessaire dans la structure MessageOut (celle devant contenir la réponse du serveur au client).

Le code du serveur est très simple :

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
)

const (
	port = 8001
)

type Serveur struct {
}

func getFormValue(hr *http.Request, name string) string {
	values := hr.Form[name]
	if len(values) > 0 {
		return values[0]
	}
	return ""
}

func envoieRéponse(w http.ResponseWriter, out *MessageOut) {
	bout, err := json.Marshal(out)
	if err != nil {
		fmt.Println("Erreur encodage réponse : ", err)
		return
	}
	fmt.Fprint(w, "recoitDuServeur(")
	w.Write(bout)
	fmt.Fprint(w, ")")
}

func (ms *Serveur) ServeHTTP(w http.ResponseWriter, hr *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Request-Method", "GET")
	hr.ParseForm()
	in := new(MessageIn)
	out := new(MessageOut)
	defer envoieRéponse(w, out)
	bin := ([]byte)(getFormValue(hr, "in"))
	err := json.Unmarshal(bin, in)
	if err != nil {
		out.Erreur = "Erreur décodage : " + err.Error()
		return
	}
	traiteLesDonnées(in, out)
}

func (server *Serveur) Start() {
	http.Handle("/", server)
	fmt.Printf("le serveur démarre sur le port %d\n", port)
	err := http.ListenAndServe(":"+strconv.Itoa(port), nil)
	if err != nil {
		fmt.Println("Erreur au lancement : ", err)
	}
}

func main() {
	ms := new(Serveur)
	ms.Start()
}

Tout est là, hormis la logique « métier » déportée dans traiteLesDonnées.

L’encodage en json, fait via json.Mashal n’encode que les données publiques (dont le nom commence par une majuscule) et transmet correctement nombres, tableaux, structures pointées, etc.

Les deux premières lignes de la fonction ServeHTTP assurent un traitement correct des requêtes en cross-domain.

L’utilisation du defer envoieRéponse permet de ne pas répéter cet appel, en particulier si l’on multiplie les conditions de sortie (et les return)

Le protocole de communication, indispensable pour le cross-domain, est JSONP, d’où, dans la réponse, l’encapsulation du json généré dans un appel à une fonction javascript (recoitDuServeur).

Côté client javascript, le code n’est guère plus complexe :

// message est un objet contenant les champs attendus par le serveur
// dans MessageIn (ou une partie)
function envoieAuServeur(message) {
	$.ajax(
		{
			url: URL_SERVEUR + '?in='+JSON.stringify(message),
			crossDomain: true,
			dataType: "jsonp"
		}
	);
	return true;
}

function recoitDuServeur(message) {
	// traitement des données recues en réponse
}

En fait, une partie de la « science » se trouve dans une fonction destinée à produire l’objet message et visant à assurer :

  • que l’objet message ne contient pas des prototypes
  • que les champs dont le nom commence par des minuscules soient exclus
  • que les 0 et nulls (inutiles) ne soient pas envoyés

Non seulement les champs dont le nom commence par une minuscule seraient inutiles car non reçus dans la structure (il existe des moyens mais ils ne m’intéressent pas) mais surtout il s’avère extrêmement commode de prolonger côté javascript cette logique d’identifier les champs publics (c’est-à-dire vus par l’autre partie) par leur majuscule. On peut ainsi facilement, des deux côté, les reconnaître lors de la programmation ou du débug et il n’y a pas de raison de se retenir, en particulier lorsque l’on manipule des structures un peu lourdes, d’intégrer aux données reçues de l’autre partie des données utiles localement (en particulier des données calculées cachées pour des raisons de performances).

J’utilise donc pour construire mon message sortant (ou des parties de mon message) la fonction suivante en javascript :

// bâtit une copie indépendante et transmissible en json à un serveur go :
// effectue une deep-copy de l'objet source mais en ignorant tous les
//  champs dont le nom ne commence pas par une majuscule. Ne copie pas
//  les prototypes ni les champs nulls (ou 0).
function goclone(source) {
	if ($.isArray(source)) {
		var clone = [];
		for (var i=0; i<source.length; i++) {
			if (source[i]) clone[i] = goclone(source[i]);
		}
		return clone;
	} else if (typeof(source)=="object") {
		var clone = {};
		for (var prop in source) {
			if (source[prop]) {
				var firstChar = prop.charAt(0);
				if (firstChar!=firstChar.toUpperCase()) continue;
				clone[prop] = goclone(source[prop]);
			}
		}
		return clone;
	} else {
		return source;
	}
}

Si vous voulez voir les sources de l’application réelle dont j’ai extrait cet exemple, voici

Une remarque pour vos tests, n’oubliez pas que peu de choses fonctionnent si vous ouvrez vos fichiers html en file://, il faut passer par un serveur http.

Sprites css et javascript

Pour le système cartographique Braldop j’ai développé un système de visualisation de type Google Maps, à base de canvas et javascript (ce système forme maintenant la base de la vue dans le jeu Braldahim).

Ce système est visible en action là : http://canop.org/braldop/groupes/public/map.html

Un problème est rapidement apparu : un temps de chargement important, de plus de trente secondes, à chaque consultation. L’étude des connexions montrait que ce délai était lié à la récupération par le navigateur des 200 icônes manipulées par le javascript et affichées dans le canvas. En fait, plutôt que la récupération, ce qui posait réellement problème était la demande de confirmation, faite par le navigateur au serveur, que l’image n’avait pas changé par rapport à la version en cache. La taille des images n’importait donc pas, il  s’agissait uniquement de leur nombre.

Il s’agit d’un problème classique dans la conception de sites web, problème qui a une solution également classique maintenant : l’usage de sprites css.

Le problème était dans mon cas légèrement différent puisque mon code javascript ne connaissait pas la notion de css mais manipulait des objets Image. Cependant j’ai décidé de passer par l’usage de css pour deux raisons :

  • ces icônes sont pour la plupart également affichées depuis du code HTML dans d’autres parties de l’application web, parties qui devaient donc bénéficier de la technique des sprites css
  • d’innombrables générateurs de sprites css (à savoir de l’image png et du fichier css associé, à partir d’un lot d’images) sont disponibles sur internet

La solution que j’ai adoptée a donc été celle d’écrire un composant en javascript capable d’analyser le css d’une page pour récupérer les objets Image correspondant aux classes css.

Ce composant, qui dans mon cas précis a permis de passer de 297 requêtes à 28 (et de réduire donc drastiquement le temps de chargement) est disponible là :

https://github.com/Canop/braldop/blob/master/src/gui/SpriteSet.js

Si j’ai une demande, j’en ferai un projet autonome sous github, avec toute la documentation nécessaire.

 

 

Profilage d’un programme en Go

Le Go intègre un package permettant la génération par échantillonnage d’un fichier au format de l’outil d’analyse pprof de Google. C’est surtout pour le tester que je l’ai intégré mais en une demi-heure, recherches et installations comprises, j’ai fini par diviser par plus de 8 le temps d’exécution de l’un de mes programmes…

Pour le profiling de votre programme en Go, vous aurez besoin de Graphviz (un paquet est disponible en standard dans la logithèque d’Ubuntu) et de Google perftools (lequel s’installe très bien sous Ubuntu via deux paquets).

La génération du fichier prof se définit en un seul point du programme (grâce entre autre au defer, lequel est si pratique pour éviter les lourds try/finally du java). J’utilise le package flag pour déterminer si j’active le profiling et pour passer le chemin du fichier :

func main() {
    cpuprofile := flag.String("cpuprofile", "", "fichier dans lequel écrire un bilan de profiling cpu")
    flag.Parse()
    if *cpuprofile != "" {
	fmt.Println("Profiling actif, résultats dans le fichier ", *cpuprofile)
        fp, err := os.Create(*cpuprofile)
        if err != nil {
            log.Fatal(err)
        }
        pprof.StartCPUProfile(fp)
        defer pprof.StopCPUProfile()
    }

Après l’exécution de mon programme mapper, j’accède à l’outil via cette commande :

Diverses commandes (list, top20) sont utiles, mais la plus intéressante est la commande web. Celle-ci construit un graphe svg et l’ouvre dans un navigateur. Ce graphe met clairement en évidence les fonctions qui consomment la CPU. Cliquez sur l’image pour visualiser le graphe complet :

cliquez pour voir le graphe

Quelques mots à propos du programme profilé : il s’agit du générateur de cartographie Braldop. Toutes les 4 heures il analyse quelques milliers de fichiers csv pour construire en mémoire une carte du jeu Braldahim qu’il exporte ensuite sous la forme de fichiers json et png. Je ne m’étais jamais trop préoccupé des performances, puisqu’une dizaine de secondes d’occupation de mon serveur toutes les 4 heures, sans interruption de service, n’avaient rien de catastrophique. Je pensais que la charge devait être liée au parsage de tous ces fichiers csv, d’où ma surprise : le graphe indiquait que 75% du temps était passé dans l’encodage des quelques images png. Et une bonne partie des fonctions appelées correspondaient manifestement à des opérations bien trop lourdes (recherche de filtre, de couleur proche, etc.) pour l’encodage d’images ne comptant qu’une trentaine de couleurs différentes.

J’ai rapidement réécrit les quelques lignes de génération de l’image pour travailler sur une image indexée (la palette étant définie a priori) plutôt que sur une image RGBA. Le test montre que l’image est maintenant générée et encodée en 200 ms (sur la brouette sur laquelle je développe) au lieu de 2 secondes.

Le code final de génération de l’image en go est disponible là :

https://github.com/Canop/braldop/blob/master/src/go/braldop/bra/pngCouche.go

Ce code peut intéresser ceux qui ont l’habitude, par exemple, du java, de ses cascades de streams, de ses try/catch, et de sa verbosité générale.

L’outil pprof ressemble aux autres outils de Google : efficace, simple d’utilisation, très sobre, dénué d’enjolivements, peu documenté mais clair. La lisibilité du graphe permet l’analyse immédiate des problèmes de performance.

 

Des terminaux pour coder

Un poste de développement efficace et agréable suppose un terminal adapté. Mes premiers réglages, sur mes postes linux,  consistent à installer

  • terminator
  • une bonne colorisation
  • scm-breeze

Terminator, tout d’abord, si l’on n’a pas été égaré parmi la myriade de logiciels homonymes, s’installe sans difficulté. Une fois ceci fait, la seconde chose à faire est de lancer man terminator et de mémoriser les quelques raccourcis claviers qui permettent de gérer facilement les terminaux et de naviguer entre eux.

J’ouvre en général une fenêtre Terminator par projet sur lequel je travaille et j’ai pour habitude d’avoir à gauche mes terminaux locaux (compilations, gestion des sources, déploiements, etc.) et à droite des terminaux connectés au serveur sur lequel je déploie (affichage d’état en tail -f, supervision, réglages, etc.).

Une fenêtre typique ressemble à ça :

Une bonne colorisation, ensuite, rend évidente la structure du contenu de la console. Elle marque clairement le début des blocs (important si vous utilisez des commandes à la sortie dense et de longueur variable), met l’accent sur les éléments variables (le chemin courant et la console par exemple) tout en les séparant. La colorisation que je me suis mise au point donne ceci :

Et voici ce que j’ai ajouté (notes d’exploration comprises) dans mon fichier .bashrc à cet effet :

if [ "$color_prompt" = yes ]; then
	# dys 20101117 : j'ai modifié ici les couleurs et les graisses du prompt
	# Mode d'emploi (ce que j'ai trouvé) :
	#  un bloc \[\033[<lumière>;<couleur>m\] change les caractéristiques de la fonte du terminal pour ce qui suit
	#  <lumière> peut être 00 (normal), 01 (couleur violente, un peu comme du gras), 02 (sombre)
	#  <couleur> peut être l'un des codes suivants :
	#  30 black
	#  31 red
	#  32 green
	#  33 yellow
	#  34 blue
	#  35 magenta
	#  36 cyan
	#  37 white
	#  40 black
	#  41 red
	#  42 green
	#  43 yellow
	#  44 blue
	#  45 magenta
	#  46 cyan
	#  47 white
	#  Notons qu'on peut également ajouter du soulignage, changer le fond, inverser, etc.
    PS1='${debian_chroot:+($debian_chroot)}\[\033[00;34m\]\u@\h\[\033[01;34m\]:\[\033[00;37m\]\w\[\033[01;34m\]>\[\033[00;37m\] '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi

scm-breeze, enfin, intègre au shell quelques raccourcis pour git et permet la désignation rapide des fichiers. La séquence suivante montre l’exemple de l’utilisation de gs (git status) suivi de ga (git add) et d’un commit :

Vous voyez que la séquence est la suivante :

gs
ga 2..5
git commit -am "message"

Taper ga 2..5 est tout de même plus rapide que de compléter tous les chemins…

Notez qu’il existe aussi des raccourcis pour les commit mais qu’ils ne me satisfont pas. J’ai forké le projet, si j’ai le temps je regarderai…

scm-breeze ajoute d’autres fonctions. J’utilise fréquemment les commandes gps (git push) et gco (git checkout) ou la commande source qui permet de changer de projet. Mais je vous invite à consulter la page du projet sur github pour en avoir un descriptif plus complet ou, mieux, à l’installer.