Git pour les nuls

la conclusion pour commencer

Voici un tutoriel Git détaillé pour ceux qui en connaissent très peu sur les systèmes de versions. Vous comprendrez l’utilité de tels systèmes et surtout comment on se sert des systèmes de versions modernes, le tout en restant le plus pragmatique possible.


Pour commencer, la conclusion

Voici la liste des commandes nécessaires et suffisantes pour utiliser Git. Il y en a très peu. Il est normal de ne pas les comprendre tout de suite mais c’est pour vous donner une idée. Malgré la longueur de l’article, 95% de l’utilisation de Git tiens dans les 7 commandes décrites ci-après.

Récupérer un projet :

git clone ssh://server/path/to/project

Utiliser Git tous les jours :

# get modifications from other
git pull
# read what was done
git log

# Make local changes to files 
hack, hack, hack...
# list the modified files
git status
# show what I've done
git diff

# tell git to version a new file
git add new/file

# commit its own modifications 
# to its local branch
git commit -a -m "Fix bug #321"

# send local modifications to other
git push

Cet article est écrit pour ceux qui en savent très peu sur les systèmes de version. Il est aussi écrit pour ceux qui n’ont pas suivi les progrès accomplis depuis CVS ou subversion (SVN). C’est pourquoi, dans un premier temps, j’explique rapidement quels sont les buts poursuivis par les systèmes de versions. J’explique ensuite comment installer et configurer Git. Puis, pour chaque action que doivent accomplir les DCVS je donne les commandes Git qui y correspondent.

Git pour quoi faire ?

Si tout ce qui vous intéresse c’est d’utiliser Git tout de suite. Lisez simplement les parties sur fond noir. Je vous conseille aussi de revenir relire tout ça un peu plus tard, pour mieux comprendre les fondements des systèmes de versions et ne pas faire de bêtises quand vous les utilisez.

Git est un DCVS, c’est-à-dire un système de versions concurrentes décentralisé. Analysons chaque partie de cette appellation compliquée.

Système de versions

Tout d’abord, les systèmes de versions gèrent des fichiers. Quand on travaille avec des fichiers sans système de version voilà ce qui arrive souvent :

Lorsqu’on modifie un fichier un peu critique et qu’on a pas envie de perdre, on se retrouve souvent à le recopier sous un autre nom. Par exemple

$ cp fichier_important.c fichier_important.c.bak

Du coups, ce nouveau fichier joue le rôle de backup. Si on casse tout, on peut toujours écraser les modifications que nous avons faites. Évidemment le problème avec cette façon de faire c’est que ce n’est pas très professionnel. Et puis c’est un peu limité. Si on veut faire trois ou quatre modifications on se retrouve avec plein de fichiers. Parfois avec des nom bizarres comme :

fichier_important.c.bak
fichier_important.c.old
fichier_important.c.Bakcup
fichier_important.c.BAK.2009-11-14
fichier_important.c.2009.11.14
fichier_important.c.12112009
old.fichier_important.c

Bon alors si on veut que ça marche il faut se fixer des conventions de nommage. Les fichiers prennent beaucoup de place alors que souvent il n’y a que quelques lignes différentes entre le fichier et son backup…

Heureusement les systèmes de version viennent à la rescousse.

Il suffit de signaler que l’on va faire une nouvelle version d’un fichier et le système de version se débrouille pour l’enregistrer quelque part où on pourra facilement le retrouver. Et en général, le système de version fait les choses bien. C’est-à-dire qu’il n’utilise que très peu d’espace disque pour faire ces backups.

Il fut un temps où les versions étaient gérées fichier par fichier. Je pense à CVS. Puis on s’est vite aperçu qu’un projet c’est un ensemble de fichiers cohérents. Et donc il ne suffit pas de pouvoir revenir en arrière par fichier, mais plutôt dans le temps. Les numéros de versions sont donc passé d’un numéro par fichier à un numéro par projet tout entier.

Ainsi on peut dire, «je veux revenir trois jours en arrière», et tous les fichiers se remettent à jour.

