Cet article est une traduction de l’article par Charles O’Farrell (un développeur Atlassian) et mettra l’accent sur les raisons qui amènent les entreprises et les équipes agiles à choisir Git comme système de contrôle de version décentralisé (DVCS). Charles a un intérêt particulier pour la programmation dans les environnements DVCS et a passé un certain temps à migrer de ClearCase & SVN à Git.

Dans notre article précédent, nous avons exploré les raisons qui poussent certaines équipes à choisir Mercurial (en) comme système de gestion de version décentralisée (DVCS). Maintenant, nous allons explorer pourquoi Git offre une alternative solide en tant que DVCS.

Depuis la nuit des temps (1970), les geeks mènent une guerre sanglante et sans merci entre le bien et le mal; le maléfique et le divin; Vim et Emacs. Au cours de ces dernières années, un nouvel ensemble d’outils défraie notre passion et fait appel à nous les geeks. Je parle, bien sûr, du conflit entre Git et Mercurial.

Cet article prend le parti de Git et se penche sur certaines des raisons pour lesquelles Git a pu s’imposer en tant que vainqueur de cette lutte épique.

Mises en garde *bâillement*

Tout d’abord, permettez-moi d’être franc et d’avouer que je suis loin de trouver Git parfait. Loin de là. J’ai passé beaucoup trop d’heures de ma vie à essayer d’expliquer pourquoi Git fait certaines choses de façon complètement inattendues. Par exemple, je deviens particulièrement nerveux lorsque je dois expliquer les différents « modes » de la commande checkout. Et tandis que msysgit est en effet une version étonnante de Git pour Windows, après toutes ces années, on le sent encore comme un citoyen de seconde classe.

Cela dit, j’ai initialement commencé sur les systèmes DVCS avec Mercurial, pour ensuite passer à Git et je n’ai jamais regardé en arrière.

Pour quelle raison?

Le format de stockage

Pour moi, la partie la plus distinctive de Git réside dans le format du dépôt. La plupart des éléments que j’aime à propos de Git découlent de la façon dont il stocke et pense au contenu.

D’un côté, Mercurial a tout misé sur les logs en append-only, optimisant (très raisonnablement) la cherche sur le disque de nos machines. D’un autre côté, Git stocke chaque commit / fichier dans un simple dépôt de ‘hash‘ de documents. Chaque commit que vous faites, chaque version de chaque fichier, finira dans ce dépôt comme une entité séparée.

Avant l’introduction du « fichier pack« , ce processus était terriblement inefficace, mais l’idée n’en était pas moins valide, et c’est ce qui est utilisé aujourd’hui. Il est important de noter que l’identité de chaque objet est stockée sous forme d’un hash de contenu (SHA-1), ce qui signifie que tout est immuable et inchangeable. Pour changer quelque chose d’aussi simple qu’un message de commit, vous devez créer un nouvel objet de commit. Ce qui conduit à …

Un historique plus sûr avec Git

Non, vraiment!

Cela m’irrite toujours lorsque les gens affirment que Git est « destructeur ». Au contraire, je considère que Git est en fait la plus sûr de toutes les options DVCS. Comme nous l’avons vu plus haut, Git ne vous laisse jamais réellement changer quelque chose directement, il vous oblige à créer de nouveaux objets de commit.

Mais qu’arrive-t-il à l’ancienne version?

En fait, Git conserve une trace de tous les changements que vous faites et les stockes dans le reflog. Puisque chaque commit est unique et immuable, tout ce que le reflog a à faire c’est stocker une référence. Après trente jours, Git supprime automatiquement les entrées du reflog pour les envoyer au « garbage collector ». Cependant, Git n’enlève pas les objets ayant encore une référence (…toutes les branches, bug fix et hot fix actif resterons dans votre reflog jusqu’a leurs Merge ). Les branches sont bien évidemment le moyen le plus pratique pour garder des références aux commits, cependant, le reflog en est une autre et vous n’avez même pas besoin d’y penser!

Il existe bien évidemment une commande correspondante vous permettant d’accéder à ce dépôt de hash. La command ‘git reflog‘, vous permet d’inspecter cette histoire, tout comme vous le feriez pour vos commit avec ‘git log’. (Ne sortez pas sans elle)

