svn

[Développement] Git, pourquoi, comment, que nous arrive-t-il?

posté le 27 July 2014 à 16:25

Bon, la masse crasseuse plèbe ingnorante foule avide de connaissance ayant exprimé un cerain intêret pour Git, je me vois contraint d'expliquer pourquoi je ne reviendrais pas en arrière depuis que je m'y suis converti (pourtant pas très motivié à la base) depuis le mois de janvier dernier. Attention, j'ai toujours pas appris à faire court et à m'empêcher de faire des digressions, ça va donc être long.

Commençons par le commencement

Déjà, il faut savoir que Git, comme SVN avant lui et CVS encore avant, est gratuit et open-source. C'est aussi le cas du plugin Eclipse correspondant, c'est déjà une bonne chose.

Deuxième point important, il y a une référence absolue pour Git, à savoir le livre "Pro Git" écrit par Scott Chacon (un des fondateurs de Github), dispo évidemment en édition papier chez tous les bons libraires, mais aussi gratuitement en ligne en anglais là: http://git-scm.com/book et même en français (je l'ai pas lu, donc je garantie pas la qualité de la traduction ni le fais que ce soit à jour) ici: http://git-scm.com/book/fr

Le "livre" fais 9 chapites, mais le premier est une introduction + historique des SCM, et si vous avez lu les chapitres 2 et 3 vous êtes déjà capables d'utiliser 80% des fonctions les plus utiles de Git.

Troisième point important: oui, l'outil est "pensé" comme un outil en ligne de commande (n'oublions pas qu'il a été pensé et implémenté par les développeurs du kernel Linux, on pouvait difficilement s'attendre à autre chose de leur part), c'est pour ça que Pro Git ne fera quasiment référence qu'à des lignes de commande. Et je conseille de commencer par là, ça permet de se familiariser avec les concepts (un peu différents mais pas trop) de Git sans s'embrouiller avec des interfaces graphiques proposant toutes les options dont vous n'avez que faire.

Encore une fois, dès la phase d'apprentissage et de migration terminée, vous n'y reviendrez (presque) jamais.

Ok, mais en quoi c'est mieux que SVN ou CVS ou ...

Les anciens de Git vont tous vous faire la même réponse: parce que c'est décentralisé. Et c'est une réponse de merde, déjà parce que si vous n'êtes pas un minimum callé sur le "comment ça fonctionne" des SCM cette réponse ne vous éclaire pas du tout, mais aussi parce qu'à moins de travailler sur un projet open-source massif (disons au hasard: comme le kernel linux) il est conseillé de garder une notion de centralisation. Je vais y revenir plus tard.

L'autre argument qu'on vous sortira souvent c'est "parce que c'est facile de brancher/merger vu que chacun a son propre repository" (en français on dit "dépôt", mais je n'arrive pas à me faire à ce terme, donc vous devrez supporter de me voir dire "repo" à chaque fois, et ne vous plaignez pas, à l'oral c'est encore pire). Là, y a déjà quelque chose de plus intéressant si vous êtes déjà utilisateurs d'un SCM comme CVS ou SVN. Sauf que si vous avez déjà fais un peu joujou avec les notions de branch et de merge en SVN, ça devrait provoquer chez vous une réaction entre le sourire narquois et le rictus figé de terreur.

Décentralisons, décentralisons, il en restera toujours quelquechose (Dixit un certain Defferre)

Décomposons la phrase, en commençant par la fin: "vu que chacun à son propre repository".

Les ancêtres centralisés ont une faiblesse: le repo n'existe qu'à un endroit, et à moins de se lancer dans ces procédure de sauvegardes régulières du repo, une crash de la machine hébergeant le repo peut être une catastrophe. Sachant que sauvegarder le repo n'est qu'une demi solution (toute personne ayant déjà essayé de rétablir un environnement de travail collaboratif pour une équipe de 10 personnes à partir de la sauvegarde vieille d'une semaine d'un repo SVN comprendra). Disons que si ça vous arrive, attendez vous à perdre une deuxième semaine à régler des conflits lors des prochains commit.

