Dive into Git

Git est un logiciel de gestion de versions (VCS, pour Version Control System) décentralisé créé par Linus Torvalds. C'est un outil très puissant et populaire. Il est notamment utilisé pour le développement du noyau Linux (et bien d'autres projets célèbres). Il est d'ailleurs tellement apprécié que les développeurs du noyau se demandent aujourd'hui comment ils ont pu s'en passer quand il n'existait pas encore.

Devant tant d'éloges, j'ai voulu tester cet outil. Il n'est pas compliqué à apprivoiser, en revanche son fonctionnement est un peu différent d'autres logiciels de versioning (notamment SVN, que j'avais déjà utilisé), principalement à cause de la décentralisation qui caractérise Git.

En effet, alors que la plupart des logiciels de gestion de versions nécessitent un serveur central auquel tout le monde se connecte pour envoyer et recevoir les modifications, Git permet à chaque utilisateur de disposer d'un dépôt local qu'il peut synchroniser avec les autres utilisateurs. Il est de plus tout à fait possible de coupler cette décentralisation à un serveur maître afin d'avoir à tout moment un dépôt accessible pour tous.

Pourquoi Git est meilleur que les autres

Une page web explique très bien cela : http://whygitisbetterthanx.com/. En résumé :

  • Git est rapide
  • Son architecture distribuée (pas de serveur central, donc travail hors-ligne possible )
  • Gestion des branches simple et rapide
  • Github

Comprendre Git

Le principe de fonctionnement de Git est relativement simple : Git vous permet de prendre des snapshot des fichiers de votre projet. Il suffit de lui indiquer quels fichiers sauvegarder, puis d'effectuer la sauvegarde (ce que l'on appelle un commit). Lorsque l'on commite, Git écrit un manifeste décrivant à quoi ressemblent les fichiers de votre projet à cet instant. Cela permet de ne pas sauvegarder tout le répertoire du projet à chaque fois.

Voyons à présent comment utiliser basiquement Git.

Créer et récupérer des dépôts

La première chose à faire pour utiliser Git est d'avoir un dépôt Git. Deux moyens d'en obtenir : soit le créer, soit le récupérer.

Créer un dépôt : git init

Pour créer un dépôt, il suffit de lancer la commande git init au sein du répertoire voulu.

$ cd fake-project
$ ls
README hello.sh
$ git init
Initialized empty Git repository in ~/fake-project/.git/

Récupérer un dépôt : git clone

Vous pouvez directement récupérer un dépôt Git via Internet, avec la commande git clone [URL]. Cela signifie que vous copierez en local les fichiers du dépôt, mais également l'historique des commit, des branches, etc.

$ git clone git://github.com/octocat/Hello-World.git

Gérer les snapshots

Avant d'aborder les commandes, il faut d'abord comprendre qu'un fichier peut avoir 4 états :

  • Non versionné
    Git ignore ce fichier.
  • En staging
    Le fichier ou les modifications du fichier seront sauvegardés lors du prochain commit.
  • Modifié
    Le fichier a changé depuis le dernier commit, mais il n'est pas en staging, et donc ne sera pas mis à jour lors du prochain commit.
  • Commité
    Le fichier est sauvegardé et n'a pas été modifié depuis.

En résumé, nous utiliserons git add pour passer un ou des fichiers en staging, git status et git diff pour voir l'état et quelles modifications ont été apportées ou non aux fichiers, et enfin git commit pour sauvegarder.

Ajouter des fichiers dans la staging area : git add

Reprenons notre exemple fake-project. Faisons un git status pour connaître l'état des fichiers du dépôt.

$ git status -s
?? README
?? hello.sh

Les points d'interrogation signifient que les fichiers sont non versionnés. Pour les passer en staging, nous utilisons git add.

$ git add README hello.sh

ou

$ git add .

pour ajouter tous les fichiers du dépôt (mode récursif).

Un git status nous confirme que les fichiers sont prêts à être commités :

$ git status -s
A  README
A  hello.sh

Une chose importante à comprendre : lorsque les fichiers seront commités, ils le seront dans l'état où ils étaient lors du dernier git add. Cela signifie qu'en cas de modifications d'un fichier après un git add, celles-ci ne seront pas prises en compte dans le snapshot de Git. Il faudra refaire un git add pour que ce soit le cas.

Voir l'état des fichiers du dépôt : git status

Comme nous l'avons vu plus haut, git status permet de savoir si les fichiers sont prêts à être commités, s'ils ont été modifiés ou supprimés.

$ vim README
$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   README
#       new file:   hello.sh
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   README
#

ou avec le -s, pour la version courte :

$ git status -s
AM README
A  hello.sh

 
Voir les différences : git diff

La commande git diff peut être utilisée de plusieurs façons. Sans argument, git diff donne la  différence entre ce qui est en staging et ce qui est modifié à posteriori.

$ vim README
$ git status -s
AM README
A hello.sh
$ git diff
diff --git a/README b/README
index 8b13789..6a4238f 100644
--- a/README
+++ b/README
@@ -1 +1 @@
-
+blablabla