Qu’apportent les systèmes de versions ? (je n’ai pas tout mentionné)

  • backup automatique de tous les fichiers: Revenir dans le temps. ;
  • donne la possibilité de voir les différences entre chaque version et les différences entre la version en cours et les modifications locales ;
  • permet de poser un tag sur certaines versions et ainsi pouvoir s’y référer facilement ;
  • permet d’avoir un historique des modifications. Car en général il est demandé aux utilisateurs d’ajouter un petit commentaire à chaque nouvelle version.

concurrentes

Les systèmes de versions sont déjà intéressants pour gérer ses projets personnels. Car ils permettent de mieux organiser celui-ci. De ne (presque) plus se poser de questions à propos des backups. Je dis presque parce qu’il faut quand même penser à protéger par backup son repository. Mais là où les systèmes de versions deviennent vraiment intéressants, c’est pour la gestion de projets à plusieurs.

Commençons par un exemple avec un projet fait par deux personnes ; Alex et Béatrice. Sur un fichier contenant une liste de dieux Lovecraftiens :

Cthulhu
Shubniggurath
Yogsototh

Disons que Alex est chez lui, il modifie le fichier :

Cthulhu
Shubniggurath
Soggoth
Yogsototh

puis il envoi ce fichier sur le serveur du projet. Ainsi sur le serveur, il y a le fichier d’Alex.

Ensuite c’est Béatrice qui n’a pas récupéré le fichier d’Alex sur le serveur qui fait une modification.

Cthulhu
Dagon
Shubniggurath
Yogsototh

Puis Béatrice envoi son fichier sur le serveur.

La modification d’Alex est perdue. Encore une fois les systèmes de versions sont là pour résoudre ce type de soucis.

Un système de version aurait mergé les deux fichiers au moment où Béatrice voulait envoyer la modification sur le serveur. Et comme par magie, sur le serveur le fichier deviendra :

Cthulhu
Dagon
Shubniggurath
Soggoth
Yogsototh

En pratique, au moment où Béatrice veut envoyer ses modifications, le système de version la préviens qu’une modification a eu lieu sur le serveur. Elle utilise la commande qui rapatrie les modifications localement et qui va mettre à jour le fichier. Ensuite Béatrice renvoie le nouveau fichier sur le serveur.

Qu’apportent les Systèmes de Versions Concurrentes ?

  • récupérer sans problème les modifications des autres ;
  • envoyer sans problème ses modifications aux autres ;
  • permet de gérer les conflits. Je n’en ai pas parlé, mais quand un conflit arrive (ça peut arriver si deux personnes modifient la même ligne avec deux contenus différents), les SVC proposent leur aide pour les résoudre. J’en dirai un mot plus loin.
  • permet de savoir qui a fait quoi et quand

décentralisé

Ce mot n’est devenu populaire que très récemment dans le milieu des systèmes de version. Et bien ça veut dire principalement deux choses.

Tout d’abord, jusqu’à très récemment (SVN) il fallait être connecté sur un serveur distant pour avoir des informations sur un projet. Comme avoir l’historique. Les nouveaux systèmes décentralisés permettent de travailler avec un REPOSITORY (le répertoire contenant tous les backups, et les différentes info nécessaires au fonctionnement du système de versions) local au projet. Ainsi on peut avoir l’historique du projet sans avoir à se connecter au serveur.

Toutes les instances de projets peuvent vivre de façon indépendantes.

Pour préciser, les systèmes de versions concurrentes décentralisés sont basés sur la notion de branche.

Et la signification pratique est très importante. Ça veut dire que tout les utilisateurs travaillent de façon complètement indépendante les uns des autres. Et c’est l’outil de version qui se charge de mettre tout ça ensemble.

Ça va même encore plus loin. Ça permet de développer plusieurs features de manière complètement indépendantes. Sous les autres systèmes c’était plus difficile.

L’exemple type :

Je développe mon projet. Je suis en train de l’améliorer. Lorsqu’un bug urgent est reporté.

Je peux très facilement avec un système décentralisé, revenir sur la version qui pose problème. Résoudre le bug. Renvoyer les modifications. Puis revenir à ma version avec les améliorations en cours. Et même récupérer la correction de bug dans ma nouvelle version avec les améliorations.

