-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgit.html
More file actions
1024 lines (868 loc) · 44.6 KB
/
Copy pathgit.html
File metadata and controls
1024 lines (868 loc) · 44.6 KB
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<html>
<head>
<meta http-equiv="content-language" content="fr" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Tutoriel GIT</title>
<style>
.gitimg { height: 400px; margin: 1em auto; }
pre { background: #333333; color: white; font-weight: bold; padding: 1em;}
.image { text-align: center; margin-bottom: 1em;w }
.caption { font-style: italic; font-size: 0.7em; }
</style>
</head>
<body>
<h1 style="font-size: 3em">Tutoriel GIT</h1>
<strong>Auteurs : </strong>Bertrand Chazeau, Clément Moussu, Laurent Charignon et Vaibhav Singh.
<h2>Table des matières</h2>
<ul>
<li><a href="#part1">I. Introduction</a></li>
<li><a href="#part2">II. Commandes de base</a></li>
<li><a href="#part3">III. Gestion des branches</a></li>
<li><a href="#part4">IV. Exemple</a></li>
</ul>
<h2 id="part1">I. Introduction</h2>
<h3 id="objectifs">Objectifs du tutoriel</h3>
À la fin de ce tutoriel vous :
<ul>
<li>Saurez ce que sont les systèmes de gestions de versions (Concurrent Version System en anglais)</li>
<li>Pourrez installer et configurer le système de gestion de version Git</li>
<li>Connaîtrez les commandes de bases pour une utilisation quotidienne de Git</li>
<li>Aurez la possibilité de travailler de manière flexible à l'aide des branches</li>
<li>Ne serez pas perdu avec la cheat sheet et les pointeurs que nous vous fournissons</li>
</ul>
<h3 id="vocabulaire">Systèmes de gestions de versions : vocabulaire</h3>
<p>Un logiciel de gestion de versions (VCS en anglais) permet de stocker les
différentes versions d'un ensemble de fichiers afin de faciliter
l'évolutivité d'une production informatique.
Par la suite nous utiliserons l'abréviation VCS pour désigner les logiciels
de gestions de versions.
Certains termes sont spécifiques au monde des VCS ou à GIT et il est
important de bien les comprendre pour pouvoir travailler efficacement :
</p>
<h4 >Commit et branche</h4>
<p>Le mot commit désigne à la fois la création d'une nouvelle version
(lorsque c'est un verbe) et cette nouvelle version (lorsque c'est un nom).
"Je commit" veut dire, j'entérine les changements que j'ai effectué et ils
constituent une version. "Le deuxième commit de mon projet", désigne sa
deuxième version. Les commits sont organisés en arbre et la figure suivante
en donne un exemple :</p>
<div class="image">
<img src="./img/arbre1.png" style="margin:30px"/>
<div class="caption">Source : <a href="http://progit.org/book">Progit</a></div>
</div>
<p>C0...C5 désignent des versions. Les flèches représentent des liens de
parentés, entendons par là que C1 a été créé à partir de C0, C1 est
postérieur à C0.
Au niveau de C2 le développement s'est divisé en deux branches : master et
iss53. Ces branches correspondent à la notion intuitive d'une branche d'un
arbre par exemple: la branche master va de C0
à C4. Ne prenez pas garde à la notation adoptée pour les branches, elle
sera expliquée par la suite.</p>
<h4>Dépôt (Repository)</h4>
<p>Désigne l'ensemble des fichiers conservés par le système de gestion de version.</p>
<h4>Remote </h4>
<p>Désigne un dépôt distant (par opposition à un dépôt local).</p>
<h3 id="architecture">Systèmes de gestions de versions : architecture</h3>
Le système de gestion de versions se déclinent en trois types :
<ul>
<li><strong>Local</strong> : le dépôt se situe uniquement sur la machine de
l'utilisateur</li>
<li><strong>Centralisé</strong> : les informations se situent sur un
serveur central. L'information y est principalement située et le ou les
utilisateurs doivent s'y connecter pour travailler</li>
<li><strong>Distribué</strong> : les informations sont stockées sur un
ensemble de machines de manière distribuée. On peut reconstituer le
contenu du projet à partir de n'importe quelle machine, il n'y a pas un
point de faiblesse comme dans le cas de l'architecture centralisée.</li>
</ul>
A partir de maintenant nous nous intéresserons à Git qui est un système de
gestion de versions distribué.
<h3 id="git">Git : aperçu des possibilités</h3>
Vous commencez certainement à deviner le fonctionnement des système de
gestion de version, voici une partie des avantages qu'apporte Git aux
développeurs :
<ul>
<li>Possibilité de revenir en arrière dans le développement</li>
<li>Possibilité de travailler en parallèle</li>
<li>Possibilité de travailler uniquement en local(par exemple sans
internet) et de transmettre toutes les informations dans un second temps
</li>
<li>Possibilité de débugger automatiquement pour localiser l'introduction
d'un bug parmi l'arbre des commits</li>
<li>...</li>
</ul>
<h3 id="installer">Installer Git</h3>
Sous ubuntu ou debian il vous suffit d'installer le paquet git pour pouvoir
disposer de toutes les fonctionnalités du logiciel.
<pre>sudo aptitude install git</pre>
Sous mac os X 10.6 git est déjà présent de base dans le système.
<h3 id="configurer">Configurer Git</h3>
Git se configure soit en ligne de commande, soit directement dans un fichier
de config qui se trouve dans votre home : .gitconfig.
En voici un exemple :
<pre>[user]
name = Laurent Charignon
email = laurent.charignon@telecom-paristech.fr
[core]
editor = vim
[alias]
co = checkout
br = branch
st = status
a = add</pre>
Ici il faut comprendre que mon nom et mon email sont spécifiés, que j'utilise
vim et que j'utilise des alias pour quatre commandes.
Pour modifier ce fichier par le biais de la ligne de commande on utilise la
commande git config avec la syntaxe suivante :
<pre>git config --global catégorie.champ valeur</pre>
par exemple :
<pre>git config --global user.name "Laurent Charignon"</pre>
L'option --global permet de faire que le choix s'applique à tous les projets
que l'on utilise. Si vous voulez faire des configurations spécifiques à un
projet vous pouvez retirer cette option.
<code>man git config</code> fournit une liste de toutes les options
configurables avec la syntaxe mentionnée ci-dessus.
<h3 id="gui">Interface graphique pour visualiser les branches</h3>
<p>Vous pouvez utiliser différentes interfaces graphiques pour visualiser
l'arbre des commits. En voici deux exemples :
</p>
<div class="image">
<img src="./img/gitk.png" style="height:100%" />
<div class="caption">Gitk et son interface Tk</div>
</div>
<div class="image">
<img src="./img/qgit.png" style="height:100%" />
<div class="caption">Qgit et son interface Qt</div>
</div>
<h2 id="part2">II. Commandes de base</h2>
<h3>Commande <code>git clone</code></h3>
<p> Essayons de voir ce qui se passe dans un projet. Au départ, nous
supposons que notre projet est déjà présent dans un dépôt distant.
Pour télécharger les fichiers (i.e. pour cloner le dépôt), nous
utilisons la commande suivante : </p>
<pre>git clone <URL></pre>
<p> par exemple : <code>git clone elecinf381@hg.comelec.enst.fr:2011/PrenomNom.git</code>
Notre machine contient une copie du dépôt distant. Maintenant, si nous
voulons ajouter un nouveau fichier à notre projet, nous créons le
fichier, par exemple <code>EthernetDriver.c</code>, mais git ne sait
rien sur ce fichier. L'état actuel du fichier est dit
<em>untracked</em>.
</p>
<h3>Commande <code>git status</code></h3>
<p> Lorsque l'on a modifié le contenu du fichier, il devient
<em>modified</em> pour git. Le répertoire dans lequel nous
travaillons est appelé <em>répertoire de travail</em> ou <em>working
directory</em>. Nous utilisons ce répertoire pour travailler. Pour
obtenir le statut des différents fichiers du dépôt on utilise la
commande <code>git status</code>. </p>
<p>
Voilà ce que renvoie cette commande.
<pre>$ git status Etherenet.c
# On branch master
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# EthernetDriver.c
nothing added to commit but untracked files present (use "git add" to track)</pre>
</p>
<h3>Commande <code>git add</code></h3>
<p>
Pour ajouter ce fichier dans dépôt local, nous utilisons la
commande <code>git add <file></code> Après cela, git commencera
le suivi de ce fichier et le statut de ce fichier passera de
<em>untracked</em> à <em>tracked</em>. Lors d'un changement du
contenu de ce fichier git détecte ce changement et le fichier passe
dans l'état <em>modified</em> Après avoir fait tous les changements,
nous devons tester nos modifications et une fois satisfaits nous
pouvons procéder à l'enregistrement de ces changements. C'est ce que
l'on appelle un <em>commit</em>. Avant de regrouper ces fichiers au
sein d'un commit, il faut préciser à git lesquels il doit envoyer pour
constituer ce commit. Pour ce faire on utilise soit la commande
<code>git add</code> suivie du nom d'un fichier. On peut utiliser la
commande <code>git add -u</code> qui tient compte de toutes les
modifications effectuées sur les fichiers déjà suivis par git.
</p>
<p>
Un <em>snapshot</em> est fait au moment du <code>add</code>. C'est ce
<em>snapshot</em> qui sera commité. <strong>Les modifications
intermédiaires ne seront pas commitées</strong>. Pour qu'elles le soit, il
suffit de refaire un <code>add</code>.
</p>
<h3>Commande <code>git rm</code></h3>
<p>
Pour arrêter de suivre un fichier nous utilisons la commande
<code>git rm</code> suivi du nom d'un fichier.<br />
Pour enlever un fichier de la <em>staging area</em>, utiliser :
<code>git rm --cached</code> suivi du nom d'un fichier.
</p>
<h3>Commande <code>git commit</code></h3>
<p>
<pre>git commit</pre> permet d'enregistrer les changements en
<em>staging area</em> dans le dépôt. Seuls les changements se
trouvant dans la <em>staging area</em> feront partie du prochain
commit. On peut utiliser la commande : <pre>git commit -a</pre> pour
indiquer à git d'envoyer dans le prochain commit tous les fichiers
déjà suivis en prenant compte toutes les modifications effectuées
depuis le dernier commit les ayant concernés.
</p>
<h3>Commande <code>git log</code></h3>
<p>
Pour afficher le log des commits :
<pre>git log </pre>
et d'un fichier particulier :
<pre>git log <file></pre>
</p>
<h3>Commande <code>git diff</code></h3>
<p>
Pour afficher les évolutions entre deux commits :
<pre>git diff <sha1_1> <sha1_2> <file></pre>
On peut aussi utiliser :
<pre>git log -p</pre>
</p>
<h3>Commande <code>git show</code></h3>
<p>
Pour afficher un fichier d'un commit particulier :
<pre>git show <sha1> <file></pre>
</p>
<h3>Commande <code>git grep</code></h3>
<p>
Cette commande permet de rechercher du texte dans les fichiers
trackés par git. Cela est plus rapide et plus efficace
qu'un <code>grep</code> standard qui va rechercher dans tous les
fichiers temporaires, fichiers objets, exécutables...
</p>
<h3>Commande <code>git revert</code></h3>
<p>
Cette commande permet d'insérer au sommet de la branche courante
le dual d'un commit. Cela est particulièrement utile dans les
branches telles que <code>master</code> dans laquelle on ne peut
pas se permettre d'avoir une perte d'historique. Par exemple si
nous sommes dans la situation suivante :
<pre>$ git log
commit 67d9dfa351f642159480b306f1004c158a283e92
Author: John Doe <doe@enst.fr>
Date: Mon Mar 7 21:52:24 2011 +0100
Fix issue #1344.
commit dc7767340c492fd93b55955a28572c98636a10f7
Author: John Doe <doe@enst.fr>
Date: Mon Mar 7 21:38:06 2011 +0100
Add feature #755.
commit b12caf55ad9c254b1997435031da49a5a05f902b
Author: John Doe <doe@enst.fr>
Date: Mon Mar 7 21:27:09 2011 +0100
Add feature #740.</pre>
Notre collègue John Doe a introduit des erreurs dans son commit
ajoutant la fonctionnalité 740. Ce commit étant sur la branche
master nous ne pouvons pas l'en enlever. Nous allons donc
exécuter la commande :
<pre>git revert b12caf55</pre>
Cela va appliquer le diff inverse de celui du
commit <code>b12caf55</code> annulant ainsi tous les changements
faits dans ce commit. Git va faire cela de manière autonome, à
condition que les commits <code>dc776734</code>
et <code>67d9dfa3</code> ne modifient pas les mêmes parties des
fichiers. Dans ce cas il faudra les fusionner manuellement.
</p>
<h3>Commande <code>git reset</code></h3>
<p>
Cette commande permet de déplacer le pointeur <code>HEAD</code>
et la branche courante vers un commit arbitraire. Il existe
néanmoins plusieurs variantes :
<pre>git reset --hard <commit></pre>
L'option <code>--hard</code> provoque la perte de tous les
changements actuels et remet la branche courante à l'état du
commit donné en paramètre.
<pre>git reset --soft <commit></pre>
L'option <code>--soft</code> remet la branche courante à l'état
du commit donné en paramètre mais laisse les différence entre ce
commit et HEAD (avant le <code>reset</code>) dans la <em>staging
area</em>. Il sont ainsi prêts à être enregistrés dans un autre
commit.
</p>
<h3>Commande <code>git bisect</code></h3>
<p>
Cette commande très utile permet de chercher par dichotomie le
commit qui aurait introduit un dysfonctionnement. Si le code
versionné ne fonctionne plus, que l'on est capable de donner un
commit pour lequel le code marchait et que l'on peut formuler un
test permettant de savoir si le code fonctionne alors
<code>git bisect</code> trouvera en un temps optimal le commit
responsable. Cette <a href="http://book.git-scm.com/5_finding_issues_-_git_bisect.html">page</a>
du <a href="http://book.git-scm.com/index.html">Git Community
Book</a> explique en détail l'utilisation de <code>git
bisect</code>.
</p>
<h2 id="part3">III. Gestion des branches</h2>
<h3> 1 - Gestion locale des branches</h3>
<p>Au sein d'un dépôt, les commits sont organisés sous la forme d'un arbre.
On peut voir sur le schéma suivant que chaque commit est identifié par un
numéro unique : le <a href="http://en.wikipedia.org/wiki/Sha1">sha1</a>
(le risque de collisions étant très faible).
On remarque aussi que chaque commit pointe vers le commit précédent. De plus le commit f30ad a deux fils :
c2b9e et 87ab2. Il y a un embranchement, les deux branches étant master et
testing.</p>
<div class="image">
<img src="img/Branch1.png" alt="Branches"/>
<div class="caption">Source : <a href="http://progit.org/book">Progit</a></div>
</div>
<p>Une branche n'est rien plus qu'un pointeur vers un commit. Un autre
pointeur est HEAD qui pointe vers le commit sur lequel on est en train
de travailler (le pointeur est sauvegardé dans le fichier .git/HEAD). La
branche master est particulière, c'est la branche principale, de
production, elle n'est censée contenir que du code fonctionnel.
Les branches sont très utiles. En effet, elles permettent de faire du
développement en parallèle. Supposons qu'on veuille ajouter une
fonctionnalité sans perturber l'avancée de master (par d'autre ou par
nous-mêmes). Il suffit pour cela de travailler dans une autre branche <em>br1</em>.
On pourra à tout moment revenir dans master sans être gêné par les
modifications faites dans <em>br1</em>.</p>
<p>Voici quelques commandes pour créer des branches et se déplacer entre elles :
<ul>
<li>Pour créer une branche au niveau du commit courant :<br />
<pre>git branch <name> </pre></li>
<li>Pour en supprimer une :<br />
<pre>git branch -d <name> </pre></li>
<li>Pour lister les branches (le * dans le listing correspond à la branche courante) :
<ul>
<li>branches locales :<br />
<pre>git branch</pre></li>
<li>branches distantes :<br />
<pre>git branch -r</pre></li>
<li>branches mergées (cf partie 3) à la branche courante :<br />
<pre>git branch --merged</pre></li>
<li>branches non mergées à la branche courante :<br />
<pre>git branch --unmerged</pre></li>
</ul>
<li>Pour voir le dernier commit de chaque branche :<br />
<pre>git branch -v</pre></li>
<li>Pour se déplacer sur un commit/une branche :<br />
<pre>git checkout <commit/branch></pre></li>
<li>Pour créer une branche et la rendre active en même temps :<br />
<pre>git checkout -b <name></pre></li>
</ul></p>
<h3>2 - Interactions avec un dépôt distant</h3>
<p>Pour partager son travail et se protéger de toutes pertes de données
qui pourraient advenir, il faut envoyer fréquemment ses commits sur un
serveur distant (dans notre cas <code>hg.comelec.enst.fr</code>). Pour cela
plusieurs commandes :
<ul>
<li><pre>git fetch</pre> <br />
Permet de se synchroniser avec le serveur
<strong>sans changer l'état courant</strong>. <br />
On reçoit l'ensemble des nouveaux commits et une copie locale des
branches distantes (leur nom est préfixé par le nom du serveur, en
général origin/)</li>
<li><pre>git push <remotename> <branch>:<remotebranch></pre><br />
Envoie au serveur <remotename> les nouveaux commits de <branch> et
met à jour le pointeur <remotebranch> au niveau de <branch></li>
<li><pre>git push <remotename> <branch></pre><br />
Équivalent de :<br />
<pre>git push <remotename> <branch>:<branch></pre></li>
<li><pre>git push <remotename> :<remotebranch></pre><br />
Supprime la branche distante</li>
</ul>
</p>
<p>
Pour tracker une branche, c'est-à-dire qu'elle soit associée à une branche
distante, plusieurs moyens :
<ul>
<li><pre>git push -u <remotename> <branch>:<remotebranch></pre></li>
<li><pre>git checkout -b <branch> <remotename>/<branch></pre></li>
<li><pre>git checkout --track <remotename>/<branch></pre></li>
</ul>
On peut alors utiliser push et pull(voir plus loin) sans argument sur ces
branches.
</p>
<h3>3. Merge</h3>
<p>Une fois que l'on a terminé de travailler sur une branche, on peut
vouloir la fusionner avec une autre (master par exemple). </p>
<div class="image">
<img src="img/Merge.png" alt="Merge"/>
<div class="caption">Source : <a href="http://progit.org/book">Progit</a></div>
</div>
<p> Dans l'exemple ci-dessus, une branche iss53 a été créée en C2, puis
deux commits (C3 et C5) y ont été fait. En parallèle un nouveau commit
(C4) a été fait dans master. On veut appliquer les modifications de iss53
sur master en mergeant cette branche. Il en résulte un commit (C6) ayant
deux parents.</p>
<p> La procédure pour faire un merge est la suivante :
<ul>
<li>Se déplacer dans la branch où merger :<br />
<pre>git checkout <branch1></pre></li>
<li>Merger :<br />
<pre>git merge <branch2></pre></li>
</ul>
<p>Il peut y avoir un <em>merge conflict</em> si git n'arrive pas à
fusionner un ou plusieurs fichiers. Cela arrive s'ils ont été
modifiés aux mêmes lignes dans les deux branches. Dans ce cas-là :
<ul>
<li> Faire un <code>git status</code> pour voir les problèmes</li>
<li> Les résoudre par exemple avec <code>git mergetool</code> qui
utilise le logiciel configuré avec
<code>git config --global merge.tool <software></code>.<br />
Git ajoute des balises pour délimiter les problèmes :<br />
<code style="display:block"><<<<<<< <branch1>:<filename><br />
<ligne(s) version 1><br />
=======<br />
<ligne(s) version 2><br />
>>>>>>>> <branch2>:<filename></code></li>
<li> Il ne reste plus qu'à faire un commit</li>
</ul>
</p>
<p> Dans les deux cas, avant de faire un commit et de pusher, il ne faut
pas oublier de tester le code résultant du merge.</p>
<p> Notez aussi la commande
<pre>git pull <remotename> <branch>:<branch></pre>
qui fetch et ensuite merge la branche.</p>
<h3> 4. Rebase</h3>
<p> Une autre façon de fusionner des branches est le rebase, détaillé dans
l'<a href="#part4">exemple</a> suivant. Cela consiste à appliquer les
modifications d'une branche, commit par commit, au sommet d'une autre :
</p>
<p>
Supposons que l'on ait une branche experiment et qu'on veuille la
fusionner avec master :
<div class="image">
<img src="img/Rebase0.png" alt="Original" />
<div class="caption">Source : <a href="http://progit.org/book">Progit</a></div>
</div>
Un merge donnerait cela :
<div class="image">
<img src="img/Reb_merge.png" alt="Merge" />
<div class="caption">Source : <a href="http://progit.org/book">Progit</a></div>
</div>
Avec un rebase on crée un nouveau commit C3' au sommet de master
contenant les mêmes modifications que C3 :
<div class="image">
<img src="img/Rebase.png" alt="Rebase" />
<div class="caption">Source : <a href="http://progit.org/book">Progit</a></div>
</div>
Il suffit ensuite de faire un fastforward de master :
<div class="image">
<img src="img/Rebased.png" alt="Rebased" />
<div class="caption">Source : <a href="http://progit.org/book">Progit</a></div>
</div>
</p>
<p> Les commandes pour réaliser cela sont :
<ul>
<li><pre>git rebase <branch></pre> rebase la branche courante sur
branch</li>
<li><pre>git rebase <--onto><branch1> <branch2></pre> rebase branch2
sur branch1</li>
<li>On utilise ensuite la commande merge dans branch pour faire un
fastforward<br />
<pre>git checkout <branch1> && git merge <branch2></pre></li>
</ul>
</p>
<p> Il peut y avoir avoir des conflits comme pour un merge. Après les avoir
résolus avec <code>git mergetool</code> faire un :
<pre>git rebase --continue</pre>
<br />
<pre>git rebase --abort</pre> permet d'annuler un rebase en cours.
</p>
<p>Attention à ne pas faire de rebase d'une branche d'où part d'autres
branches ou sur laquelle d'autres personnes travaillent. Cela rendrait
difficile les merges/rebases futurs.</p>
<p> <pre>git pull --rebase</pre> fait un rebase à la place d'un merge.</p>
<h2 id="part4">IV. Exemple</h2>
<p>
Dans un premier temps vous allez vouloir cloner votre
dépôt. Cela se fait avec la commande <code>git clone</code> qui
va créer une copie locale de votre dépôt distant. Ce dépôt local
va contenir <strong>tout</strong> l'historique de votre
projet. Si le dépôt distant venait à être perdu votre dépôt
local contiendrait toutes les informations nécessaires à sa
recréation à l'exception des commits faits après votre
dernier <code>clone</code> / <code>pull</code>
/ <code>fetch</code>. Git utilise par défaut le
protocole <em>ssh</em> donc, dans notre cas, nous n'avons rien
besoin d'ajouter avant le nom d'utilisateur (pas
de <code>http://</code>, <code>https://</code>, <code>git://</code>
...) :
<pre>git clone elecinf381@hg.comelec.enst.fr:2011/Projet.git</pre>
Pour éviter toute confusion précisons que <code>2011</code>
n'est <emph>pas</emph> un numéro de port mais un répertoire sur
le serveur. Notez que vous ne clonez pas en tant que vous même
mais avec le nom d'utilisateur <code>elecinf381</code>. C'est
votre clé ssh qui vous identifie et vous permet de vous
connecter. Plus précisément, pour les gens qui se mélangent un
peu dans les clés, c'est <strong>la</strong> clé dont vous avez
transmis la partie publique aux enseignant. Pour que cela
fonctionne il faut donc que vous ayez cette clé sur l'ordinateur
duquel vous voulez cloner. Elle doit se trouver dans le
répertoire <code>~/.ssh/</code> sous la forme
de <strong>deux</strong> fichiers <code>id_Xsa.pub</code> (clé
publique) et <code>id_Xsa</code> (clé privée <strong>à ne jamais
transmettre à personne</strong>). Votre clé privée est
elle-même chiffrée à l'aide d'un mot de passe que vous devez
entrer au moment du clone (sauf si vous avez un agent ssh). Vous
devez donc vous retrouver face à une invite semblable à :
<pre>Enter passphrase for key '~/.ssh/id_Xsa':</pre>
Si l'invite demande un mot de passe et non une passphrase :
<pre style='text-decoration:line-through'>Password:</pre>
c'est que votre clé n'a <em>pas</em> été trouvée ou que ce
n'est <em>pas</em> la clé que vous avez transmise aux
enseignants. Ne vous acharnez donc pas à entrer votre passphrase
ou votre mot de passe de l'école cela ne
marchera <strong>pas</strong>.
</p>
<div class="image">
<img class="gitimg" src="img/2.png" alt="Clonage d'un dépôt" />
<div class="caption">Figure 1 : Clonage d'un dépôt</div>
</div>
<p>
Regardons la figure 1. Nous remarquons que le dépôt local
contient exactement les mêmes commits que le dépôt distant. On
remarque qu'une branche <code>b1</code> est présente et a
divergé de <code>master</code> trois commits dans le
passé. Considérons que cette branche contient des commits
effectué par un collègue, Bob, et qui corrigent une erreur
connue dans le code source. Notons également l'apparition de
nouvelles branches <code>origin/...</code> ici représentées en
gris. Ces branches sont des <em>copies locales des branches
distantes</em>. Ce concept un peu dur à appréhender vous semblera
très vite évident. Elles indiquent la position d'une branche sur
le serveur distant. Par exemple si vous faites évoluer votre
branche <code>master</code>, la
branche <code>origin/master</code> n'évolue pas et vous permet
ainsi savoir dans quel état est la branche sur le serveur avant
que vous ne pushiez. Une fois que vous pushez sur le serveur la
branche <code>origin/master</code> se déplace sur le même commit
que <code>master</code>.
</p>
<p>
Mais pourquoi <code>origin/...</code> ? Nous avons vu
précédemment que git est distribué. Cela implique que tout ne
passe pas par un seul serveur comme dans les gestionnaires de
version centralisés. Il vous est possible d'avoir plusieurs
serveurs distants sur lesquels vous pushez, mais vous pouvez
également puller des changements sur des dépôts de vos
collègues. Notez que pour pusher sur un dépôt il doit
être <em><a href="http://gitready.com/advanced/2009/02/01/push-to-only-bare-repositories.html">bare</a></em>;
vous ne pouvez donc pas pusher sur le dépôt d'un collègue sauf
si il vous a mis à disposition un dépôt <em>bare</em>. Git nomme
tous ces différents dépôts distants <em>remote</em>. Une remote
va donc se composer de l'adresse d'un dépôt et d'information sur
les branches à suivre et avec lesquelles se
synchroniser. Lorsque vous clonez git crée automatiquement une
remote spéciale appelée <code>origin</code> qui contient
l'adresse du dépôt que vous avez cloné. Si vous avez deux
remotes <code>origin</code> et <code>enst</code>, un dépôt que
vous avez à l'école par exemple, vous aurez donc pour une
branche locale <code>master</code> deux
branches <code>origin/master</code> et <code>enst/master</code>
qui décrivent l'état des branches sur ces deux remotes.
</p>
<p>
Essayons de créer une branche. Pour cela plaçons nous au sommet
de <code>master</code> :
<pre>git checkout master</pre>
Puis créons une nouvelle branche que l'on nomme <code>b2</code>
:
<pre>git branch b2</pre>
Enfin positionnons nous sur cette nouvelle branche :
<pre>git checkout b2</pre>
Se positionner sur une branche signifie que c'est cette branche
que le prochain commit déplacera. On remarque sur la figure 2
l'apparition de la branche <code>b2</code> au niveau
de <code>master</code>.
</p>
<div class="image">
<img class="gitimg" src="img/3.png" alt="Création d'une branche" />
<div class="caption">Figure 2 : Création d'une branche</div>
</div>
<p>
Mais pourquoi se placer au sommet de <code>master</code> ? Et
qu'est ce que <code>master</code> ? La
branche <code>master</code> est, comme son nom l'indique, la
branche principale de vos développements. Cette branche est
communément utilisée pour du code en production c'est à dire du
code qui compile et qui marche. Pourquoi ? Parce que c'est la
branche commune à tout le monde et la branche sur laquelle tout
le monde s'appuie. Il serait embêtant de devoir corriger les
erreurs de quelqu'un d'autre avant de commencer votre
travail. S'appuyer sur <code>master</code> est la garantie que
l'on part d'une base stable (à condition que tout le monde
respecte les règles du jeu) avant d'implémenter sa
fonctionnalité. On travaillera donc toujours en trois étapes :
travail dans une branche, vérifications et tests et enfin
intégration dans <code>master</code>.
</p>
<p>
C'est ce que nous allons faire ici. Notre
branche <code>b2</code> étant créée, nous allons faire des
modifications qui ajoutent une fonctionnalité à nos
développements :
<pre><modifications> ...
git add <files>
git commit</pre>
Les commits devant rester atomiques, supposons que l'on réitère
ces opérations trois fois pour ajouter notre fonctionnalité. La
figure 3 montre l'état du dépôt local après ces
modifications. Le dépôt distant reste pour l'instant inchangé
: <em>tous les changements sont locaux</em>.
</p>
<div class="image">
<img class="gitimg" src="img/3-quad.png" alt="Modifications" />
<div class="caption">Figure 3 : Modifications</div>
</div>
<p>
Nous allons maintenant vouloir transférer nos modifications sur
le serveur. Qu'ils soient stables ou non, justifiés ou non,
importants ou non, nous vous conseillons de transférer
régulièrement vos changements sur le serveur. Cela vous
permettra avant tout d'avoir une sauvegarde en cas de problème
avec votre ordinateur (perte, vol, détérioration, mauvaise
manipulation, bug...). Cela permet également à vos collègues de
voir votre travail et de vous faire des remarques et des
suggestions. La figure 4 nous montre l'état des dépôts local et
distant après l'execution de la commande :
<pre>git push origin b2</pre>
Cette commande qui est un raccourci pour <code>git push origin
b2:b2</code> transfère au serveur tous les commits qu'il n'a pas
déjà puis déplace la branche distante <code>b2</code> sur le
commit pointé par la branche locale <code>b2</code>. Notons la
création d'une branche <code>origin/b2</code> dont vous avez
maintenant compris la sémantique et l'utilité.
</p>
<div class="image">
<img class="gitimg" src="img/4.png" alt="Mise à jour d'une branche distante" />
<div class="caption">Figure 4 : Mise à jour d'une branche distante</div>
</div>
<p>
Supposons à présent que Bob, qui est en week-end, vous appelle
et vous dise qu'il faut absolument que vous intégriez sa
branche <code>b1</code> dans <code>master</code> avant la votre
car Sally, une autre collègue, en a besoin rapidement. Ce
scénario qui semble tiré par les cheveux est en réalité très
courant.
</p>
<p>
Vous allez dans un premier temps laisser votre
branche <code>b2</code> de coté pour vous concentrer sur celle
de Bob : <code>b1</code>. Il ne serait pas judicieux (et
compliqué) d'intégrer les commits <code>baf</code>
et <code>cac</code> entre les commits <code>a3f</code>
et <code>1ed</code> de <code>master</code>. Cela modifierait
l'historique de <code>master</code> <strong>ce qu'il ne faut
jamais faire</strong>. En effet, si on faisait ça, les
commits <code>1ed</code> <code>f5a</code> <code>053</code>
devraient être réappliqués au dessus de <code>cac</code> et
changeraient donc d'identifiant <em>sha-1</em>. Cela signifie
que toutes les personnes qui auraient fait partir une branche
d'un de ces trois commits ne sont plus reliés à master de la
façon qu'ils pensent. Cela implique qu'ils auraient
potentiellement beaucoup d'opérations compliquées à faire alors
qu'ils ne sont pas responsables de ces changements. À éviter...
</p>
<p>
Dans ce cas on va préférer utiliser une opération
appelée <code>rebase</code>. Un rebase d'une branche sur une
autre permet de réappliquer les commits de la première au sommet
de la seconde. Le fait de réappliquer ces commits au dessus
d'autres qui contenaient de nouvelles modifications modifie leur
identifiant <em>sha-1</em>. La figure 5 présente l'état du dépôt
local après l'execution de la commande :
<pre>git rebase master b1</pre>
Ou si nous nous trouvions déjà sur <code>b1</code> :
<pre>git rebase master</pre>
Nous constatons que les branches <code>b1</code>
et <code>origin/b1</code> sont devenues deux branches totalement
différentes : différences des points de divergence avec master
et différence des identifiants des commits. La
branche <code>b1</code> est désormais au sommet de master et est
prête à y être intégrée, à condition que d'autres commits ne
soient pas pushés sur la branche <code>master</code> du serveur
entre temps. Dans ce cas il faudrait refaire
des <code>rebase</code> tant qu'il y a des nouveaux commits
sur <code>master</code>, cela arrive fréquemment sur des dépôts
où beaucoup de gens pushent.
</p>
<div class="image">
<img class="gitimg" src="img/5.png" alt="Rebaser une branche sur une autre" />
<div class="caption">Figure 5 : Rebaser une branche sur une autre</div>
</div>
<p>
Le <code>rebase</code> n'est néanmoins pas une opération
anodine. Les changements intervenus entre l'ancien et le nouveau
point de divergence peuvent entrer en conflits avec les
changements sur la branche. Git va donc pouvoir vous demander de
fusionner des fichiers quand il ne peut pas le faire lui
même. Dans ce cas le rebase s'arrête à l'endroit du conflit,
dans le même état que juste avant de faire le commit, et git
vous laisse faire <em>ce que vous voulez</em> pour corriger le
conflit. Vous pouvez faire absolument tout ce que vous voulez,
vous êtes dans le passé et vous faites ce que vous pouvez pour
que le présent soit possible. Vous pouvez par exemple utiliser
l'outil de fusion défini dans votre fichier de configuration en
exécutant la commande :
<pre>git mergetool</pre>
Une fois les changements et les fusions réalisées (plus
de <code>both modified</code> dans le <code>git status</code>)
vous continuez le rebase en exécutant :
<pre>git rebase --continue</pre>
Enfin si vous êtes en train de tout casser et que vous ne voyez
plus comment vous en sortir vous aimerez la possibilité
d'annuler le rebase en cours et de revenir dans le même état
(état du dépôt et pas seulement de la branche en cours) à l'aide
de la commande :
<pre>git rebase --abort</pre>
</p>
<p>
Pour pouvoir intégrer <code>b1</code> dans <code>master</code>
nous aurions également pu utiliser un <code>merge</code>. Nous
utilisons ici un rebase car il plus adapté à ce que nous voulons
faire. Il est pratique que l'historique de la
branche <code>master</code> reste linéaire, notamment pour
chercher l'apparition d'une erreur dans l'historique. Un merge
introduit un nouveau commit qui fusionne les deux branches et
qui a de ce fait deux parents. Il est moins aisé de remonter
dans l'histoire d'une branche si il y a plusieurs chemins
possibles. Par ailleurs en utilisant un rebase on efface de
l'historique le fait que la fonctionnalité ait été développée
sur une branche et le moment auquel cela a été fait. Dans la
plupart des cas cette information n'apporte rien, mais on peut
très bien vouloir la conserver et faire un merge.
</p>
<p>
Nous allons maintenant vouloir synchroniser la
branche <code>b1</code> du serveur avec la notre. Essayons
d'exécuter la commande suivante :
<pre>git push origin b1</pre>
Git affiche un message d'erreur de la forme :
<pre>To elecinf381@hg.comelec.enst.fr:2011/Project.git
! [rejected] master -> master (non-fast forward)
error: failed to push some refs to ‘elecinf381@hg.comelec.enst.fr:2011/Project.git’</pre>
Ce push est spécial, il transfère les nouveaux
commits <code>3a4</code> et <code>83d</code> sur le serveur puis
tente de déplacer le pointeur b1 sur du commit <code>cac</code>
vers le commit <code>83d</code>. Mais quid du
commit <code>cac</code> ? Il devient pendant
ou <em>dangling</em> puisque référencé par aucune branche. Cette
opération implique donc une perte d'historique. Dans la
terminologie git on appelle cela un push <em>non
fast-forward</em>. Il est possible de forcer git à faire des
push non fast-forward en utilisant <code>git push</code> avec
l'option <code>--force</code> ou <code>-f</code> pour spécifier
que l'on est <strong>conscient de</strong> et <strong>d'accord
avec</strong> une éventuelle perte d'historique. L'exécution de
la commande suivante :
<pre>git push -f origin b1</pre>
nous amène donc dans l'état illustré par la figure 6. L'ancienne
branche <code>b1</code> est perdue et <code>origin/b1</code>
pointe désormais sur <code>83d</code>.
</p>
<div class="image">
<img class="gitimg" src="img/6.png" alt="Synchronisation forcée (non fast-forward) avec du serveur" />
<div class="caption">Figure 6 : Synchronisation forcée (non fast-forward) avec du serveur</div>
</div>
<p>
Les push non fast-foward sont donc nécessaires mais doivent être
utilises avec certaines précautions. La première d'entre elles
est que pour les raisons citées plus haut <strong>personne ne
doit jamais faire un push non fast-forward sur la
branche <code>master</code></strong>. On ne détruit pas
l'historique sur lequel se sont appuyé les autres. Si un commit
de <code>master</code> est erroné on utilise <code>git revert
<commit></code> qui introduit au sommet le commit
dual. Plus généralement on ne fait pas de push non fast-forward
sur des branches où l'on travaille à plusieurs, ou on s'assure
que les autres sont conscients que l'on va le faire.
</p>
<p>
Enfin on veut intégrer les changements de Bob
dans <code>master</code>. Pour cela plusieurs solutions. Nous
pouvons le faire en local et pusher sur le serveur :
<pre>git checkout master
git merge b1
git push origin master</pre>
Ici, la branche <code>b1</code> étant au sommet de master aucun
commit n'est introduit par le merge et <code>master</code> est
simplement déplacée au sommet de <code>b1</code>. Cela s'appelle
un <em>merge fast-forward</em>. On déplace ensuite la
branche <code>b1</code> du serveur (ainsi
qu'<code>origin/b1</code>) au sommet de b1 à l'aide du <code>git
push origin master</code>.
</p>
<p>
On peut également faire évoluer la branche <code>master</code>
distante dans un premier temps puis récupérer les changements :
<pre>git push origin b1:master
git pull origin master</pre>
La première commande synchronise les commits avec le serveur
puis déplace la branche <code>master</code> distante au niveau
de la branche <code>b1</code> locale. La figure 7 présente
l'état des dépôts local et distant après cette opération. On
exécute ensuite la commande <code>git pull origin master</code>
pour mettre à jour la branche <code>master</code> locale avec
la distante. La figure 8 présente l'état des dépôts après
l'exécution de cette commande. Dans cette figure on a en plus
supprimé les branches <code>b1</code> locale et distante à
l'aide des commandes :
<pre>git branch -d b1
git push origin :b1</pre>
</p>
<p>
Dans les deux cas le push est fast-forward (pas besoin
d'utiliser l'option <code>-f</code>) puisque nous avons rebasé
notre branche sur <code>master</code>. On fait donc évoluer la
branche <code>master</code> de deux commits sans perdre
d'historique.
</p>
<div class="image">
<img class="gitimg" src="img/7.png" alt="Intégration des changements de b1 dans master sur le serveur" />
<div class="caption">Figure 7 : Intégration des changements
de <code>b1</code> dans <code>master</code> sur le serveur</div>
</div>
<div class="image">
<img class="gitimg" src="img/8.png" alt="Suppression des branches et synchronisation avec la branche master distante" />
<div class="caption">Figure 8 : Suppression des branches et
synchronisation avec la branche <code>master</code>
distante</div>
</div>
<p>
Notre branche <code>b2</code> ayant été testée nous souhaitons
maintenant intégrer les changements
dans <code>master</code>. Problème : elle n'est plus au sommet
de <code>master</code>. De la même manière que précédemment nous
allons donc utiliser un rebase :
<pre>git rebase master b2
git push -f origin b2</pre>
Les commits de la branche <code>b2</code> sont réappliqués au
sommet de <code>master</code>. Et branche <code>b2</code>
distante est mise à jour à l'aide d'un push non fast-forward.
</p>
<div class="image">
<img class="gitimg" src="img/9.png" alt="Mise à jour de b2 sur le dépôt distant" />
<div class="caption">Figure 9 : Mise à jour de <code>b2</code> sur le dépôt distant</div>
</div>
<p>
Enfin on veut intégrer les changements de la
branche <code>b2</code> dans <code>master</code>. Pour cela on exécute les commandes :
<pre>git push origin b2:master
git branch -D b2
git push origin :b2
git pull origin master</pre>
La figure 10 montre une branche <code>master</code>,
synchronisée avec le dépôt distant, qui contient tous les