1
2
3
4
5
6
7
8
git reflog
5adb986 HEAD@{0}: rebase: Use JSONObject instead of strings
6a34803 HEAD@{1}: checkout: moving from finagle to 6a3480325f3beeecbafd351d30877694963a3f01^0
74bd03e HEAD@{2}: commit: Use JSONObject instead of strings
36c9142 HEAD@{3}: checkout: moving from 36c9142e81482f6c3eb8ad110642206a4ea3dfec to finagle
36c9142 HEAD@{4}: commit: Finagle and basic folder/json
1090fb7 HEAD@{5}: commit: Ignore Eclipse files
d6e3e63 HEAD@{6}: checkout: moving from master to d6e3e63889fd98e89e12e53a79bf96b53cbf9396^0

Réécrire l’historique

L’une des choses que je n’ai jamais aimées avec Mercurial est le fait qu’il rend très difficile l’édition de commits passés. « Pourquoi voudrais je faire cela, vous pourriez demander ? ». Si une pull request affecte de nombreux fichiers ou implique une refactorisation importante, il est beaucoup plus facile d’apprécier la teneur des changements en examinant l’histoire racontée pas les commits.

Avec Git, il est facile de « remonter le temps » pour éditer les commits précédant si nécessaire. En conséquence, les logs de commit Git peuvent devenir des récits soigneusement élaborés, contrastant avec les plutôt fidèles (mais inorganisé) prises de notes manuelles.

Il existe une extension pour Mercurial faisant essentiellement la même chose, appelée Mercurial Queus (files d’attente). Mercurial Queus permet d’empiler des pré-commits de sorte à pouvoir les réorganiser jusqu’à votre commit final. Mercurial Queus est livré avec tout un tas de nouvelles commandes à apprendre (qui ne sont pas dans SVN!).

1
2
3
4
5
6
hg qnew firstpatch
hg qrefresh
hg qdiff
hg qnew secondpatch
hg qrefresh
hg qcommit

Dans Git, vous pouvez faire vos commits normalement et vous souciez de ce qu’il faut faire plus tard. Lorsque vous arriverez au moment où vous avez besoin de faire un changement dans votre historique de commits; vous n’aurez vraiment qu’une seule chose à savoir: « interactive rebase » (git rebase -i). Cette commande lance un éditeur de texte et vous permet de modifier l’histoire de Git à votre guise.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
git rebase –interactive origin/master
pick   94f56db Debug an error raised when importing view
squash 772e7e8 Re-join comments using DELIM
reword a04f10e Error on filter branch – print line
pick   e09b0a2 Added troubleshooting for msysgit + Cygwin
fixup  276c49a Added troubleshooting for missing master_cc branch
pick   a2c08f6 Added exclude configuration
pick   4c09e5e Ignore errors from _really_ long file paths
pick   9f38cf0 Actually, use fnmatch for exclude
# Rebase f698827..9f38cf0 onto f698827
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like « squash », but discard this commit’s log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.

Mercurial a une extension à peu près équivalente s’appelant « histedit« , cependant celle-ci utilise la méthode « strip » pour mettre à jour le dépôt, normalement en append-only (ajout seul en fin de fichier) ce qui génère un fichier de sauvegarde externe. Du coup, comment analyser les modifications de cette sauvegarde? Pendant combien de temps allez-vous conserver vos sauvegardes? Quelles sont les nouvelles commandes à utiliser pour restaurer le backup?

Revenons à Git. En comparaison, vous pourriez vous inquiéter de la perte d’un commit après le délai de 30 jours du reflog. Nous avons mentionné plus haut que Git ne détruit pas les objets ayant une référence, pour arrêter le « garbage collector » de Git et retrouver votre ‘sauvegarde’ facilement, il vous suffit d’appliquer un tag pour référence future – juste au cas où.

Un peu comme une branche?

Oui! Ces ‘sauvegardes’ sont juste des commits sans leur branche, du coup le reflog garde notre historique et vous n’avez pas besoin d’apprendre une nouvelle série de commandes pour savoir comment les utiliser.

« Rendez les choses aussi simples que possible, mais pas plus simples. »

Les Branches dans Git

Pendant longtemps les Branches ont été la « killer feature » de Git. D’ailleurs, Mercurial recommande toujours le clonage de dépôt pour chaque branche. Attendez, on utilise bien un DVCS et pas SVN?

Mercurial a également eu une commande « branche », qui créait un lien permanent entre un tag et un commit donné. Une fois appliqué, il est impossible de le modifier, sauf si vous avez finalement fusionné ou fermé la branche. Finalement, en raison de la demande populaire, l’extension Bookmark a été présentée comme un clone direct des branches Git, cependant au début vous ne pouviez pas faire le push de vos bookmarks sur le serveur.