L'argument --cached donne le changement effectué lors du passage en staging.

$ git status -s
AM README
A hello.sh
$ git add .
$ git diff
$
$ git diff --cached
diff --git a/README b/README
new file mode 100644
index 0000000..6a4238f
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+blablabla
diff --git a/hello.sh b/hello.sh
new file mode 100644
index 0000000..e69de29

La commande git diff HEAD permet de connaitre toutes les modifications depuis le dernier commit, que le fichier soit en staging ou simplement modifié.

Créer un snapshot de la staging area : git commit

Maintenant que nous avons mis nos fichiers en staging, il nous est possible de les sauvegarder en utilisant la commande git commit. Avant cela, on donne à Git notre nom et notre mail,  qui seront enregistré lors du commit.

$ git config --global user.name 'Your Name'
$ git config --global user.email you@yourdomain.com

Nous pouvons maintenant commiter. Nous fournissons un message décrivant le commit avec l'option -m

$ git status -s
A README
A hello.sh
$ git commit -m 'my first commit'
[master (root-commit) cba1144] my first commit
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 README
create mode 100644 hello.sh

Et voilà ! Notre staging area a été sauvegardée et vidée. Un git status nous le confirme.

$ git status
# On branch master
nothing to commit (working directory clean)

Un petit schéma qui résume ce qu'il s'est passé :

Un git commit -a vous permet de commiter tous les fichiers modifiés ou supprimés sans passer par l'étape git add. Cette commande ne commitera pas les nouveaux fichiers non versionnés, il faudra les ajouter au moins une fois en staging avec git add pour pouvoir directement commiter ultérieurement.

$ vim README
$ commit -am 'My second commit'
[master 8151463] My second commit
1 files changed, 2 insertions(+), 0 deletions(-)

Ainsi, on saute l'étape du staging pour passer directement au snapshot :

 

Revenir en arrière : git reset HEAD

Il est possible d'annuler le passage en staging d'un fichier avec la commande git reset HEAD.

$ git status -s
M README
$ git add README
$ git status -s
M README
$ git reset HEAD -- README
Unstaged changes after reset:
M README

Pour annuler le dernier commit : git reset HEAD^

Gérer les fichiers : git rm et git mv

La commande git rm permet de supprimer un fichier de votre dépôt Git. Le fichier est littéralement effacé de votre disque dur.. L'option --cached permet de supprimer le fichier du staging. Le fichier passera en non versionné, et ne sera donc plus géré par Git.

$ git rm --cached README
rm 'README'
$ ls
hello.sh README
git status -s
D README
?? README
$ git commit -m 'bye README'
[master a870e51] bye README
0 files changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 README
$ git status -s
?? README

git mv renomme un fichier et met à jour l'index de Git en conséquence.

$ git mv oldname newname

est l'équivalent de

$ mv oldname newname
$ git add newname
$ git rm oldname

Les branches

Nous abordons la une grande force de Git : sa gestion des branches. Oubliez les galères des autres VCS, les branches sont ici simplissimes à gérer. Pour ceux qui ne connaissent pas le principe, les branches vous permettent de créer des ramifications de votre projet. Imaginez que vous vouliez ajouter une fonctionnalité dans votre projet. Commiter directement les changements en pleine phase de développement rendra votre logiciel instable tant que vous n'aurez pas terminé le développement de la nouvelle fonctionnalité.

Pour contourner ce problème, vous pouvez créer une branche test, qui sera une copie de votre branche principale, apporter et tester vos modifications, sans altérer la branche principale. Une fois que la branche de test vous parait stable, vous pouvez fusionner (merge) le code avec votre branche principale.

Un excellent lien pour bien concevoir l'architecture de vos branches : http://nvie.com/posts/a-successful-git-branching-model/.

Gestion des branches : git branch et git checkout

Commençons par faire un état des lieux : git branch pour savoir sur quelle branche nous travaillons :

$ git branch
* master

Il n'y a ici qu'une branche, master. Elle est précédée d'une étoile, qui signifie que nous utilisons cette branche. Elle a été crée par défaut lors du git init.

Créons une branche avec git branch [name], puis déplaçons nous sur cette nouvelle branche avec un git checkout [name].

$ git branch testing
$ git branch
* master
testing
$ git checkout testing
Switched to branch 'testing'
$ git branch
master
* testing

Il existe une commande qui combine la création de la branche et le déplacement sur celle-ci :git checkout -b [name]

La branche testing a été créée. Il s'agit d'une copie du dernier commit de la branche master. Bien que pour l'instant identiques, nos branches sont à présent dissociées : tout commit de l'une ne sera pas répercuté sur l'autre.

Pour supprimer une branche, git branch -d  [name].

Vous ne pouvez pas supprimer une branche si vous êtes dessus.

$ git branch
* master
testing
$ git branch -d testing
Deleted branch testing (was a870e51).

 
 Fusion de branches : git merge

