Si vous avez été à une ou deux présentations de git, vous avez sans doute déjà vu ce diagramme (ou un équivalent).

Après avoir un peu pratiqué git, ce genre de workflow devient naturel, et est particulièrement appréciable :

  • les fonctionnalités ont chacune leur branche dédiée : la lecture de son historique n’est donc pas gêné par le reste des développements
  • les livraisons peuvent être préparées sans empêcher les personnes qui travaillent sur les futures fonctionnalités de partager leur travail
  • etc.

Voila pour la théorie : sur le papier, ça laisse absolument rêveur ! Mais dans la pratique, tout ne fonctionne pas aussi bien, surtout au début. Et c’est là que commence mon retour d’expérience sur la mise en place de ce workflow.

Git est un outil complexe

Lorsque l’on met git en place avec une équipe qui avait l’habitude de travailler avec SVN, on ne peut pas s’attendre à ce qu’un tel workflow soit mis en place du jour au lendemain : il faut d’abord commencer par apprendre à l’équipe les bases de l’utilisation de git. Car si c’est un outil extrêmement puissant, il ne faut pas perdre de vue qu’il est aussi objectivement beaucoup plus complexe à utiliser. Il n’y a qu’à vérifier sur ce petit pense-bête des commandes pour s’en rendre compte :

  • On passe d’une communication entre un workspace et dépôt à un système de quatre « zones » (et encore, je ne parle même pas du stash) : workspace, index, dépôt local, dépôt distant.
  • L’équivalent d’un commit SVN, c’est commit + push. L’équivalent d’un checkout SVN, c’est fetch + checkout (ou pull). etc. Bref, plus de commandes à apprendre !

Il y a donc nécessairement un temps d’adaptation. L’idéal serait évidemment de pouvoir arrêter le développement le temps de véritablement former l’équipe à git, sur quelques jours à temps plein. Dans la pratique, ce n’est souvent pas possible, et on se retrouve à devoir apprendre petit à petit sur le tas.

C’est à ce stade que les formations ne sont vraiment pas un luxe. Personnellement, ce sont les formations sur une journée complète de Christophe Porteneuve qui m’ont permis de véritablement apprendre à maîtriser git. Pour ceux que cela intéresse, la prochaine formation, c’est samedi en huit à Paris. Et 100€ pour une journée de formation, déjeuner compris, c’est donné vu tout ce qu’on y apprend. Note : pour ceux qui se posent la question, je ne touche strictement rien pour les publicités que je lui fait. J’encourage ces ateliers parce que je les trouve excellents ! Et vu le prix des formations par rapport au temps qu’il y passe, je ne le soupçonne pas de faire ça pour l’argent non plus…

Les habitudes SVN

Tous les manuels le disent : il ne faut pas penser comme en SVN, et ne surtout pas se dire « quel est l’équivalent de cette commande SVN en git ? ». C’est bien beau de le dire, mais dans la pratique, faute d’avoir le temps de commencer par une formation complète, ça commence souvent comme ça quand même !

On se retrouve donc avec deux habitudes que l’on reproduit :

  • la trunkite : dans SVN, la gestion des branches est tellement catastrophique que souvent, on ne les utilise même pas. Quand on arrive dans git, on n’a donc pas le réflexe de créer des branches, et on développe dans master, qui devient le « git trunk ».
  • la pushite : avec SVN, les commits intermédiaires d’une fonctionnalité sont généralement interdits car ils cassent le build. On développe donc toute la fonctionnalité, avec d’effectuer un unique gros commit, que l’on le partage immédiatement à l’équipe par un push.
  • la pullite : il s’agit de la peur de ne pas être à jour par rapport à ce que font les camarades, et donc d’effectuer très régulièrement des pull, pour remettre à jour sa branche.

On utilise donc git comme si c’était un SVN. Et à plusieurs, il arrive souvent que les push soient refusés pour cause d’absence de mise à jour préalable. Qu’à cela ne tienne : « git pull + git push » résout le problème ! A ceci près que git conserve ces fusions dans son arbre. Quand il y a un merge, ça va… mais quand plusieurs développeurs commencent à faire la même chose en parallèle, on a vite un arbre qui ressemble à ça :

Workflow Subversion utilisé dans Git