Néanmoins, Git garde un avantage car tous les bookmarks Mercurial partagent le même ‘namespace‘. Pour comprendre ce que cela implique, jetons un oeil à un scénario assez normal où quelqu’un vient juste de faire le Push de certaines modifications sur le serveur.

1
2
3
4
5
6
7
8
9
10
git fetch
From bitbucket.org:atlassian/helloworld
* [new branch]      test       –> origin/test
565ad9c..9e4b1b8  master     –> origin/master
git log –graph –oneline –decorate –all
* 9e4b1b8 (origin/master, origin/test) Remove unused variable
| * 565ad9c (HEAD, master) Added Hello example
|/
* 46f0ac9 Initial commit

Où ce trouve ma branche master? Bien sûr, il n’y a rien de surprenant ici. Il y a deux branches dont le nom est « Master ». Le namespace du serveur (d’origine dans ce cas) indique clairement qui est qui.

Qu’en est-il de Mercurial?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> hg pull
pulling from default
importing bookmark test
divergent bookmark master stored as master@default
> hg glog
o changeset: 2:98c63da09bb1
| bookmark: master@default
| bookmark: test
| summary: Third commit
|
| o changeset: 1:d9989a0da93e
| | bookmark: master
| | summary: Second commit
|/
o changeset: 0:2e92d3b3d020
summary: First commit

Lorsque nous faisons le Pull vous pouvez voir que nous avons une branche qui ‘clash’ avec notre propre Master et une branche « test » valide… Puisqu’il n’y a pas de notion de namespace, nous n’avons aucun moyen de savoir quels sont les bookmarks locaux ou externes, et en fonction de la façon dont on a nommé les bookmarks, nous pourrions commencer à voir certains conflits faire surface.

Staging (Zone de transit)

C’est l’une des choses que les gens aiment ou détestent à propos de Git. Git a cette chose étrange qu’il appelle confusément « l’index ». Certaines personnes l’associent à une zone de transit (Staging) mais peu importe.

Dans Git, tout ce que vous voulez ajouté à un commit, doit d’abord passer par cette zone de transit (index). Comment ajoutez-vous du contenu dans l’index? En appelant « git add« . Cela a du sens pour les utilisateurs SVN, mais il peut être déroutant d’avoir à le faire cela pour les fichiers que vous avez déjà commité. La chose à garder à l’esprit est que vous « ajoutez » des modifications, pas aux fichiers eux-mêmes, mais au reflog. Ce que j’aime avec cette méthode, c’est que vous savez exactement ce qui va être ajouté au commit.

