"Les cours de neeko.fr"

Retour en haut

Les outils de gestion de versions (VCS)

Les outils de gestion de versions

Le meilleur ami du programmeur.

Principes

Les outils de gestions de versions permettent de sécuriser l'évolution d'un logiciel en gardant un historique des modifications, et de simplifier le travail collaboratif.

Quelques logiciels

Il existe de nombreux outils de gestions de version propriétaires. Les outils open sources sont utilisés pour gérer les grands projets open-sources.

Voici les outils de gestion de version "open-source" les plus communs :

Tous ces outils sont capables de gérer des projets de toutes tailles, dans n'importe quel langage de programmation.

La plupart sont des logiciels à installer sur la machine de travail et sur un serveur distant.

Il existe 2 grands types d'outils : centralisés et distribués.

Mise en situation

Voici un scénario de travail collaboratif sans outils de gestion, avec SVN (centralisé) et avec GIT (distribué) :

Méthode sans outils spécialisé (FTP, mails)

Pierre initie le projet

Pierre crée un dossier et commence à coder sur sa machine.

Pierre travaille

Pierre ajoute des fichiers, en modifie d'autres, etc.

Paul rejoint le projet

Pierre envoie les fichiers du projet à Paul par mail ou par FTP.

Paul étudie le code existant.

Pierre travaille

Pendant ce temps, Pierre ajoute des fichiers, en modifie d'autres, etc.

Paul travaille

De son côté, Paul ajoute des fichiers, en modifie d'autres, etc.

Il envoie les fichiers à Pierre... qui a modifié entre temps des fichiers...

Pierre travaille

Ca commence à ce compliquer : Pierre tente de récupérer "à la main" les modifications de Paul pour les intégrer aux siennes en lui demandant sur quels fichiers il a travaillé...

Paul travaille, Pierre travaille, Paul... etc.

Il continuent ainsi pendant plusieurs jours. De nombreux problèmes se posent : certaines lignes de codes sont oubliées, certains fichiers sont écrasés sur le FTP, ils créent des dizaines de dossiers pour garder une trace des différentes versions.

Orage !

Le projet est sur plusieurs machines en même temps, mais on ne sais pas forcement quelle est la bonne version. On risque donc de perdre une partie du travail.

Moralité

Cette méthode est intenable, même pour des petits projets. Le nombre de bugs peux exploser, et on peut perdre à tous moment une partie du travail. Au plus il y a de développeurs, au plus c'est problématique. La seule garantie de la qualité est la rigueur des participants, ce qui n'est pas acceptable.

Avec Subversion (SVN)

Pierre initie le projet

Pierre crée le dossier du projet (vide) sur son serveur SVN. C'est la révision 0.

Pierre checkout (récupère) le projet vide sur sa machine.

Pierre travaille

Pierre ajoute des fichiers, en modifie d'autres, etc.

Il commit (envoie) régulièrement ses modifications sur le serveur. A chaque fois, le serveur stocke la différence entre les fichiers, et augmente le numéro de révision : 1, puis 2, etc.

Le commit nécessite d'être connecté au serveur.

Paul rejoint le projet

Paul checkout (récupère) le projet à sa dernière révision (2).

Paul étudie le code existant.

Pierre travaille

Pendant ce temps, Pierre ajoute des fichiers, en modifie d'autres, etc.

Il commit (envoie) régulièrement ses modifications sur le serveur. A chaque fois, le serveur augmente le numéro de révision : 3, 4, 5, etc.

Paul travaille

Paul ajoute des fichiers, en modifie d'autres, etc.

Paul tente de faire un commit, mais le serveur refuse, car Paul n'est pas "à jour" : il a travaillé sur la révision 2, alors que le serveur est à la révision 5.

Paul fait un update (mise à jour), qui lui permet de récupérer les révisions manquantes...

Comme SVN sait exactement quelles lignes ont été modifiées, il merge automatiquement les modifications s'il le peut, ou indique à Paul les lignes qui lui posent problème.

Paul résout manuellement les conflicts et peut faire son commit.

Pierre travaille

Pendant ce temps, Pierre fait régulièrement des update et commit pour être à jour et travailler sereinement.

Orage !

Si les machines de Pierre et Paul sont détruites, pas de problème : il suffira de faire un nouveau checkout du projet, pour continuer à travailler.

Si c'est le serveur qui est détruit, il faudra réimporter le code le plus à jour (révision la plus haute) dans un nouveau projet SVN. Par contre l'historique sera perdu.

Moralité

Il est très difficile de perdre du travail. Toutes les modifications de code sont datées et signées. Le travail en parallèle est possible.

Par contre, il faut avoir une connection pour chaque modification importante (pour le commit).

Avec GIT

Pierre initie le projet

Pierre crée le projet sur son serveur GIT.

Il crée un dossier et init (initie) le projet vide sur sa machine.

Il le configure pour qu'il soit connecté au dépôt distant de son serveur GIT.

Pierre travaille

Pierre ajoute des fichiers, en modifie d'autres, etc.