Nous l'avons déjà vu, les branches sont isolées, ce qui permet d'apporter des modifications à l'une sans modifier l'autre. Et si nous décidons d'incorporer les changements d'une branche dans l'autre ? C'est ce que nous faisons avec git merge [branche], qui permet de fusionner la branche courante avec la branche spécifiée.

Un exemple où nous créons un fichier que nous commitons dans la branche testing, avant de la merger dans la branche master :

$ git branch
* master
$ git checkout -b testing
Switched to a new branch 'testing'
$ ls
hello.sh README
$ git branch
master
* testing
$ touch trololo
$ git add trololo
$ git commit -m 'trololo commit'
[testing e49b586] trololo commit
0 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 trololo
$ ls
hello.sh README trololo
$ git checkout master
Switched to branch 'master'
$ git merge testing
Updating a870e51..e49b586
Fast-forward
0 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 trololo

Notez que si Git fusionnera  la plupart du temps vos branches sans problème, il y a un cas où il ne pourra pas faire de miracle : le cas où le même bloc de code d'un fichier est modifié sur les deux branches. Cette situation entraîne un conflit. Il faudra alors résoudre le conflit à la main, dans le fichier en question. Une fois cela fait, un git add suivi d'un git commit seront nécessaires avant de refaire le git merge.

S'y retrouver dans sa branche : git log

Pour retrouver l'historique des commit et des merge d'une branche, nous avons git log.

$ git log
commit e49b586f5cd44e053f3491094e484c160cd3b052
Author: jeyg
Date: Wed Aug 16 20:00:14 2011 +0200

trololo commit

commit a870e513b9a2fdb8c6f0940b237dea59b240c5be
Author: jeyg
Date: Wed Aug 16 18:59:31 2011 +0200

bye README

Partage du projet

Comme déjà dit, Git repose sur une architecture décentralisée. Ainsi, tout dépôt Git peut à la fois être client et serveur., ce qui est très utile pour travailler hors-ligne. Ceci dit, il est également intéressant de disposer d'un dépôt Git accessible par une équipe afin d'avoir une base commune, que chacun pourra récupérer et modifier localement, pour enfin renvoyer les changements au dépôt commun.

Gérer les alias de dépôt : git remote

Afin de ne pas taper l'URL des dépôts à chaque fois, Git possède un système d'alias. git remote affiche la liste des dépôts enregistrés par Git pour ce dépôt. Par défaut, si vous avez cloné vôtre dépôt, un alias orgin a automatiquement été créé. git remote add [alias] [URL] est utilisé pour ajouter un alias, git remote rm [alias] pour supprimer.

$ git clone http://android.git.kernel.org/platform/external/iptables
Cloning into iptables...
remote: Counting objects: 617, done.
remote: Compressing objects: 100% (347/347), done.
remote: Total 617 (delta 285), reused 576 (delta 263)
Receiving objects: 100% (617/617), 351.46 KiB | 436 KiB/s, done.
Resolving deltas: 100% (285/285), done.
$ cd iptables
$ git remote
origin
$ git remote -v
origin http://android.git.kernel.org/platform/external/iptables (fetch)
origin http://android.git.kernel.org/platform/external/iptables (push)
$ git remote add bob git://github.com/bob
$ git remote
bob
origin
$ git remote -v
bob git://github.com/bob (fetch)
bob git://github.com/bob (push)
origin http://android.git.kernel.org/platform/external/iptables (fetch)
origin http://android.git.kernel.org/platform/external/iptables (push)
$ git remote rm bob
$ git remote
origin

 
Mettre à jour les branches locales depuis le dépôt distant : git fetch, git pull

Vous avez un dépôt cloné, vous pouvez commiter tout ce que vous souhaitez. Imaginons que pendant ce temps, un développeur commite une nouvelle fonctionnalité sur le dépôt distant. Votre travail n'est pas perdu : il suffit de récupérer les modifications via un git fetch [alias], puis de fusionner via un git merge [alias]/[branche]. La commande git pull [alias] permet d'aller plus vite, elle effectue un fetch suivi immédiatement d'un merge.

Envoyer vos branches au dépôt distant : git push

À l'inverse, pour envoyer vos branches vers le dépôt distant, il faut utiliser la commande git push [alias] [branche]. Si la branche que vous tentez d'envoyer existe déjà sur le serveur distant, elle sera mise à jour, si elle n'existe pas, elle sera crée.

Conclusion

Nous venons de terminer notre découverte de Git. J'espère que je vous aurai donné envie de vous y mettre. Pour ma part, je suis pleinement satisfait d'avoir perdu quelques heures à apprivoiser ce logiciel, alors que je connaissais déjà Subversion. Je pense que j'aurais à présent beaucoup de mal à repasser sur un CVS centralisé, tant Git est pratique !

Note : Cet article a grandement été inspiré par l'excellent gitref.org. Je vous encourage à aller le consulter pour plus de détails.

Quelques ressources pour approfondir :

Vus : 5803
Publié par Jeyg : 33