Pour aider à expliquer ce que je veux dire, voici une commande que j’utilise presque plus que toutes les autres, « git add –patch ».  Le « Patching » vous permet d’ajouter un morceau / hunk / snippets spécifique à partir d’un fichier, à la difference d’une approche tout ou rien.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
git add –patch
diff –git a/OddEven.java b/OddEven.java
index 99c0659..911da1b 100644
a/OddEven.java
+++ b/OddEven.java
@@32,6 +32,7 @@ public class OddEven {
* Object) and initializes it by calling the constructor.  The next line of code calls
* the « showDialog() » method, which brings up a prompt to ask you for a number
*/
+        System.out.println(« Debug »);
OddEven number = new OddEven();
number.showDialog();
}
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? n
@@49,7 +50,7 @@ public class OddEven {
* After that, this method calls a second method, calculate() that will
* display either « Even » or « Odd. »
*/
–            this.input = Integer.parseInt(JOptionPane.showInputDialog(« Please Enter A Number »));
+            this.input = Integer.parseInt(JOptionPane.showInputDialog(« Please enter a number »));
this.calculate();
} catch (final NumberFormatException e) {
/*
Stage this hunk [y,n,q,a,d,/,K,g,e,?]? y

Comme vous pouvez le voir, j’avais oublié de retirer une déclaration de bug dans le premier ‘hunk’, heureusement que nous avons vérifié avant de commit! Ce qui est bien ici, c’est que, si je veux, je peux le laisser là et tout de même accepter le deuxième ‘hunk’. Tout cela à partir du même fichier et sans avoir à rééditer quoi que ce soit après avoir constaté mon erreur.

Sans surprise, Mercurial a une extension Record qui imite ce comportement. Cependant, du fait que celle-ci ne sois qu’une extension (ou tout du moins une extension de base), elle doit copier les modifications ‘un-staged’  dans un emplacement temporaire, mettre à jour les fichiers de stockage, commit, et enfin annuler les modifications. Si vous faites une erreur, vous devez tout recommencer.

Ce qui est bien avec l’approche de Git, c’est qu’au niveau fondamental Git a seulement besoin de connaître, et ne se soucie que de la zone de transite (index) et n’a pas besoin de toucher à vos fichiers. Lorsque vous exécutez ‘git statuts‘ après avoir utilisé ‘git add‘ pour ajouter vos changements, vous pouvez revérifier que tout semble correct avant de poursuivre.

1
2
3
4
5
6
7
8
9
10
11
12
13
> git status
# On branch master
# Changes to be committed:
#   (use « git reset HEAD … » to unstage)
#
#   modified:   OddEven.java
#
# Changes not staged for commit:
#   (use « git add … » to update what will be committed)
#   (use « git checkout — … » to discard changes in working directory)
#
#   modified:   OddEven.java
#

Pour ceux d’entre vous étant inquiets à propos des tests sur du code ‘un-staged’, vous avez la possibilité d’utiliser « git stash -keep-index » pour effectuer une sauvegarde temporaire des éléments ne faisant pas partie du commit. Soit dit en passant, ce ‘stash’ est stocké, sans surprise, comme un autre commit et peut être vu par notre vieil ami le reflog.

1
2
git reflog –all
b7004ea refs/stash@{0}: WIP on master: 46f0ac9 Initial commit

Le jeu du blâme

L’une des choses intéressantes au sujet Git est qu’il ne fait pas le suivi des renommages de fichiers. C’est une source de préoccupation pour certaines personnes, mais je pense que Git a pris la bonne décision. Qu’est-ce qu’un ‘renommage‘ après tout? Nous ne faisons que déplacer du contenu d’un emplacement à un autre. Mais qu’advient-il lorsque nous déplaçons juste un morceau de fichier? La commande ‘git blame‘ est une commande très utile permettant d’afficher le dernier commit touchant à chaque ligne d’un fichier. Grâce à l’option magique ‘-C’, Git  détectera les lignes circulant entre les fichiers et dans ce cas le ‘-s’  a pour but de supprimer certaines dates et une partie du bruit de l’auteur.

1
2
3
4
5
6
7
8
9
10
git blame -s -C OddEven.java
d46f0ac9 OddEven.java     public void run() {
d46f0ac9 OddEven.java         OddEven number = new OddEven();
d46f0ac9 OddEven.java         number.showDialog();
d46f0ac9 OddEven.java     }
d46f0ac9 OddEven.java
565ad9cd Hello.java       public static void main(final String[] args) {
565ad9cd Hello.java           new Hello();
565ad9cd Hello.java       }
d46f0ac9 OddEven.java }

Conclusion

Git signifie que vous n’aurez jamais à dire, « j’aurais du / si j’avais su », cela peut arriver avec Mercurial. À la seconde où vous voulez rebase / modifier un commit – quelque chose que je sais que je fais tous les jours – vous sortez de la zone de confort de Mercurial. Le format append-only a été intentionnellement conçu sans ce comportement à l’esprit. Je suis d’accord avec Scott Chacon (GitHub); pour moi Mercurial est très similaire à un « Git Lite« .

Git n’est pas parfait. Cependant, je dirais qu’il y a des choses plus importantes que d’avoir une ligne de commande parfaite. On aimerait tous que Git fasse les choses un peut mieux, si seulement il nous donnait des erreurs moins mystérieuses et tournait plus rapidement sur Windows, etc. Mais en fin de compte ce ne sont que des problèmes superficiels, vous avez toujours le choix d’écrire un alias si vous n’aimez pas la syntaxe et pouvez toujours installer Linux si vous avez l’impression que Windows se traine. Le format de dépôt Git et à la source de l’innovation et des possibilités offertes par nos outils DVCS.

Antisèche Git et Mercurial

Nous espérons que cet article et le précédent « Les avantages de Mercurial par rapport Git (en)« , mettront en lumière les forces et faiblesses de ces deux systèmes.

Si vous venez d’un système de version centralisé comme Subversion (SVN) et que vous vous dirigez vers un système décentralisé comme Git. Je vous conseille de consulter notre tutoriel Git et notre guide sur les workflows Git.

Git Essentials

Vous voulez essayer Git dans votre organisation? Découvrez le Git Essentials.

Essayez le Git Essentials Gratuitement