Il commit régulièrement ses modifications sur son dépôt local.

Chaque commit est identifié par un "hash"
(par exemple: 2e48687b6404312ce5408275d40c0b8a0cdbf347).

Les commits sont d'abord locaux avec GIT. Donc pas de connection au serveur pour le moment, tout est stocké sur la machine de Pierre.

Paul rejoint le projet

Pierre push (publie) tous ces commits sur le serveur GIT.

Paul clone le projet contenant tous les commits de Pierre sur sa propre machine.

Paul étudie le code existant.

Pierre travaille

Pendant ce temps, Pierre ajoute des fichiers, en modifie d'autres, etc.

Il commit et push régulièrement ses modifications.

Paul travaille

Paul aussi ajoute des fichiers, en modifie d'autres, etc.

Il commit régulièrement ses modifications sur son dépôt local, sans ce soucier des modifications de Pierre pour le moment.

Au bout d'un moment, Paul tente de faire un push (publie), mais le serveur refuse, car Paul n'est pas "à jour" : le dernier commit sur le dépôt local de Paul ne correspond pas au dernier commit sur le dépôt distant.

Paul fait un pull (mise à jour), qui lui permet de récupérer les commits manquants...

Comme SVN, l'outil GIT merge automatiquement les modifications s'il le peut, ou indique à Paul les lignes qui lui posent problème.

Paul résout les conflicts et peut faire son push.

Pierre travaille

Pendant ce temps, Pierre fait régulièrement des commit, pull et push pour être à jour et travailler sereinement.

Orage !

Si les machines de Pierre et Paul sont détruites, pas de problème : il suffira de faire un nouveau clone du projet à partir du serveur.

Si c'est le serveur qui est détruit, pas de problème, car les dépôts locaux sont des clones complets du dépôt distant. Il suffira de le renvoyer sur un serveur, et tout l'historique sera gardé.

Moralité

Il est très difficile de perdre du travail. Toutes les modifications de code sont datées et signées. Le travail en parallèle est possible.

Il est possible de travailler sereinement, même ponctuellement sans connection.

Pour résumer, on peut dire que GIT ajoute "un étage" par rapport à SVN, ce qui le rends très interressant.

Le workflow SVN et GIT

Pour SVN, les fichiers doivent être ajouté pour être suivi par SVN (avec la commande add). Une fois suivi, s'il est modifié, ce fichier sera publié lors d'un commit.

GIT, par contre, fonctionne avec le contenu, pas avec le fichier complet. Lorsqu'on ajoute un fichier, on ajoute en fait sa modification. Ce qui signifie qu'il faut ajouter le fichier à chaque modification pour le publier.

GIT possède une zone intermédiaire, le "staging" qui permet de nombreuses fonctionnalités supplémentaires par rapport à SVN.

GIT

Nous choissisons de travailler avec GIT pour plusieurs raisons :

Nous utiliserons BitBucket (www.bitbucket.org) pour stocker nos projets. C'est gratuit, même pour des projets privés.

Les commandes de GIT de base

GIT INIT

Permet de définir un dépôt local pour le dossier courant. C'est ce qu'on utilise lorsqu'on crée un projet.

git init

GIT REMOTE ADD

Permet d'ajouter le nom et l'adresse d'un dépôt distant vers lequel on va pouvoir publier (push).

Utile pour ajouter un dépôt distant a un dépôt local que l'on vient de créer, ou pour en ajouter un autre (par exemple un autre environnement).

git remote add origin https://mon-compte@bitbucket.org/mon-compte/mon-projet.git

GIT CLONE

Permet de copier un dépôt distant vers un dossier. C'est ce qu'on utilise lorsqu'on récupère un projet existant.

git clone https://mon-compte@bitbucket.org/mon-compte/mon-projet.git

GIT ADD

Permet d'ajouter une modification dans le "staging area" avant de pouvoir la commiter.

git add monFichier.java

git add monDossier/

GIT COMMIT

Permet de commiter les modifications (qui ont été ajoutées) dans le dépôt local.

Il est nécessaire de préciser un message qui décrit ces modifications.

git commit -m "Correction du calcul du prix de la pizza"

GIT STATUS

Affiche l'état actuel de la copie de travail locale :

Exemple :

git status

# Sur la branche master # Modifications qui seront validées : # # modifié : monfichier1.java # modifié : monfichier2.java # # Modifications qui ne seront pas validées : # # modifié : monfichier3.java # modifié : monfichier4.java # # Fichiers non suivis: # # unDossier/ # unAutreDossier/ # unFichier.java

Ou encore, une copie de travail sans modification :

# Sur la branche master rien à valider, la copie de travail est propre

GIT PUSH

Permet de publier sur le dépôt distant les commits présent dans le dépôt local. Il est possible de choisir le dépôt distant.

git push origin master

A noter que le push peut être refusé par le serveur, si votre version locale n'est pas à jour :

To https://mon-compte@bitbucket.org/mon-compte/mon-projet.git ! [rejected] master -> master (fetch first)