Workflow Subversion utilisé dans Git

Le problème, c’est que dans ce workflow, les micro-merges de chacun ne correspondent pas à de véritables fusions fonctionnelles. On se retrouve donc avec un graph dont la compréhension nécessite parfois quelques aspirines. Paradoxalement, on se retrouve dans une situation où la gestion de configuration est plus difficile à lire que l’équivalent SVN, ce qui est l’inverse du but recherché.

… dont on se soigne !

Heureusement, après quelques temps et un peu de formation, la maîtrise des commandes progresse, ainsi que la compréhension des avantages de git et la méthode de travail. En particulier, voilà quelques réflexes qu’on acquiert :

Commiter régulièrement

C’est un des premiers réflexes à acquérir. Les commit étant locaux, le commit d’un « morceau de fonctionnalité » ne gènera pas le reste de l’équipe et ne cassera pas le build tant qu’il ne sera pas partagé. Ca permet d’avoir plus de commits par fonctionnalité, avec de nombreux avantages :

  • un suivi plus fin du développement, et une meilleure compréhension pour le reste de l’équipe de l’enchaînement des étapes de développement
  • une identification plus facile du commit ayant entrainé une régression quand il y en a (recherche qui peut être automatisée avec le surpuissant git bisect)

Cette évolution de la méthode de développement va évidemment de pair avec l’arrêt de la pushite, puisqu’on risquerait là de partager une évolution incomplète cassant le build, et de se faire taper dessus par le {reste de l’équipe/chef de projet/scrum master} (rayer les mentions inutiles).

Créer des branches

Avant tout début de développement (fonctionnalité, refactoring, etc.), le réflexe à avoir est de créer une branche dédiée, avant même de commencer à réfléchir à ce que l’on va coder.

Petite astuce : avec quelques commandes git, si on a oublié de créer une branche, on peut le faire à postériori. Supposons que j’ai fait 3 commits dans master, que j’aurais du faire dans une branche dédiée feature :

1
2
3
git branch feature # Création de la branche manquante
git reset HEAD~3 # La branche master revient là où elle devrait être, ni vu ni connu…
git checkout feature # Retour sur la branche et poursuite des développements…

Une fois cette habitude prise, la pullite n’est plus un problème : rien n’empêche de mettre à jour master (aussi souvent qu’on le souhaite), en fast-forward, tout en continuant ses développements dans la branche feature.

On ne fusionne sa branche de développement dans master que lorsque l’évolution est terminée :

Git workflow

Git workflow

Résolution des conflits

Le workflow précédent est déjà nettement plus lisible dans l’historique git. Cependant, il présente un inconvénient : si l’évolution est complexe, on travaille « en aveugle » par rapport au reste des développements jusqu’à la fusion finale. Si différentes évolutions impactent les mêmes classes, la fusion finale peut être difficile.

D’où l’idée de résoudre les conflits par avance. Pour cela, il y a un moyen simple : réaliser régulièrement des fusions de la branche master vers sa branche de développement :

Git workflow avec fusions intermédiaires

Git workflow avec fusions intermédiaires

Cela permet de résoudre les conflits au fur et à mesure qu’ils apparaissent, et ainsi de ne pas avoir à tout résoudre d’un seul coup à la fin.

En route vers git ReReRe

Ce chapitre est naturellement dédié à Nicolas, suite à une discussion que nous avons eu à la What’s Next la semaine dernière (juste avant qu’il ne s’enfuie pour mettre à jour une présentation…).

Certaines fusions de master vers la branche de développement sont justifiées, quand par exemple, il faut intégrer des correctifs qui impactent les développements en cours. Mais si ces fusions n’ont pour but que de résoudre par avance les conflits, elles sont un peu artificielles et « polluent » l’historique du projet.

Mise en place

Or, il existe un moyen d’avoir une résolution par avance, sans pour autant conserver ces fusions. Il s’agit de la fonctionnalité ReReRe de git (Reuse Recorded Resolution) : son principe est d’enregistrer par avance la résolution du conflit, afin que git l’utilise automatiquement lors de la fusion finale.

Pour activer cette fonctionnalité, il faut l’ajouter à la configuration de git (soit pour un projet, soit de manière globale). Ca ne coûte pas grand chose : c’est donc maintenant quelque chose qui fait partie de ma configuration .gitconfig de base (avec quelques alias et autres choses…). D’autant plus que cela se fait en une commande :