Git a cette spécificité que chaque utilisateur dispose de son propre repo. Oui, oui, un repo complet, avec tout l'historique, et un certain nombre de branches (mais pas forcément toutes pour le coup). Il se trouve qu'en dehors de la première personne (appelons-le Adam) à créer le repo pour un projet, tous les autres vont "cloner" un repo existant (pas forcément celui créé par Adam d'ailleurs).

J'ai nommé Adam parce que je compte lui donner un rôle un peu à part des autres utilisateurs: admin du repo central. Et là vous me dites: tu te fous de notre gueule. Alors non, déjà parce que je suis poli, et vous pourriez me rendre la politesse. Ensuite parce qu'en entreprise, contrairement aux projets open source, on a souvent besoin d'une référence unique (par exemple pour faire tourner un serveur d'intégration continue, pour produire la version release, etc). La force de Git c'est que chacun a un repo, ce qui n'exclue nullement que l'un de ces repo (au hasard; celui qui est sur le serveur d'intégration) fasse office de référence pour différentes tâches. D'ailleurs même pour les projets open-source, il y a souvent une référence (pour le kernel linux, je serais vous je me pencherais du côté de monsieur Torvalds par exemple).

Donc voilà, Adam, premier utilisateur, a créé un repo pour le nouveau projet (ou pour un projet déjà en cours, mais on y viendra plus tard sinon on s'en sors plus). Mon conseil, c'est que ce repo soit sur un serveur visible du reste de l'équipe et serve pour toutes les tâches d'intégration continue, production de release, suivi des rapport de bugs, etc. Et SURTOUT PAS de repo de travail de développement pour Adam.

Adam, comme tous les autres membres, va se faire un repo de travail à lui, dans son environnement de développement, en "clonant" un repo existant. Bon, pour faire simple, on va dire qu'il clone le repo initial. Mais son pote Benjamin qui arrive juste après lui, a le choix entre cloner le repo initial ou cloner le repo d'Adam. Ca change assez peu de choses au final, mais pour faire simple disons que tout le monde clone le repo initial.

Donc maintenant, chaque utilisateurs à un repo cloné à partir d'un autre repo... so what?

Ben déjà, si jamais un repo disparait (mettons le plus critique: celui du serveur, mais même simplement parce qu'un membre a vu son HDD flamber, ou s'est fait volé son portable, ou bien vient d'acheter une nouvelle machine), il est extrêmement simple de rétablir la situation très vite: il suffit de cloner un des autres repo existants et voilà, on est prêts à travailler en 2 temps 3 mouvements. Plus besoin de sauvegarde externe: tout utilisateur est une sauvegarde à lui tout seul. La date de sauvegarde correspondant à sa dernière connection avec le repo disparu (donc pour le repo central: en général moins d'une journée).

Autre avantage: vous pouvez travailler déconnecté. C'était absolument obligatoire pour les développeurs du kernel linux qui ne veulent pas (et pour bon nombre: ne peuvent pas) être connectés à chaque fois qu'ils ont un bout de code dans un état satifaisant dont ils veulent garder une trace d'historique pour ne pas la mélanger au bout de code suivant. Pareil pour vous: vous êtes en déplacement pro, votre portable sur la tablette du train qui vous emporte dieu sait où? Vous êtes du genre "télétravail" à bosser 1 semaine chez vos amis dans le Larzac, loin de toute connection digne de ce nom? Pas de soucis, vous avez votre propre repo, vous pouvez donc faire des commit, faire des branches, faire des merge, etc.Evidemment, quand vous retrouverez la civilisation faudra resynchroniser un peu tout le monde, mais vous verrez que ça se fait plutôt bien.

Branch, Merge, historique

Allez, on tourne autour depuis tout à l'heure, maintenant on y va: l'autre avantage, c'est que vous pouvez faire des branches (plein, autant que vous voulez) et des merge (pareil) à tout va.

Bon, là va falloir me croire sur parole parce que sinon on y passe l'année: Git gère les merge de branche 1000 fois mieux que SVN. Pourquoi? Bonne question, je n'ai pas la réponse exacte mais je suppose que ça tient surtout à 2 facteurs:

1) la façon dont Git stock les différents états de l'historique dans le repo: contrairement à SVN, il ne stock pas uniquement les delta de modification d'un état à un autre d'un fichier, mais bien la totalité du fichier dans son nouvel état. Ca devrait provoquer une montée en taille abominable des repo (c'est pour des considérations de taille du repo que SVN a adopté cette stratégie des delta), ce n'est pas le cas, je ne sais pas pourquoi. Et puis à l'heure actuelle le Go coûte rien, donc même si votre repo fait 5Go, c'est pas vraiment un problème.

