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.
$ 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.
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.
?? README
?? hello.sh
Les points d'interrogation signifient que les fichiers sont non versionnés. Pour les passer en staging, nous utilisons git add.
ou
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 :
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.
$ 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 :
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.
$ 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.
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.email you@yourdomain.com
Nous pouvons maintenant commiter. Nous fournissons un message décrivant le commit avec l'option -m
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.
# 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.
$ 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.
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.
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.
est l'équivalent de
$ 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 :
* 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
* 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.
* 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 :
* 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.
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.
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
bob
origin
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
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 :
- The Pro Git Book
- The Git Community Book
- Git Magic
- Le Git de Git : git://git.kernel.org/pub/scm/git/git.git