Dans un système non décentralisé, cela est possible, mais fastidieux. Les systèmes décentralisés rendent ce type de comportement très naturels. Ainsi, il devient naturel de tirer des branches pour toutes les features, les bug…

Avantages donnés par la décentralisation des systèmes de versions concurrentes :

  • Possibilité de travailler sans être connecté au serveur de version ;
  • Possibilité de créer beaucoup de patches atomiques ;
  • Grande facilité de maintenance de plusieurs versions différentes de la même application.

Pour résumer

Résumons l’ensemble des choses que l’on peut faire facilement avec un DCVS :

Systèmes de versions

  • revenir dans le temps ;
  • lister les différences entre chaque version ;
  • nommer certaines versions pour s’y référer facilement ;
  • afficher l’historique des modifications.

Concurrentes

  • récupérer les modifications des autres ;
  • envoyer ses modifications aux autres ;
  • permet de savoir qui a fait quoi et quand ;
  • gestion des conflits.

Décentralisé

  • manipuler facilement des branches

Maintenant voyons comment obtenir toutes ces choses facilement avec Git.

Avant l’utilisation, la configuration

installation

Sous Linux Ubuntu ou Debian :

$ sudo apt-get install git

Sous Mac OS X :

$ sudo port selfupdate

$ sudo port install git-core

Configuration globale

Enregistrez le fichier suivant comme le fichier ~/.gitconfig.

[color]
    branch = auto
    diff   = auto
    status = auto
[alias]
    st        = status
    co        = checkout
    br        = branch
    lg        = log --pretty=oneline --graph
    logfull   = log --pretty=fuller --graph --stat -p
    unstage   = reset HEAD
    # there should be an article on what this command do
    uncommit = !zsh -c '"if (($0)); then nb=$(( $0 - 1 )); else nb=0; fi; i=0; while ((i<=nb)); do git revert -n --no-edit HEAD~$i; ((i++)); done; git commit -m \"revert to $0 version(s) back\""'
    undomerge = reset --hard ORIG_HEAD
	conflict  = !gitk --left-right HEAD...MERGE_HEAD
    # under Mac OS X, you should use gitx instead
	# conflict    = !gitx --left-right HEAD...MERGE_HEAD
[branch]
	autosetupmerge = true

Vous pouvez obtenir le même résultat en utilisant pour chaque entrée la commande git config --global. Configurez ensuite votre nom et votre email. Par exemple si vous vous appelez John Doe et que votre email est john.doe@email.com. Lancez les commandes suivantes :

$ git config --global user.name John Doe

$ git config --global user.email john.doe@email.com

Voilà, la configuration de base est terminée. J’ai créé dans le fichier de configuration global des alias qui vont permettre de taper des commandes un peu plus courtes.

Récupération d’un projet déjà versionné

Si un projet est déjà versionné avec Git vous devez avoir une URL pointant vers les sources du projet. La commande a exécuter est alors très simple.

$ cd ~/Projets
$ git clone git://main.server/path/to/file

S’il n’y a pas de serveur git sur le serveur distant, mais que vous avez un accès ssh, il suffit de remplacer le git de l’url par ssh. Pour ne pas avoir à entrer votre mot de passe à chaque fois le plus simple est de procéder comme suit :

$ ssh-keygen -t rsa

Répondez aux question et n’entrez surtout PAS de mot de passe. Ensuite copiez les clés sur le serveur distant. Ce n’est pas la façon la plus sûre de procéder. L’idéal étant d’écrire quand même un mot de passe et d’utiliser ssh-agent.

Ensuite le plus simple, si vous possédez ssh-copy-id (sous Ubuntu par exemple) :

me@locahost$ ssh-copy-id -i ~/.ssh/id_rsa.pub me@main.server

ou manuellement :

me@locahost$ scp ~/.ssh/id_rsa.pub me@main.server:
me@locahost$ ssh me@main.server
password:
me@main.server$ cat id_rsa.pub >> ~/.ssh/authorized_keys
me@main.server$ rm id_rsa.pub
me@main.server$ logout