2) du fait de la facilité de faire des branches et de la facilité et rapidité de passer de l'une à l'autre, on fait des branches plus souvent, qu'on merge plus souvent, donc les risques de conflits sont plus limité. De mon expérience (environ 5 ans) de SVN, on fait des branches pas trop souvent (genre pas plus d'une par semaine, voir moins) et les merge sont chaque fois une épreuve qu'on repousse autant que possible. Avec Git, vous prendrez vite l'habitude de faire des branches, potentiellement plusieurs fois par jour, qui seront parfois ré-intégrées dans la même journée

Si vous êtes comme moi, vous me dites: OK, mais chaque branche est une copie complète du projet qu'il faut mettre à un endroit différent du disque, ça doit être un bordel monstre, en plus de prendre une taille folle sur les disques, et je parle pas de passer de l'une à l'autre avec mon environnement de développement.

Ben non. Parce que Git est sympa, et qu'il gère toutes les branches au même endroit. Je m'explique: vous avez votre répertoire de développement: C:développementmon_projet . Et bien tous vos travaux auront lieux dans cet unique répertoire (et ses sous-répertoires).

Vous avez besoin de travailler sur une nouvelle fonctionnalité? Vous demandez à Git de vous faire une nouvelle branche à partir de l'état actuel du tronc (ou de n'importe quelle autre branche), et de basculer dessus. L'état du projet dans C:développementmon_projet est celui sur lequel vous travaillez.

Mais un patron vous appel en urgence, il faut un fix de sécurité sur la branche de maintenance de la dernière release 2.4... pas de soucis, vous demandez à Git de vous mettre de côté les modifications en cours (ce que vous étiez en train de faire en tout dernier mais pas encore en étant d'être historisé tel quel), puis vous demandez à basculer sur une nouvelle branche "hotfix" créée à partir de l'état courant de la branche de maintenance de la release "2.4". L'état du projet dans C:développementmon_projet est maintenant celui de la branche de lreease 2.4. Vous travaillez, faites des commit, etc. Quand c'est bon, vous intégrez votre branche à la branche de release (ou vous demandez au responsable de cette branche de le faire), puis vous demandez à Git de revenir sur votre branche de fonctionnalité. L'état du projet dans C:développementmon_projet est à nouveau celui dans lequel vous l'aviez laissé avant d'être interrompu, moins vos dernières modifications non commitées. Mais il suffit de demander à Git de vous les rendre, et voilà.

Sachant que chaque "basculement" d'une branche à une autre est affaire de secondes (moins de 2 pour mon précédent projet d'un millier de fichiers sur un SSD, pour exemple).

Et chaque basculement se fait en 1 commande en ligne ou quelques clics (genre 3 ou 4, pas plus) dans Eclipse. Et vous n'avez rien d'autre à faire, puisque tout à lieu dans le même unique répertoire, donc tous vos outils externes (Eclipse en particulier, mais aussi vos scripts de compilation, etc) vont continuer à fonctionner sans soucis.

Et donc en sachant cela, on hésites plus à créer une branche, même pour 5 minutes, même pour modifier un seul fichier, une seule ligne au besoin. Et d'un point de vue traçabilité de l'historique, c'est un vrai plus. Si ça vous est déjà arrivé de devoir chercher quand un bug a été introduit et ce qui l'a introduit, vous me comprenez. Avec Git, il est TRES facile de tester différents points de l'historique (tout simplement en demandant à Git: bascule mon projet sur une branche créée à partir de l'état du tronc tel qu'il était hier/avant-hier/etc, compiler et tester si le bug est déjà présent ou non) puis d'identifer pourquoi il a été introduit (= comment s'appelle la branche qui a introduit le bug lors du merge? et que dit le message du commit qui a introduit le bug dans cette branche?). Evidemment, ça implique de donner des noms "parlant" à vos branche (donc pas "ma_branche" mais plutôt "fonction_tri_par_date") et de mettre des messages de commit un peu parlant ( pas "Work In Progress" mais "modification de l'aglo de tri").

Oui parce qu'en plus, comme vous êtes dans votre propre repo, vous n'hésitez pas à faire beaucoup, beaucoup plus de commits, et donc les messages deviennent plus faciles à écrire, parce qu'ils sont courts et résument juste ce que vous avez fait ces 2 dernières heures en gros. Ca aussi c'est bien pour la traçabilité (et donc la maintenabilité).

Ok, mais les conflits?

Bon, on va pas se mentir: des conflits vous en aurez, forcément. A moins de bosser tout seul, ou d'avoir une séparation des tâches et des domaines absolument parfaite, il y aura des moments où 2 personnes auront modifié le même fichier. Mais déjà comme je le disais précédemment, Git gère ça plutôt mieux que SVN, et donc régulièrement Git se contendra de vous dire "ouais, j'ai vu, Benjamin et Carlos ont modifié le même fichier, mais c'est pas les mêmes lignes donc ça va, je gère". Mais même ça, ça arrivera pas souvent, pour une raison simple: chaque développeur s'occupe de résoudre ses éventuels conflits AVANT d'intégrer sa branche.

Allez, déroulons un scénario:

1) Adam a créé un repo pour le projet, et ajouté un fichier nommé source.txt et contenant 2 lignes de texte:

debut

fin

2) Benjamin et Carlos on tous les deux cloné le repo initial, et ont chacun fait une branche "fonction 1" (pour Benjamin) et "fonction 2" (pour Carlos) et ajouté une ligne entre les deux existantes : "ma ligne" dans un cas, "mon autre ligne" dans l'autre.

3) Benjamin a fini le premier. Et là on entre dans la partie intéressante: il COMMENCE par vérifier que son travail ne créé pas un conflit avec l'état ACTUEL du tronc en faisant un merge de la branche principale (elle s'appelle "master" par defaut, mais c'est l'équivalent du "trunk" SVN) dans sa branche à lui. Comme la branche master n'a pas changé depuis la création de sa branche à lui, Git lui indique que l'intégration s'est faite sans soucis, et il peut maintenant:

a) intégrer directement sa branche "fonction 1" dans la branche master DE SON REPO A LUI puis signaler au serveur central que la branche master du repo initial devrait être remplacée par la branche master de son repo à lui, et seulement si Adam lui a donné les droits d'écritures nécessaire

OU

b) signaler à quelqu'un ayant les droits d'écritures nécessaires sur le serveur (disons Adam) d'intégrer la branche "fonction 1" de son repo à lui dans la branche "master" du repo initial. Adam étant consciencieux, il commencera par créer une branche sur le repo initial à partir de l'état de la branche master, essaiera d'y intégrer la branche "fonction 1" du repo de Benjamin, et s'il n'y a pas de conflit, reproduira l'opération sur la branche master

4) Maintenant Carlos a fini aussi. Mais lui c'est un fainéant (saligaud de basanés), donc il se fait pas chier, il demande direct à Adam d'intégrer sa branche "fonction 2" dans la branche master du repo initial. Adam fait une branche depuis son master qui contient déjà l'intégration de la branche "fonction 1", essaie de faire un merge, arrive à un conflit: il refuse l'intégration, signalant le problème à Carlos.

5) Carlos doit donc mettre à jour la branche master de son repo par rapport à la branche master telle qu'elle est dans le repo initiale PUIS faire un merge de la branche master de son repo dans sa branche "fonction 2". Git lui signale le conflit sur le fichier "source.txt". Carlos ouvre le fichier et voit:

debut

<<<<< theirs zer54ez867654zer

ma ligne

=======

mon autre ligne

>>>>> mine

fin

6) il résoud le conflit (au besoin en contactant Benjamin pour savoir exactement à quoi correspond la ligne en conflit, savoir s'il la garde, etc) EN LOCAL. Une fois le conflit résolu (git va créer un commit supplémentaire dans l'historique correspondant à ce merge de la branche master et à la résolution du conflit), Carlos peut redemander l'intégration de sa branche dans master.

En conclusion de ce long (beaucou trop) paragraphe, il faut surtout retenir que le "gestionnaire" du repo central n'a pas pour tâche de résoudre tous les conflits (raison n°1 pour laquelle ma précédente entreprise refusait de passer à Git), mais que chaque membre doit s'assurer de la bonne intégration de sa branche avant de la proposer.

Mais encore une fois, Git est là pour vous aider, car chaque développeur peut comparer et intégrer sa/ses branches non seulement avec celle(s) du repo central, mais aussi avec les branches des repo de chaque autre développeur!