GIT PULL

Permet de récupérer sur le dépôt local les commits présent dans le dépôt distant. Il est possible de choisir lequel.

git pull

Les modifications sont fusionnées dans la mesure du possible par le programme GIT. En cas d'échec, il le signalera et déclanchera un ou plusieurs conflits.

En cas de conflit

remote: Counting objects: 5, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From https://mon-compte@bitbucket.org/mon-compte/mon-projet.git 1a10ac5..0fe69e3 master -> origin/master fusion automatique de monFichier.java CONFLIT (contenu) : conflit de fusion dans monFichier.java

git status

# Sur la branche master # Votre branche et 'origin/master' ont divergé, # et ont 1 et 1 commit différent chacune respectivement. # (utilisez "git pull" pour fusionner la branche distante dans la vôtre) # # Vous avez des chemins non fusionnés. # (réglez les conflits et lancez "git commit") # # Chemins non fusionnés : # (utilisez "git add ..." pour marquer résolu) # # modifié des deux côtés :monFichier.java #

Il faut alors régler manuellement le conflit en éditant simplement le fichier qui a été modifié par GIT :

for(int i=0; i<10; i++){ <<<<<<< HEAD test.uneMethode(i); ======= test.uneAutreMethode(i); >>>>>>> 0fe69e3fe83e711853c71a4298eb15535762dba3 }

Une fois corrigé, il faut reprendre le workflow normal : git add, git commit puis git push

GIT DIFF

Permet de comparer les modifications que l'on a apporté aux fichiers avec ce qui est commité.

git diff

diff --git a/monfichier1.java b/monfichier1.java index 325a0fc..1e0b01f 100644 --- a/monfichier1.java +++ b/monfichier1.java @@ -1,4 +1,4 @@ for(int i=0; i<10; i++){ + test.ancienneMethode(i); - test.nouvelleMethode(i); }

GIT LOG

Permet de consulter l'historique du projet ou d'un fichier en particulier.

git log

commit 1a10ac5e6a1a9ce6f171beb02899338e3261a9f9 Author: Boby Lapointe Date: Fri Oct 11 21:28:32 2013 +0200 Fix #5 Correction du calcul du prix de la pizza. commit 153a706ae1ec12d2a3f9db740c2719f0ab714bd5 Author: Robert Smith Date: Fri Oct 11 10:58:17 2013 +0200 initial commit

GIT BLAME

Permet d'afficher pour chaque ligne d'un fichier son commit correspondant.

git blame monFichier.java

... 1153a706 (Boby Lapointe 2013-10-11 10:58:17 1) for(int i=0; i<10; i++) { 1a10ac5e (Robert Smith 2013-10-11 22:51:43 2) test.uneMethode(i); 1153a706 (Boby Lapointe 2013-10-11 10:58:17 3) } ...

GIT BRANCH

Permet de créer des branches pour implémenter des fonctionnalités sans toucher à la branche principale. Tous les commits seront alors stockés dans cette branche.

Une branche pourra être ensuite intégrée à une autre.

Pour commencer, on travaille sur une seule et unique branche : master.

Aller plus loin : git pull --rebase

Les "merges" (utilisé lors d'un pull classique) donnent des historiques non-linéaires que l'ont cherche parfois à éviter.

Pierre et Paul sont tous les deux à jour et voici leur dépôt local (3 commits) :

Paul travaille, commite et pushe. Ses commits sont basés sur "C".

Pierre travaille aussi, commite mais ne pushe pas encore. Ses commits sont aussi basés sur "C".

Virtuellement, l'arbre des commits ressemble donc à ça :

Si Pierre pushe, le serveur refusera. Il sera obligé de se mettre à jour avec un pull.

Dans ce cas, il a 2 choix : soit git pull simple, qui donnera lieu à un commit supplémentaire de "merge" :

Soit un git pull --rebase qui "déplacera" son commit dans l'histoirique, et qui le fera dépendre son premier commit non plus de "C", mais du dernier commit ("E Paul"). L'historique sera linéaire :

Selon les situations et la politique de workflow, on pourra utiliser l'un ou l'autre.

Les conflits lors d'un rebase

Le conflit est traité différement lors d'un "rebase". GIT passe en mode "rebase" et l'on doit l'aider à régler les conflits en même temps qu'il les rencontres.

git status

# rebase in progress; onto 3d75a5a # Vous êtes en train de rebaser la branche 'master' sur '3d75a5a'. # (résolvez les conflits puis lancez "git rebase --continue") # (utilisez "git rebase --skip" pour sauter ce patch) # (utilisez "git rebase --abort" pour extraire la branche d'origine) # # Chemins non fusionnés : # (utilisez "git reset HEAD ..." pour désindexer) # (utilisez "git add ..." pour marquer résolu) # # modifié des deux côtés : monFichier.java #

Il faut éditer le fichier problématique pour régler le conflit, puis dire à GIT que c'est terminé pour ce conflit : git add monFichier.java git rebase --continue

On peut aussi annuler le "rebase" avec git rebase --abort