Maintenant vous n’avez plus besoin de taper votre mot de passe pour accéder à main.server. Et donc aussi pour les commandes git.

Créer un nouveau projet

Supposons que vous avez déjà un projet avec des fichiers. Alors il est très facile de le versionner.

$ cd /path/to/project
$ git init
$ git add .
$ git commit -m "Initial commit"

Une petite précision. Si vous ne souhaitez pas versionner tous les fichiers. Par exemple, les fichiers de compilations intermédiaires. Alors il faut les exclure. Pour cela, avant de lancer la commande git add .. Il faut créer un fichier .gitignore qui va contenir les pattern que git doit ignorer. Par exemple :

*.o
*.bak
*.swp
*~

Maintenant si vous voulez créer un repository sur un serveur distant, il faut absolument qu’il soit en mode bare. C’est-à-dire que le repository ne contiendra que la partie contenant les informations utile à la gestion de git, mais pas les fichiers du projet. Sans rentrer dans les détails, il suffit de lancer :

$ cd /path/to/local/project
$ git clone --bare . ssh://server/path/to/project

Les autres pourront alors récupérer les modifications via la commande vue précédemment :

git clone ssh://server/path/to/project

Résumé de la seconde étape

Vous avez maintenant un répertoire sur votre ordinateur local. Il est versionné. Vous pouvez vous en rendre compte parcequ’à la racine (et à la racine seulement), il y a un répertoire .git. Ce répertoire contient tous les fichiers nécessaires au bon fonctionnement de Git.

Il ne reste plus qu’à savoir comment s’en servir maintenant pour obtenir toutes les jolies promesses faites dans la première partie.

Et c’est parti !

Voici une parmi de nombreuses autres façon d’utiliser Git. Cette méthode est nécessaire et suffisante pour travailler seul ou en collaboration sur un projet commun. Cependant, on peut faire beaucoup mieux avec Git que ce workflow (en langage anglo-saxon).

Utilisation basique

La façon immédiate de travailler avec Git :

  • récupérer les modifications des autres git pull
  • voir les détails de ces modifications git log
  • Plusieurs fois:
  • Faire une modification atomique
  • verifier le details de ses modifications git status et git diff
  • indiquer si nécessaire que de nouveaux fichiers doivent être versionnés git add [file]
  • enregistrer ses modifications
    git commit -a -m "message"
  • envoyer ses modifications aux autres git push (refaire un git pull si le push renvoie une erreur).

Voilà, avec ces quelques commandes vous pouvez utiliser Git sur un projet avec d’autres personnes. Même si c’est suffisant, il faut quand même connaître une chose avant de se lancer ; la gestion des conflits.

Gestion des conflits

Les conflits peuvent survenir lorsque vous modifiez les même lignes de codes sur le même fichier d’une autre branche que vous mergez. Ça peut sembler un peu intimidant, mais avec Git ce genre de chose est très facile a régler.

exemple

Vous partez du fichier suivant :

Zoot 

et vous modifiez une ligne

Zoot the pure

sauf que pendant ce temps, un autre utilisateur a aussi modifié cette ligne et a fait un push de sa modification.

Zoot, just Zoot

Maintenant quand vous lancez la commande

$ git pull
remote: Counting objects: 5, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /home/yogsototh/tmp/conflictTest
   d3ea395..2dc7ffb  master     -> origin/master
Auto-merging foo
CONFLICT (content): Merge conflict in foo
Automatic merge failed; fix conflicts and then commit the result.

Notre fichier foo contient alors :

<<<<<<< HEAD:foo
Zoot the pure
=======
Zoot, just Zoot
>>>>>>> 2dc7ffb0f186a407a1814d1a62684342cd54e7d6:foo

Résolution du conflit

Régler le conflit, il suffit d’éditer le fichier, par exemple en écrivant :

Zoot the not so pure

et de ‘commiter’ tout simplement :

git commit -a -m "conflict resolved"

Maintenant vous êtes fin prêt pour utiliser Git. Sauf que Git, c’est un outil qui permet de faire beaucoup plus que juste ça. Nous allons maintenant voir comment utiliser les fonctionnalités de Git qui n’étaient pas disponibles avec CVS et consorts.