Mettons que vous avez un développement en cours sur une fonctionnalité un peu compliquée, ça fait bien 3 jours que vous êtes dessus, et il vous reste au moins 2 ou 3 jours de boulot à vue de nez. Sauf qu'entre temps la branche master progresse, et qu'hier un autre développeur a fourni la correction d'un bug critique pour votre fonctionnalité qui a été intégrée à la branche master du repo central. Vous avez plusieurs options:

1) intégrer directement la branche master du repo central dans votre branche. Avantage: vous restez synchro avec la branche master, donc les risques de conflits à la fin, lorsque vous voudrez intégrer votre branche sont diminués d'autant (et au pire vous les résolvez au fur et à mesure ça devrait êytre plus simple). Inconvénient: vous récupérer toutes les autres modifs, et ça vous arrange peut-être pas pour le moment.

2) intégrer uniquement la branche du développeur contenant le correctif. Avantage; vous ne récupérez que la branche nécessaire, donc moins de risque de conflits et de "pollution" par des modifications qui n'ont rien à voir. Inconvénient: Et les conflits lorsque vous allez vouloir intégrer "master" alors? Est-ce que les modifications ne vont pas être faites 2 fois, provoquant des conflits systématiques? Ben non, Git est un malin, il va "reconnaitre" les commits de la branche en question et ne pas les re-intégrer, et hop, pas de conflits. Mais intégrer une branche complète, c'est potentiellement encore trop.

3) intégrer uniquement le (ou les) commit contenant le correctif. Oui, Git permet d'aller chercher un unique commit dans une autre branche et de l'intégrer dans la branche courante. Et bien sûr, il identifiera correctement tout ce petit monde plus tard quand vous intégrerez la branche master et ne le rejouera pas.

Souplesse maximale de votre workflow, et tout ça toujours en local, donc sans emmerder personne et sans squatter de ressources communes (ça vous est déjà arrivé d'avoir un commit SVN "bloqué" pendant 1h parce qu'un collègue a commité par erreur un fichier dump de 2Go? Moi oui, et pendant ce temps là vous ne pouvez plus faire aucun commit). Bien sûr, il faut un minimum de communication réseau pour "récupérer" une branche d'un autre repo, mais c'est assez rapide.

Oui mais nous on a 10 ans d'historique SVN et une demi-douzaine de branches, comment qu'on fait?

Vous ne mettez pas la mode au pays, Git exitse depuis déjà quelques années, donc ce cas de figure est connu et couvert depuis longtemps. Git intègre donc un outil "git-svn" permettant des communications entre un repo Git (le vôtre, en local) et un repo SVN "central". Je ne vais pas m'étendre parce que sinon je finis plus, mais cet outil permet entre autres de migrer un repo SVN en repo Git (branch et tag compris), mais également des trucs un peu dangereux, comme se faire un repo Git en local pour travailler avec une équipe qui utilise toujours un repo SVN central (essentiellement dangereux parce que votre repo local gère beaucoup plus de branches que ce que le repo SVN peut gérer, et que la gestion des numéros de commit est radicalement différentes).

C'est précisement dans ce contexte que j'ai découvert Git (migration d'un repo SVN en repo Git), ça m'a pris une petite semaine d'écrire le script qui va bien pour tout bien récupérer, sachant que le reste de l'équipe continuait de bosser et de commiter sur le repo central SVN en même temps et qu'ils s'étaient tous créé des comptes Github avec des identifiants différents (donc qu'il fallait modifier les commit SVN au fure et à mesure pour changer l'attribution du commiter). Et que je suis pas un expert du script shell. Donc c'est vraiment à la portée de n'importe qui ou presque (on exclue tata michèle par principe, mais à part ça).

Tout ça c'est beau, mais c'est trop technique pour être vendu à mon incompétent de directeur

Je ne peux que vous conseiller d'aller regarder du côté des outils graphiques comme "giggle" ou "gource" qui permettent de faire des rendu graphique de l'historique d'un repo Git, ça peut faire des visuels convaincant pour une présentation powerpoint. Et ça peut aussi servir pour de vrai (enfin pour gource j'ai un doute, mais giggle je m'en sers régulièrement pour vérifier l'état des différentes branches de mon repo et de celui de mon collègue).

 

Allez, j'ai la flemme de me relire mais je suis sûr que j'ai très mal expliqué un tas de trucs, donc allez-y, posez vos questions!