forked from gitbuch/gitbuch_cc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
advanced.txt
1684 lines (1302 loc) · 61.6 KB
/
advanced.txt
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
[[ch.advanced]]
== Fortgeschrittene Konzepte ==
Das folgende Kapitel behandelt ausgewählte fortgeschrittene Konzepte.
Im Vordergrund steht das Rebase-Kommando mit seinen vielfältigen
Anwendungen. Wir finden heraus, wer wann eine Zeile im Quellcode
verändert hat ('Blame') und wie Sie Git anweisen, Dateien und
Verzeichnisse zu ignorieren. Außerdem wird darauf eingegangen, wie
Sie Änderungen am Working Tree in den Hintergrund stellen
('Stash') und Commits annotieren ('Notes'). Zuletzt zeigen
wir Ihnen, wie Sie schnell und automatisiert Commits finden, die einen
Bug einführen ('Bisect').
[[sec.rebase]]
=== Commits verschieben – Rebase ===
Im Abschnitt über die Interna von Git wurde bereits erwähnt, dass man
Commits in einem Git-Repository (anschaulich: dem Graphen) beliebig
verschieben und modifizieren kann. Möglich wird das in der Praxis vor
allem durch das Git-Kommando `rebase`. Das Kommando ist sehr
mächtig und wichtig, aber zum Teil auch etwas anspruchsvoller in der
Anwendung.
'Rebase' ist ein Kunstwort, was soviel bedeutet wie ``etwas
auf eine neue Basis stellen''. Gemeint ist damit, dass eine Gruppe
von Commits innerhalb des Commit-Graphen verschoben, also Commit für
Commit auf Basis eines anderen Knotens aufgebaut wird. Die
nachfolgenden Grafiken veranschaulichen die Funktionsweise:
.Vor dem Rebase
image::bilder_ebook/rebase-vorher.png[id="fig.rebase-vorher-dia",width="90%"]
.{empty}...und danach
image::bilder_ebook/rebase-nachher.png[id="fig.rebase-nachher-dia",width="90%"]
In der einfachsten Form lautet das Kommando `git rebase
<referenz>` (im o.g. Diagramm: `git rebase master`). Damit
markiert Git zunächst alle Commits `<referenz>..HEAD`, also die
Commits, die von `HEAD` aus erreichbar sind (dem aktuellen
Branch) abzüglich der Commits, die von `<referenz>` aus
erreichbar sind – anschaulich gesprochen also alles, was im aktuellen
Branch, aber nicht in `<referenz>` liegt. Im Diagramm sind das
also E und F.
Die Liste dieser Commits wird zwischengespeichert. Anschließend checkt
Git den Commit `<referenz>` aus und kopiert die einzelnen,
zwischengespeicherten Commits in der ursprünglichen Reihenfolge als
neue Commits in den Branch.
Hierbei sind einige Punkte zu beachten:
* Weil der erste Knoten des +topic+-Branches (E) nun einen neuen Vorgänger (D) hat, ändern sich seine Metadaten und somit seine SHA-1-Summe (er wird zu E'). Der zweite Commit (F) hat dann ebenfalls einen anderen Vorgänger (E' statt E), dessen SHA-1-Summe ändert sich (er wird zu F') usw. – dies wird auch als 'Ripple Effect' bezeichnet. Insgesamt werden 'alle' kopierten Commits neue SHA-1-Summen haben – sie sind also im Zweifel gleich (was die Änderungen betrifft), aber nicht identisch.
* Bei einer solchen Aktion können, genau wie bei
einem Merge-Vorgang, konfliktbehaftete Änderungen auftreten. Git
kann diese teilweise automatisch lösen, bricht aber mit einer
entsprechenden Fehlermeldung ab, wenn die Konflikte nicht trivial
sind. Der Rebase-Prozess kann dann entweder ``repariert''
und weitergeführt oder abgebrochen werden (s.u.).
* Sofern keine weitere Referenz auf den Knoten F zeigt, geht
dieser verloren, weil die Referenz +HEAD+ (und gegebenenfalls
der entsprechende Branch) bei einem erfolgreichen Rebase auf den
Knoten F' 'verschoben' wird. Wenn also F keine Referenz mehr
hat (und auch keine Vorgänger, die F referenzieren), kann Git den
Knoten nicht mehr finden, und der Baum ``verschwindet''.
Wenn Sie sich nicht sicher sind, ob Sie den Original-Baum noch
einmal benötigen, können Sie zum Beispiel mit dem
`tag`-Kommando einfach eine Referenz darauf setzen. Dann
bleiben die Commits auch nach einem Rebase erhalten (dann aber in
doppelter Form an verschiedenen Stellen im Commit-Graphen).
[[sec.rebase-bsp]]
==== Ein Beispiel ====
Betrachten Sie folgende Situation: Der Branch `sqlite-support`
zweigt vom Commit ``fixed a bug...'' ab. Der
`master`-Branch ist aber schon weitergerückt, und ein neues
Release 1.4.2 ist erschienen.
.Vor dem Rebase
image::bilder_ebook/screenshot-rebase-vorher.png[id="fig.screenshot-rebase-vorher",width="90%"]
Nun wird `sqlite-support` ausgecheckt und neu auf
`master` aufgebaut:
[subs="macros,quotes"]
--------
$ *git checkout sqlite-support*
$ *git rebase master*
First, rewinding head to replay your work on top of it...
Applying: include sqlite header files, prototypes
Applying: generalize queries
Applying: modify Makefile to support sqlite
--------
Rebase wendet die drei Änderungen, die durch Commits aus dem Branch
`sqlite-support` eingeführt werden, auf den
`master`-Branch an. Danach sieht das Repository in Gitk wie
folgt aus:
.Nach Rebase
image::bilder_ebook/screenshot-rebase-nachher.png[id="fig.screenshot-rebase-nachher",width="90%"]
[[sec.rebase-extended]]
==== Erweiterte Syntax und Konflikte ====
Normalerweise wird `git rebase` immer den Branch, auf dem Sie
gerade arbeiten, auf einen neuen aufbauen. Allerdings gibt es eine
Abkürzung: Wollen Sie `topic` auf `master` aufbauen,
befinden sich aber auf einem ganz anderen Branch, können Sie das per
[subs="macros,quotes"]
--------
$ *git rebase master topic*
--------
Git macht intern Folgendes:
[subs="macros,quotes"]
--------
$ *git checkout topic*
$ *git rebase master*
--------
Beachten Sie die (leider wenig intuitive) Reihenfolge:
--------
git rebase <worauf> <was>
--------
Bei einem Rebase kann es zu Konflikten kommen. Der Prozess hält dann
mit folgender Fehlermeldung an:
[subs="macros,quotes"]
--------
$ *git rebase master*
...
CONFLICT (content): Merge conflict in <datei>
Failed to merge in the changes.
Patch failed at ...
The copy of the patch that failed is found in:
.../.git/rebase-apply/patch
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase
--abort".
--------
Sie gehen vor wie bei einem regulären Merge-Konflikt (siehe
<<sec.merge-conflicts>>) – `git mergetool` ist hier
sehr hilfreich. Fügen Sie dann einfach die geänderte Datei per
`git add` hinzu und lassen Sie den Prozess per `git
rebase --continue` weiterlaufen.footnote:[Wenn Sie Patch-Stacks mit Git verwalten, bei denen
potentiell Konflikte auftreten können, sollten Sie sich in jedem
Fall das Feature 'Reuse Recorded Resolution' ansehen, kurz
'rerere'. 'Rerere' speichert Konfliktlösungen und
korrigiert Konflikte automatisch, wenn schon eine Lösung gespeichert
wurde, siehe auch <<sec.rerere>>.]
Alternativ lässt sich der problematische Commit auch überspringen, und
zwar mit dem Kommando `git rebase --skip`. Der Commit ist
dann aber verloren, sofern er nicht in einem anderen Branch irgendwo
referenziert wird! Sie sollten diese Aktion also nur ausführen, wenn
Sie sicher wissen, dass der Commit obsolet ist.
Wenn das alles nicht weiterhilft (Sie z.B. den Konflikt nicht an
der Stelle lösen können oder gemerkt haben, dass Sie gerade den
falschen Baum umbauen), ziehen Sie die Notbremse: `git rebase
--abort`. Dies verwirft alle Änderungen am Repository (auch schon
erfolgreich kopierte Commits), so dass der Zustand danach genau so
ist, wie zu dem Zeitpunkt, als der Rebase-Prozess gestartet wurde. Das
Kommando hilft auch, wenn Sie irgendwann vergessen haben, einen
Rebase-Prozess zu Ende zu führen, und sich andere Kommandos
beschweren, dass sie ihre Arbeit nicht verrichten können, weil gerade
ein Rebase im Gang ist.
[[sec.rebase-sinnvoll]]
==== Warum Rebase sinnvoll ist ====
Rebase ist vor allem sinnvoll, um die Commit-Geschichte eines Projekts
einfach und leicht verständlich zu halten. Beispielsweise arbeitet ein
Entwickler an einem Feature, hat dann aber ein paar Wochen lang etwas
anderes zu tun. Währenddessen ist die Entwicklung im Projekt aber
schon weiter vorangeschritten, es gab ein neues Release etc. Erst
jetzt kommt der Entwickler dazu, ein Feature zu beenden. (Auch wenn
Sie Patches per E-Mail verschicken wollen, hilft Rebase, Konflikte zu
vermeiden, siehe dazu <<sec.patch-queue>>.)
Für die Versionsgeschichte ist es nun viel logischer, wenn sein
Feature nicht über einen langen Zeitraum unfertig neben der
eigentlichen Entwicklung ``mitgeschleppt'' wurde, sondern wenn
die Entwicklung vom letzten stabilen Release abzweigt.
Für genau diese Änderung in der Geschichte ist Rebase gut: Der
Entwickler kann nun einfach auf seinem Branch, auf dem er das Feature
entwickelt hat, das Kommando `git rebase v1.4.2` eingeben, um
seinen Feature-Branch neu auf dem Commit mit dem Release-Tag
`v1.4.2` aufzubauen. So lässt sich wesentlich leichter
ablesen, welche Unterschiede das Feature wirklich in die Software
einbringt.
Auch passiert es jedem Entwickler im Eifer des Gefechts, dass Commits
im falschen Branch landen. Da findet sich zufällig ein Fehler, der
schnell durch einen entsprechenden Commit behoben wird; aber dann muss
direkt noch ein Test geschrieben werden, um diesen Fehler in Zukunft
zu vermeiden (ein weiterer Commit), was wiederum in der Dokumentation
entsprechend zu vermerken ist. Nachdem die eigentliche Arbeit getan
ist, kann man diese Commits mit Rebase an eine andere Stelle im
Commit-Graphen ``transplantieren''.
Rebase kann auch dann sinnvoll sein, wenn in einem Branch ein Feature
benötigt wird, das erst kürzlich in die Software eingeflossen ist. Ein
'Merge' des `master`-Branches ist semantisch nicht
sinnvoll, da dann diese und andere Änderungen untrennbar mit dem
Feature-Branch verschmolzen werden. Stattdessen baut man den Branch
per Rebase auf einen neuen Commit auf, in dem das benötigte Feature
schon enthalten ist, und kann dieses dann in der weiteren Entwicklung
nutzen.
[[sec.rebase-vs-merge]]
==== Wann Rebase 'nicht' sinnvoll ist – Rebase vs. Merge ====
Das Konzept von Rebase ist zunächst etwas schwierig zu verstehen. Aber
sobald Sie verstanden haben, was damit möglich ist, stellt sich die
Frage: Wozu braucht man überhaupt noch ein simples Merge, wenn man
doch alles mit Rebase bearbeiten kann?
Wenn Git-Rebase nicht oder kaum angewendet wird, entwickelt sich
häufig eine Projektgeschichte, die relativ unüberschaubar wird, weil
ständig und jeweils für wenige Commits Merges ausgeführt werden
müssen.
Wird Rebase dagegen zu viel angewendet, besteht die Gefahr, dass das
gesamte Projekt sinnlos linearisiert wird: Das flexible Branching von
Git wird zwar zur Entwicklung genutzt, die Branches werden aber dann
reißverschlussartig per Rebase hintereinander(!) in den
Veröffentlichungsbranch integriert. Das stellt uns vor allem vor zwei
Probleme:
* Logisch zusammengehörige Commits sind nicht mehr als solche
zu erkennen. Da alle Commits linear sind, vermischt sich die
Entwicklung mehrerer Features untrennbar.
* Die Integration eines Branches kann nicht mehr ohne
weiteres rückgängig gemacht werden, denn diejenigen Commits zu
identifizieren, die einmal zu einem Feature-Branch gehörten, ist nur
manuell möglich.
So verspielen Sie die Vorteile des flexiblen Branchings von Git. Die
Schlussfolgerung ist, dass Rebase weder zu viel noch zu wenig
angewendet werden sollte. Beides macht die Projektgeschichte (auf
unterschiedliche Art und Weise) unübersichtlich.
Generell fahren Sie mit den folgenden Faustregeln gut:
. Ein Feature wird, wenn es fertig wird, per 'Merge'
integriert. Sinnvollerweise sollte vermieden werden, einen
'Fast-Forward-Merge' zu erzeugen, damit der Merge-Commit als
Zeitpunkt der Integration erhalten bleibt.
. Während Sie entwickeln, sollten Sie häufig Rebase benutzen
(besonders interaktives Rebase, s.u.).
. Logisch getrennte Einheiten sollten auf getrennten Branches
entwickelt werden – logisch zusammengehörige eventuell auf mehreren,
die dann per Rebase verschmolzen werden (wenn das sinnvoll ist).
Die Zusammenführung logisch getrennter Einheiten erfolgt dann per
Merge.
[[sec.rebase-warnung]]
==== Ein Wort der Warnung ====
Wie schon angesprochen, ändern sich bei einem Rebase zwangsläufig die
SHA-1-Summen aller Commits, die ``umgebaut'' werden. Wenn diese
Änderungen noch nicht veröffentlicht wurden, d.h. bei einem
Entwickler im privaten Repository liegen, ist das auch nicht schlimm.
Wenn aber ein Branch (z.B.{empty}{nbsp}`master`) veröffentlicht{empty}footnote:[Indem zum Beispiel der
Branch in ein öffentlich verfügbares Repository hochgeladen wird,
siehe <<sec.hochladen>>.]
und später per Rebase umgeschrieben wird, hat das unschöne Folgen
für alle Beteiligten: Alle Branches, die auf `master` aufbauen,
referenzieren nun die alte Kopie des mittlerweile umgeschriebenen
`master`-Branches. Also muss jeder Branch wiederum per Rebase
auf den neuen `master` aufgebaut werden (wodurch sich wiederum
alle Commit-IDs ändern). Dieser Effekt setzt sich fort und kann (je
nachdem, wann so ein Rebase passiert und wie viele Entwickler an dem
Projekt beteiligt sind) sehr zeitaufwendig zu beheben sein (das trifft
vor allem dann zu, wenn Git-Neulinge dabei sind).
Daher sollten Sie immer an folgende Regel denken:
[WARNING]
==================
Bearbeiten Sie mit dem Rebase-Kommando nur unveröffentlichte Commits!
==================
Ausnahmen bilden Konventionen wie persönliche Branches oder
`pu`. Letzterer ist ein Kürzel für 'Proposed Updates' und
ist in der Regel ein Branch, in dem neue, experimentelle Features auf
Kompatibilität getestet werden. Auf diesen Branch baut sinnvollerweise
niemand seine eigene Arbeit auf, daher kann er ohne Probleme und
vorherige Ankündigung umgeschrieben werden.
Eine weitere Möglichkeit bieten private Branches, also solche, die zum
Beispiel mit `<user>/` starten. Trifft man die
Vereinbarung, dass Entwickler auf diesen Branches eigene Entwicklung
betreiben, aber ihre Features immer nur auf ``offiziellen''
Branches aufbauen, dann dürfen die Entwickler ihre Branches beliebig
umschreiben.
[[sec.rebase-cp]]
==== Code-Dopplungen vermeiden ====
Wird über einen langen Zeitraum an einem Feature entwickelt, und Teile
des Features fließen schon in ein Mainstream-Release (z.B. per
`cherry-pick`), dann erkennt das Rebase-Kommando diese Commits
und lässt sie beim Kopieren bzw. Neuaufbauen der Commits aus, da die
Änderung schon in dem Branch enthalten ist.
So besteht der neue Branch nach einem Rebase nur aus den Commits, die
noch nicht in den Basis-Branch eingeflossen sind. Auf diese Weise
treten Commits nicht doppelt in der Versionsgeschichte eines Projekts
auf. Wäre der Branch einfach nur per Merge integriert worden, so wären
mitunter die gleichen Commits mit unterschiedlichen SHA-1-Summen an
verschiedenen Stellen im Commit-Graphen vorhanden.
[[sec.rebase-ps]]
==== Patch-Stacks verwalten ====
Es gibt Situationen, in denen es von einer Software eine
Vanilla-Version (``einfachste Version'') gibt und außerdem
eine gewisse Anzahl von Patches, die darauf angewendet werden, bevor
die Vanilla-Version ausgeliefert wird. Zum Beispiel baut Ihre Firma
eine Software, aber vor jeder Auslieferung an den Kunden müssen (je
nach Kunde) einige Anpassungen vorgenommen werden. Oder Sie haben eine
Open-Source-Software im Einsatz, diese aber ein wenig an Ihre
Bedürfnisse angepasst – jedes Mal, wenn nun eine neue, offizielle
Version der Software erscheint, müssen Sie Ihre Änderungen neu
anwenden und die Software anschließend neu bauen.footnote:[Im letzteren Fall machen Sie z.B. einfach ein
`git remote update` (die neuen Commits werden in den Branch
`origin/master` geladen) und bauen anschließend Ihren eigenen
Branch von neuem auf `origin/master` auf. Siehe auch <<sec.verteilte_systeme>>.]
Um Patch-Stacks zu verwalten, gibt es einige Programme, die auf Git
aufbauen, Ihnen aber den Komfort bieten, nicht direkt mit dem
Rebase-Kommando arbeiten zu müssen. Beispielsweise erlaubt
'TopGit'{empty}footnote:[Den Quellcode finden Sie unter
http://repo.or.cz/w/topgit.git.]
Ihnen, Abhängigkeiten zwischen Branches zu definieren – wenn sich
dann in einem Branch etwas ändert und andere Branches
hängen davon ab, baut TopGit diese auf Wunsch neu auf. Eine
Alternative zu TopGit ist 'Stacked Git'{empty}footnote:[Kurz `stg` oder StGit, erreichbar unter
http://www.procode.org/stgit/.].
[[sec.rebase-onto]]
==== Rebase einschränken mit --onto ====
Sie mögen sich nun gewundert haben: `git rebase <referenz>`
kopiert immer 'alle' Commits, die zwischen `<referenz>`
und `HEAD` liegen. Was aber, wenn Sie nur einen Teil eines
Branches umsetzen, quasi ``transplantieren'' möchten?
Betrachten Sie folgende Situation:
.Vor dem `rebase --onto`
image::bilder_ebook/rebase-onto-vorher.png[id="fig.rebase-onto-vorher-dia",width="90%"]
Sie haben gerade auf dem Branch `topic` ein Feature entwickelt,
als Ihnen ein Fehler aufgefallen ist; Sie haben einen Branch
`bugfix` erstellt und noch einen Fehler gefunden. Rein
semantisch hat aber Ihr Branch `bugfix` nichts mit dem
`topic`-Branch zu tun. Sinnvollerweise sollte er daher vom
`master`-Branch abzweigen.
Wenn Sie nun aber per `git rebase master` den Branch
`bugfix` neu aufbauen, passiert Folgendes: Alle Knoten, die in
`bugfix` enthalten sind, aber nicht im `master`, werden
der Reihe nach auf den `master`-Branch kopiert – das sind also
die Knoten D, E, F und G. Dabei gehören jedoch D und E gar nicht zum
Bugfix.
Hier kommt nun die Option `--onto` ins Spiel: Sie erlaubt, einen
Start- und Endpunkt für die Liste der zu kopierenden Commits
anzugeben. Die allgemeine Syntax lautet:
--------
git rebase --onto <worauf> <start> <ziel>
--------
In diesem Beispiel wollen wir nur die Commits F und G (oder auch: die
Commits von `topic` bis `bugfix`) von oben auf
`master` aufbauen. Daher lautet das Kommando:
[subs="macros,quotes"]
--------
$ *git rebase --onto master topic bugfix*
--------
Das Ergebnis sieht aus wie erwartet:
.Nach einem `rebase --onto`
image::bilder_ebook/rebase-onto-nachher.png[id="fig.rebase-onto-nachher-dia",width="90%"]
[[sec.rebase-onto-ci-amend]]
==== Einen Commit verbessern ====
Sie haben in <<sec.grundlagen>> das Kommando `commit
--amend` kennengelernt, mit dem Sie einen Commit verbessern. Es
bezieht sich aber immer nur auf den aktuellen (letzten) Commit. Mit
`rebase --onto` können Sie auch Commits anpassen, die weiter
in der Vergangenheit liegen.
Suchen Sie zunächst den Commit heraus, den Sie editieren wollen, und
erstellen Sie einen Branch darauf:
[subs="macros,quotes"]
--------
$ *git checkout -b fix-master 21d8691*
--------
Anschließend führen Sie Ihre Änderungen aus, fügen geänderte Dateien
mit `git add` hinzu und korrigieren dann den Commit mit
`git commit --amend --no-edit` (die Option `--no-edit`
übernimmt Meta-Informationen wie die Beschreibung des
alten Commits und bietet diese nicht erneut zum Editieren an).
Nun spielen Sie alle Commits aus dem `master`-Branch von oben
auf Ihren korrigierten Commit auf:
[subs="macros,quotes"]
--------
$ *git rebase --onto fix-master 21d8691 master*
--------
Sie kopieren so alle Commits von `21d8691` (exklusive!) bis
`master` (inklusive!). Der fehlerhafte Commit `21d8691`
wird nicht mehr referenziert und taucht daher nicht mehr auf. Der
Branch `fix-master` ist nun obsolet und kann gelöscht werden.
Eine äquivalente Möglichkeit, einen Commit zu editieren, haben Sie
mit der Aktion `edit` im interaktiven Rebase (siehe <<sec.rebase-i-edit>>).
[[sec.rebase-optionen]]
==== Rebase feinjustieren ====
Es gibt Situationen, in denen Sie das Standardverhalten von
`git rebase` anpassen müssen. Erstens ist dies der Fall, wenn
Sie einen Branch mit Rebase bearbeiten, der Merges enthält. Rebase
kann versuchen, diese nachzuahmen statt die Commits zu linearisieren.
Zuständig ist die Option `-p` bzw.
`--preserve-merges`.footnote:[Das funktioniert auch problemlos,
sofern alle Abzweigungen und Zusammenführungen 'oberhalb' der
neuen Referenz sind (also nur Commits enthalten sind, von denen aus
man die neue Basis erreichen kann). Sonst schlägt Rebase bei jedem
Commit fehl, der schon in der Geschichte enthalten ist
(Fehlermeldung: ``nothing to commit''); diese müssen dann stets mit einem `git rebase --continue` übersprungen werden.]
Mit der Option `-m` bzw. `--merge` können Sie
`git rebase` anweisen, Merge-Strategien zu verwenden (siehe
dafür auch <<sec.merge-strategies>>). Wenn Sie diese Strategien
anwenden, bedenken Sie, dass Rebase intern Commit für Commit per
`cherry-pick` auf den neuen Branch aufspielt; daher sind die
Rollen von `ours` und `theirs` vertauscht: `theirs`
bezeichnet den Branch, den Sie auf eine neue Basis aufbauen!
Ein interessanter Anwendungsfall ist daher die Strategie-Option
`theirs` für die Merge-Strategie `recursive`: Falls
Konflikte auftreten, wird den Änderungen aus dem Commit, der kopiert
wird, Vorrang gegeben. Ein solches Szenario ist also sinnvoll, wenn
Sie wissen, dass es konfliktverursachende Änderungen gibt, sich aber
sicher sind, dass die Änderungen des neu aufzubauenden Branches
``richtiger'' sind als die des Baumes, auf den Sie aufbauen.
Wenn Sie `topic` neu auf `master`
aufbauen, sähe ein solcher Aufruf so aus:
[subs="macros,quotes"]
--------
$ *git checkout topic*
$ *git rebase -m -Xtheirs master*
--------
In den Fällen, in denen die `recursive`-Strategie (Default) den
Änderungen aus Commits aus `topic` den Vorzug gibt, werden Sie
einen entsprechenden Hinweis `Auto-merging
<Commit-Beschreibung>` finden.
Eine kleine, sehr nützliche Option, die von Rebase direkt an
`git apply` weitergeleitet wird, ist
`--whitespace=fix`. Sie veranlasst Git, automatisch
Whitespace-Fehler (z.B. Trailing-Spaces) zu korrigieren.
Falls Sie Merge-Konflikte aufgrund von Whitespace haben (zum Beispiel
wegen geänderter Einrückung), können Sie auch die in <<sec.recursive-options>>
vorgestellten Strategie-Optionen verwenden, um automatisch Lösungen
erzeugen zu lassen (zum Beispiel durch Angabe von `-Xignore-space-change`).
[[sec.rebase-i]]
=== Die Geschichte umschreiben – Interaktives Rebase ===
Rebase kennt einen interaktiven Modus; er ist zwar technisch gleich
implementiert wie der normale Modus, allerdings ist der typische
Anwendungsfall ein ganz anderer, denn der interaktive Rebase erlaubt
es, die Geschichte umzuschreiben, d.h. Commits beliebig zu
bearbeiten (und nicht nur zu verschieben).
Im interaktiven Rebase können Sie
* die Reihenfolge von Commits verändern
* Commits löschen
* Commits miteinander verschmelzen
* einen Commit in mehrere aufteilen
* die Beschreibung von Commits anpassen
* Commits auf jede sonst erdenkliche Weise
bearbeiten
Sie aktivieren den Modus durch die Option `-i`
bzw. `--interactive`. Prinzipiell läuft dann der
Rebase-Prozess genau so wie vorher, allerdings erhalten Sie eine Liste
von Commits, die Rebase umschreiben wird, bevor das Kommando damit
anfängt. Das kann zum Beispiel so aussehen:
[subs="macros,quotes"]
--------
*pick e6ec2b6* Fix expected values of setup tests on Windows
*pick 95b104c* t/README: hint about using $(pwd) rather than $PWD in tests
*pick 91c031d* tests: cosmetic improvements to the repo-setup test
*pick 786dabe* tests: compress the setup tests
*pick 4868b2e* Subject: setup: officially support --work-tree without
--git-dir
--------
Unter dieser Auflistung finden Sie einen Hilfstext, der beschreibt,
was Sie nun mit den aufgelisteten Commits tun können. Im Wesentlichen
gibt es pro Commit sechs mögliche Aktionen. Die Aktion schreiben Sie
einfach statt der Standard-Aktion `pick` an den Anfang der
Zeile, vor die SHA-1-Summe. Im Folgenden die Aktionen – Sie können
diese auch jeweils durch ihren Anfangsbuchstaben abkürzen, also z.B.
`s` für `squash`.
`pick`:: ``Commit verwenden'' (Default). Entspricht der Behandlung
von Commits im nicht-interaktive Rebase.
`-`:: Löschen Sie eine Zeile, dann wird der Commit nicht verwendet
(geht verloren).
`reword`:: Commit-Beschreibung anpassen.
`squash`:: Commit mit dem vorherigen verschmelzen; Editor wird
geöffnet, um die Beschreibungen zusammenzuführen.
`fixup`:: Wie `squash`, wirft aber die Beschreibung des Commits weg.
`edit`:: Freies Editieren. Sie können beliebige Aktionen ausführen.
`exec`:: Der Rest der Zeile wird als Kommando auf der Shell ausgeführt.
Falls das Kommando sich nicht erfolgreich (das heißt mit Rückgabewert 0)
beendet, hält der Rebase an.
Die Aktion `pick` ist die simpelste – sie besagt einfach, dass
Sie den Commit verwenden wollen, Rebase soll diesen Commit so, wie er
ist, übernehmen. Das Gegenteil von `pick` ist das simple
Löschen einer kompletten Zeile. Der Commit geht dann verloren (wie
`git rebase --skip`).
Wenn Sie die Reihenfolge der Zeilen tauschen, dann wird Git die
Commits in der neu definierten Reihenfolge anwenden. Zu Anfang sind
die Zeilen in der Reihenfolge, in der sie später angewendet werden --
also genau anders herum als in der Baumansicht! Beachten Sie, dass
Commits häufig aufeinander aufbauen; daher wird es bei der
Vertauschung von Commits häufig zu Konflikten kommen, sofern die
Commits auf den gleichen Dateien und an den gleichen Stellen
Änderungen durchführen.
Das Kommando `reword` ist praktisch, wenn Sie Tippfehler in
einer Commit-Nachricht haben und diese korrigieren wollen (oder bisher
keine ausführliche verfasst haben und dies nun nachholen wollen). Der
Rebase-Prozess wird bei dem mit `reword` markierten Prozess
angehalten, und Git startet einen Editor, in dem die Nachricht des
Commits bereits angezeigt wird. Sobald Sie den Editor beenden
(Speichern nicht vergessen!), wird Git die neue Beschreibung
einpflegen und den Rebase-Prozess weiterlaufen lassen.
[[sec.rebase-i-squash]]
==== Kleine Fehler korrigieren: Bug Squashing ====
Die Kommandos `squash` bzw. `fixup` erlauben es, zwei
oder mehr Commits miteinander zu verschmelzen.
Niemand schreibt immer sofort fehlerfreien Code. Häufig gibt es einen
großen Commit, in dem Sie ein neues Feature implementiert haben; kurz
darauf finden sich kleine Fehler. Was tun? Eine ausführliche
Beschreibung, warum Sie aus Unachtsamkeit vergessen haben, eine Zeile
hinzuzufügen oder zu entfernen? Nicht wirklich sinnvoll, und vor
allem störend für andere Entwickler, die später Ihren Code überprüfen
wollen. Schöner wäre es doch, die Illusion zu wahren, dass der Commit
gleich beim ersten Mal fehlerfrei war...
Für jeden Fehler, den Sie finden, machen Sie einen kleinen Commit mit
einer mehr oder weniger sinnvollen Beschreibung. Das könnte dann zum
Beispiel so aussehen:
[subs="macros,quotes"]
--------
$ *git log --oneline master..feature*
b5ffeb7 fix feature 1
34c4453 fix feature 2
ac445c6 fix feature 1
ae65efd implement feature 2
cf30f4d implement feature 1
--------
Wenn sich einige solche Commits angesammelt haben, starten Sie einen
interaktiven Rebase-Prozess über die letzten Commits. Schätzen Sie
dazu einfach ab, auf wie vielen Commits Sie arbeiten wollen, und
bearbeiten Sie dann beispielsweise per `git rebase -i HEAD~5`
die letzten fünf.
Im Editor erscheinen die Commits nun in umgekehrter Reihenfolge im
Vergleich zur Ausgabe von `git log`. Ordnen Sie nun die kleinen
Bugfix-Commits so an, dass sie 'unter' dem Commit, den sie
korrigieren, stehen. Markieren Sie dann die Korrektur-Commits mit
`squash` (oder `s`), also z.B. so:
[subs="macros,quotes"]
--------
pick cf30f4d implement feature 1
*s* ac445c6 fix feature 1
*s* b5ffeb7 fix feature 1
pick ae65efd implement feature 2
*s* 34c4453 fix feature 2
--------
Speichern Sie die Datei und beenden Sie den Editor; der Rebase-Prozess
startet. Weil Sie `squash` ausgewählt haben, hält Rebase an,
nachdem Commits verschmolzen wurden. Im Editor erscheinen die
Commit-Nachrichten der verschmolzenen Commits, die Sie nun geeignet
zusammenfassen. Verwenden Sie statt `squash` das Schlüsselwort
`fixup` oder kurz `f`, wird die Commit-Nachricht der so
markierten Commits weggeworfen – für diese Arbeitsweise also
vermutlich praktischer.
Nach dem Rebase sieht die Versionsgeschichte viel aufgeräumter aus:
[subs="macros,quotes"]
--------
$ *git log --oneline master..feature*
97fe253 implement feature 2
6329a8a implement feature 1
--------
[TIP]
========
Oft kommt es vor, dass man eine kleine Änderung noch in den zuletzt
getätigten Commit ``schleusen'' möchte. Hier bietet sich
folgendes Alias an, das an die `fixup`-Aktion angelehnt ist:
[subs="macros,quotes"]
--------
$ *git config --global alias.fixup "commit --amend --no-edit"*
--------
Wie oben schon erwähnt, übernimmt die Option `--no-edit` eins zu
eins die Meta-Informationen des alten Commits, insbesondere die
Commit-Message.
========
Wenn Sie die Commit-Nachricht mit `fixup!` bzw.
`squash!` beginnen, gefolgt vom Anfang der Beschreibung des
Commits, den Sie korrigieren wollen, können Sie das Kommando
[subs="macros,quotes"]
--------
$ *git rebase -i --autosquash master*
--------
aufrufen. Die wie oben mit `fixup!` bzw. `squash!`
markierten Commits werden automatisch an die richtige Stelle
verschoben und mit der Aktion `squash` bzw. `fixup`
versehen. So können Sie den Editor direkt beenden, und die Commits
werden verschmolzen. Falls Sie häufig mit dieser Option arbeiten, können
Sie dieses Verhalten auch durch eine Konfigurationsoption zum Standard
bei Rebase-Aufrufen machen: Setzen Sie dafür die Einstellung
`rebase.autosquash` auf `true`.
[[sec.rebase-i-edit]]
==== Commits beliebig editieren ====
Wenn Sie einen Commit mit `edit` markieren, kann er beliebig
editiert werden. Dabei geht Rebase wie in den anderen Fällen auch
sequentiell die Commits durch. Bei den Commits, die mit `edit`
markiert sind, hält Rebase an und `HEAD` wird auf den
entsprechenden Commit gesetzt. Sie können dann den Commit ändern, als
wäre er der aktuellste im Branch. Anschließend lassen Sie Rebase
weiterlaufen:
[subs="macros,quotes"]
--------
$ *vim ...*
# Korrekturen vornehmen
$ *git add ...*
$ *git commit --amend*
$ *git rebase --continue*
--------
Im Wesentlichen erreichen Sie dabei dasselbe wie im Beispiel
`git rebase --onto` in <<sec.rebase-onto-ci-amend>>
-- allerdings können Sie auch weit kompliziertere Aktionen ausführen.
Einen häufigen Anwendungsfall beschreibt folgendes
``Rezept''.
[[sec.rebase-split-cmmits]]
===== Commits aufteilen =====
Jeder Programmierer kennt das: Diszipliniert und penibel jede Änderung
einzuchecken, ist anstrengend und unterbricht häufig den Arbeitsfluss.
Das führt in der Praxis zu Commits, die groß und unübersichtlich sind.
Damit die Versionsgeschichte aber für andere Entwickler – und Sie
selbst! – nachvollziehbar bleibt, sollten die Änderungen in so kleine
logische Einheiten wie möglich aufgeteilt werden.
Im Übrigen ist es nicht nur für Entwickler hilfreich, so vorzugehen.
Auch die automatisierte Fehlersuche mittels `git bisect`
funktioniert besser und akkurater, je kleiner und sinnvoller die
Commits sind (siehe <<sec.bisect>>).
Mit ein wenig Erfahrung können Sie einen Commit sehr schnell
aufteilen. Wenn Sie häufig große Commits produzieren, sollte Ihnen der
folgende Arbeitsschritt zur Routine werden.
Zunächst starten Sie den Rebase-Prozess und markieren den Commit, den
Sie aufteilen wollen, mit `edit`. Rebase hält dort an,
`HEAD` zeigt auf diesen Commit.
Anschließend setzen Sie den `HEAD` einen Commit zurück, ohne
allerdings die Änderungen von `HEAD` (der aufzuteilende Commit)
wegzuwerfen. Das passiert durch das Kommando `reset` (siehe
auch <<sec.reset>>; beachten Sie, dass, sofern Sie die
Commit-Beschreibung noch brauchen, Sie diese vorher kopieren sollten):
[subs="macros,quotes"]
--------
$ *git reset HEAD^*
--------
Die Änderungen, die der aufzuteilende Commit verursacht, sind nun noch
in den Dateien vorhanden; der Index und das Repository spiegeln aber
den Stand des Vorgänger-Commits wider. Sie haben also die Änderungen
des aufzuteilenden Commits in den 'unstaged'-Zustand verschoben
(das können Sie verifizieren, indem Sie `git diff` vor und nach
dem `reset`-Aufruf betrachten).
Nun können Sie einige Zeilen hinzufügen, einen Commit erstellen,
weitere Zeilen hinzufügen und schließlich einen dritten Commit für
die übrigen Zeilen erstellen:
[subs="macros,quotes"]
--------
$ *git add -p*
$ *git commit -m "Erster Teil"*
$ *git add -p*
$ *git commit -m "Zweiter Teil"*
$ *git add -u*
$ *git commit -m "Dritter (und letzter) Teil";*
--------
Was passiert? Durch das Reset-Kommando haben Sie den `HEAD`
einen Commit zurückgesetzt. Mit jedem Aufruf von `git commit`
erstellen Sie einen neuen Commit, aufbauend auf dem jeweiligen
`HEAD`. Statt des einen großen Commits (den Sie durch den
`reset`-Aufruf weggeworfen haben) haben Sie nun drei kleinere
Commits an dessen Stelle gesetzt.
Lassen Sie jetzt Rebase weiterlaufen (`git rebase
--continue`) und die übrigen Commits von oben auf `HEAD`
(der jetzt der neueste Ihrer drei Commits ist) aufbauen.
[[sec.blame]]
=== Wer hat diese Änderungen gemacht? – git blame ===
Wie andere Versionskontrollsysteme hat auch Git ein Kommando
`blame` bzw. `annotate`, das alle Zeilen einer Datei mit
Datum und Autor der letzten Änderung versieht. So können Sie z.B.
schnell herausfinden, wer der Verantwortliche für eine Zeile Code ist,
die ein Problem verursacht, oder seit wann das Problem besteht.
Dabei ist das Kommando `annotate` lediglich für Umsteiger
gedacht und hat die gleiche Funktionalität wie das Kommando
`blame`, nur ein etwas anderes Ausgabeformat. Sie sollten also
im Zweifel immer `blame` verwenden.
Nützliche Optionen sind `-M`, um Code-Verschiebungen, und
`-C`, um Code-Kopien anzuzeigen. Anhand des Dateinamens in der
Ausgabe können Sie dann erkennen, aus welcher Datei möglicherweise
Code kopiert oder verschoben wurde. Wird kein Dateiname angezeigt,
konnte Git keine Code-Bewegungen oder -Kopien finden. Wenn Sie diese
Optionen verwenden, ist es meist sinnvoll, per `-s` die Angabe
von Autor und Datum zu unterdrücken, damit die Anzeige noch ganz auf
den Bildschirm passt.
Aus der folgenden Ausgabe erkennt man z.B., dass die Funktion
`end_url_with_slash` ursprünglich aus der Datei
`http.c` stammte. Die Option `-L<m>,<n>` grenzt die
Ausgabe auf die entsprechenden Zeilen ein.
[subs="macros,quotes"]
--------
$ *git blame -C -s -L123,135 url.c*
638794cd url.c 123) char *url_decode_parameter_value(const char
**query)
638794cd url.c 124) {
ce83eda1 url.c 125) struct strbuf out = STRBUF_INIT;
730220de url.c 126) return url_decode_internal(query, "&", &out,
1);
638794cd url.c 127) }
d7e92806 http.c 128)
eb9d47cf http.c 129) void end_url_with_slash(struct strbuf *buf, const
char *url)
5ace994f http.c 130) {
5ace994f http.c 131) strbuf_addstr(buf, url);
5ace994f http.c 132) if (buf->len && buf->buf[buf->len - 1] != \'/')
5ace994f http.c 133) strbuf_addstr(buf, "/");
5ace994f http.c 134) }
3793a309 url.c 135)
--------
[[sec.blame-gui]]
==== Blame grafisch ====
Eine bequeme Alternative zu `git blame` auf der Konsole bietet
das grafische Tool `git gui blame` (hierfür müssen Sie
gegebenenfalls das Paket `git-gui` installieren).
.Ein Stück Code, das aus einer anderen Datei verschoben wurde
image::bilder_ebook/git-gui-blame.png[id="fig.git-gui-blame",width="100%"]
Wenn Sie eine Datei per `git gui blame <datei>` untersuchen,
werden die unterschiedlichen Blöcke, die aus verschiedenen Commits
stammen, mit Grautönen hinterlegt dargestellt. Links sehen Sie die
abgekürzte Commit-ID sowie die Initialen des Autors.
Erst wenn Sie mit der Maus über einen solchen Block fahren, erscheint
ein kleines Popup-Fenster mit Informationen zum Commit, der die Zeilen
geändert hat, möglicherweise auch mit einer Mitteilung, aus welcher
Datei und welchem Commit dieser Codeblock verschoben oder kopiert
wurde.
Bei der Code-Review interessiert man sich häufig dafür, wie eine Datei
eigentlich vor einer bestimmten Änderung aussah. Dafür bietet das
grafische Blame-Tool die folgende Möglichkeit, in der
Versionsgeschichte zurückzugehen: Klicken Sie mit der rechten
Maustaste auf die Commit-ID eines Code-Blocks und wählen Sie im
Kontextmenü 'Blame Parent Commit' aus – nun wird der
Vorgänger dieser Änderung angezeigt. Sie können auf diese Weise
mehrere Schritte zurückgehen. Über den grünen Pfeil links oben können
Sie wieder in die Zukunft springen.
[[sec.ignore]]
=== Dateien ignorieren ===
In fast jedem Projekt fallen Dateien an, die Sie nicht versionieren
wollen. Sei es der binäre Output des Compilers, die autogenerierte
Dokumentation im HTML-Format oder die Backup-Dateien, die Ihr Editor
erzeugt. Git bietet Ihnen verschiedene Ebenen, um Dateien zu
ignorieren:
* benutzerspezifische Einstellung
* repositoryspezifische Einstellung
* repositoryspezifische Einstellung, die mit eingecheckt wird
Welche Option Sie wählen, hängt ganz von Ihrem Anwendungsfall ab. Die
benutzerspezifischen Einstellungen sollten Dateien und Muster
enthalten, die für den Benutzer relevant sind, beispielsweise
Backup-Dateien, die Ihr Editor erzeugt. Solche Muster werden
üblicherweise in einer Datei im
`$HOME`-Verzeichnis abgelegt. Mit der Option
`core.excludesfile` geben Sie an, welche Datei dies sein soll,
z.B. im Fall von `~/.gitignore`:
[subs="macros,quotes"]
--------
$ *git config --global core.excludesfile ~/.gitignore*
--------
Bestimmte Dateien und Muster sind an ein Projekt gebunden und gelten
für jeden Teilnehmer, z.B. Compiler-Output und autogenerierte
HTML-Dokumentation. Diese Einstellung legen Sie in der Datei
`.gitignore` ab, die Sie ganz normal einchecken und somit an
alle Entwickler ausliefern.
Zuletzt lässt sich die Datei `.git/info/exclude` für
repositoryspezifische Einstellungen nutzen, die nicht mit einem
Klon ausgeliefert werden sollen, also Einstellungen, die gleichzeitig
projekt- und benutzerspezifisch sind.
[[sec.muster]]
==== Syntax für Muster ====
Die Syntax für Muster ist der Shell-Syntax nachempfunden:
* Leere Zeilen haben keinen Effekt und können zum Gliedern
und Trennen verwendet werden.
* Zeilen, die mit einem `#` anfangen, werden als
Kommentare gewertet und haben ebenfalls keinen Effekt.
* Ausdrücke, die mit `!` anfangen, werden als Negation
gewertet.
* Ausdrücke, die mit einem `/` enden, werden als
Verzeichnis gewertet. Der Ausdruck `man/` erfasst das
Verzeichnis `man`, nicht aber die gleichnamige Datei oder den
Symlink.
* Ausdrücke, die kein `/` enthalten, werden als
Shell-Glob für das aktuelle und alle Unterverzeichnisse gewertet.
Der Ausdruck `*.zip` in der obersten `.gitignore` etwa
erfasst alle Zip-Dateien in der Verzeichnisstruktur des Projekts.
* Der Ausdruck `**` umfasst Null oder mehr Dateien und Verzeichnisse.
Sowohl `t/data/set1/store.txt` als auch `t/README.txt` werden durch
das Muster `t/**/*.txt` erfasst.
* Sonst wird das Muster als Shell-Glob gewertet, genauer als
Shell-Glob, das von der Funktion `fnmatch(3)` mit dem Flag
`FNM_PATHNAME` ausgewertet wird. Das heißt, das Muster
`doc/*html` erfasst `doc/index.html`, nicht aber
`doc/api/singleton.html`.
* Ausdrücke, die mit einem `/` beginnen, sind an den
Pfad gebunden. Der Ausdruck `/*.sh` zum Beispiel erfasst
`upload.sh`, nicht aber `scripts/check-for-error.sh`.
Ein Beispiel:footnote:[Weitere Beispiele finden Sie auf der Man-Page zu `gitignore(5)` und unter http://help.github.com/git-ignore/.]
[subs="macros,quotes"]
--------
$ *cat ~/.gitignore*
# vim swap files
.*.sw[nop]
# python bytecode
*.pyc
# documents
*.dvi
*.pdf