1
git config --global rerere.enabled true

Utilisation

Cette configuration en place, si on utilise la ligne de commandes, on remarque l’apparition de nouvelles lignes lors des fusions contenant des conflits à résoudre :

Recorded preimage for ‘fichier_en_conflit’

Et, une fois la résolution effectuée, au moment du commit :

Recorded resolution for ‘fichier_en_conflit’

Git a donc enregistré la manière dont on a géré le conflit, et sera capable de la rejouer manuellement. Vérifions cela immédiatement. Juste après ce commit, on exécute :

1
2
git reset --hard HEAD^ # Annulation de la fusion que l'on vient d'effectuer
git merge branche_en_conflit

Git indique alors :

Resolved ‘fichier_en_conflit’ using previous resolution.

Le commit n’est pas fait : mais si on regarde les différents fichiers en conflit, on s’aperçoit que la résolution précédente a été appliquée automatiquement. Après vérification de celle-ci, il suffit de valider à nouveau le commit.

Pour le cas qui nous intéresse…

Annuler une fusion pour la rejouer immédiatement après, l’intérêt est limité. Mais ces résolutions sont conservées dans le temps. Ainsi, on peut pré-enregistrer la résolution des conflits entre sa branche et master, annuler la fusion, et continuer à travailler.

La résolution qu’on a enregistrée ne sera appliquée qu’au moment de la fusion finale, quand la fonctionnalité sera entièrement développée.

Concrètement, voici la manière de procéder :

1
2
3
4
5
6
7
8
9
10
git merge master
# Résolution des conflits
git add fichier_en_conflit
git commit -m 'Pré-résolution des conflits avec master'
git reset --hard HEAD^ # Annulation du commit, mais en conservant la résolution
# Suite de l'évolution dans la branche feature…
# Nouvelle pré-résolution de conflit si l'évolution est longue (il n'y a pas de limite).
# Une fois l'évolution terminée, intégration dans la branche principale :
git checkout master
git merge feature # Utilisation des résolutions pré-enregistrées

Informations supplémentaires

Quelques informations complémentaires concernant « git rerere » :

  • si on travaille à plusieurs sur la branche feature (en la partageant sur le dépôt central) : les enregistrements de résolution sont partagés entre les membres de l’équipe . Ainsi, même si j’annule le commit après le pré-enregistrement d’une résolution, les développeurs travaillant sur la même branche bénéficieront des résolutions automatiquement lorsqu’ils feront à leur tours des fusions avec la branche master.
  • les résolutions enregistrées s’appliquent automatiquement dès que le conflit est à nouveau rencontré, et pas seulement pour les fusions. Ainsi, si par la suite je décide de faire un rebase sur la branche master de mon évolution plutôt d’une fusion, les conflits pour lesquels une résolution est connue seront automatiquement gérés.
  • mise en garde pour les cas extrêmes de développements très longs : les pré-résolutions ne sont conservées qu’une durée limitée. Par défaut, cette durée est de 60 jours : c’est généralement suffisant, mais si ça ne l’était pas, il est bien sûr possible d’augmenter ce temps dans la configuration git.

Vu la puissance de cette fonction, il est dommage qu’elle soit assez méconnue. On peut cependant comprendre pourquoi elle n’est pas activée par défaut : il est préférable de bien en comprendre l’intérêt et le mode de fonctionnement du ReReRe avant de l’utiliser. Il n’est en effet pas naturel au début de fusionner et de commiter… pour annuler ce commit immédiatement après !

Comme toutes les commandes git, celle-ci dispose de toute une batterie d’options dans lesquelles on peut se plonger pour aller encore plus loin, avec le manuel officiel.

Conclusion

Voici donc comment on peut monter petit à petit un workflow d’utilisation de git sur un projet qui utilisait préalablement SVN. Pour ceux qui se poseraient la question, ce n’est pas que de la théorie : cet article est le fruit d’une discussion avec Nicolas à la What’s Next d’un véritable retour d’expérience sur un projet où nous avons « switché » de SVN à git (en particulier sur les habitudes SVN qu’il n’est pas évident de perdre).