Pourquoi Git est cool ?

Parce que grace à Git vous pouvez travailler sur plusieurs partie du projet de façon complètement isolée les unes des autres. Ça c’est la partie décentralisée de Git.

Toutes les branches locales utilisent le même répertoire. Ainsi on peu changer de branche très aisément et rapidement. On peut aussi changer de branche alors que certains fichier sont en cours de modifications. On peut même pousser le vice jusqu’à modifier un fichier, changer de branche, commiter une partie seulement des modifications de ce fichier dans la branche courante. Revenir dans l’ancienne branche et commiter à nouveau les modifications restantes. Et merger dans une troisième branche les deux modifications.

Avec la command git rebase on peut après coup, décider que certaines modifications devaient aller dans certaines branches, que d’autres ne servaient à rien. C’est une commande vraiment très puissante pour organiser l’historique.

En pratique, qu’est-ce que ça signifie ? Mieux qu’avec tous les autres systèmes de versions, vous pouvez utiliser Git pour vous concentrer sur votre code. En effet, on peut envoyer les commits après avoir coder. Par exemple, vous pouvez coder sur la résolution du bug b01, du bug b02 et de la feature f03. Puis ensuite, vous pouvez créer une branche par bug et par feature. Puis commiter les modifications pour chaque branche et chaque feature. Puis finalement merger tous les modifications dans la branche principale.

Tout a été pensé pour vous permettre de coder d’abord, puis de vous occuper du système de version plus tard. Bien entendu, faire des commit atomique au fur et à mesure du code permet de gagner du temps et de ne pas trop s’embêter pour organiser les branches. Mais rien ne vous y oblige. Par contre faire la même chose dans d’autres systèmes de versions n’est absolument pas naturel.

Avec Git vous pouvez aussi dépendre de plusieurs sources. Ainsi, plutôt que d’avoir un serveur centralisé, vous pouvez avoir plusieurs sources. Vous pouvez définir ce genre de chose très finement.

Ce qui change le plus avec Git c’est la vision d’un projet centralisé sur un serveur avec plusieurs personnes qui travaillent dessus. Avec Git plusieurs personnes peuvent travailler sur le même projet, mais sans nécessairement avoir un repository de référence. On peut très facilement résoudre un bug et envoyer le patch à plein d’autres versions du projet.

Liste de commandes

Les commandes pour chaque choses

Dans la première partie, nous avons vu la liste des problèmes résolus par Git. En résumé Git doit pouvoir :

  • récupérer les modifications des autres ;
  • envoyer ses modifications aux autres ;
  • revenir dans le temps ;
  • lister les différences entre chaque version ;
  • nommer certaines versions pour s’y référer facilement ;
  • afficher l’historique des modifications ;
  • savoir qui a fait quoi et quand ;
  • gérer des conflits ;
  • manipuler facilement des branches.

récupérer les modifications des autres

$ git pull

envoyer ses modifications aux autres

$ git push

ou plus généralement

$ git pull
$ git push

revenir dans le temps

Pour toute l’arborescence

$ git checkout
$ git revert

revenir trois versions en arrière

$ git uncommit 3

Revenir avant le dernier merge (s’il s’est mal passé).

$ git revertbeforemerge

Pour un seul fichier

$ git checkout file
$ git checkout VersionHash file
$ git checkout HEAD~3 file

lister les différences entre chaque version

liste les fichiers en cours de modifications

$ git status

différences entre les fichiers de la dernière version et les fichiers locaux.

$ git diff

liste les différences entre les fichier d’une certaine version et les fichiers locaux.

$ git diff VersionHash fichier

nommer certaines versions pour s’y référer facilement

$ git tag 'toto'

afficher l’historique des modifications

$ git log
$ git lg
$ git logfull

savoir qui a fait quoi et quand

$ git blame fichier

gérer des conflits

$ git conflict

manipuler facilement des branches

Pour créer une branche :

$ git branch branch_name

Pour changer de branche courante :

$ git checkout branch_name

Comments

comments powered by Disqus
Published on 2009-11-12
Follow @yogsototh
Yann Esposito©
Done with Vim & nanoc Hakyll