-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathch08.html
2481 lines (2462 loc) · 122 KB
/
ch08.html
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
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Kapitel 8. Git automatisieren</title>
<link rel="stylesheet" type="text/css" href="gitbuch.css" />
<meta name="generator" content="DocBook XSL Stylesheets V1.78.1" />
<link rel="home" href="index.html" title="Git" />
<link rel="up" href="index.html" title="Git" />
<link rel="prev" href="ch07.html" title="Kapitel 7. Git auf dem Server" />
<link rel="next" href="ch09.html" title="Kapitel 9. Zusammenspiel mit anderen Versionsverwaltungssystemen" />
<meta xmlns="" name="language" content="de" />
<script xmlns="" src="http://hyphenator.googlecode.com/svn/trunk/Hyphenator.js" type="text/javascript"></script>
<script xmlns="" type="text/javascript">
Hyphenator.run();
</script>
</head>
<body class="hyphenate">
<div xmlns="" class="toc">
<p><a href="index.html">Startseite</a></p>
<dl class="toc">
<dt>
<span class="preface">
<a href="pr01.html">Vorwort</a>
</span>
</dt>
<dd>
<dl>
<dt>
<span class="section">
<a href="pr01.html#sec.leser">1. An wen richtet sich dieses Buch?</a>
</span>
</dt>
<dt>
<span class="section">
<a href="pr01.html#sec.struktur">2. Wie ist das Buch zu lesen?</a>
</span>
</dt>
<dt>
<span class="section">
<a href="pr01.html#sec.konventionen">3. Konventionen</a>
</span>
</dt>
<dt>
<span class="section">
<a href="pr01.html#sec.install-git-repo">4. Installation und „das Git-Repository“</a>
</span>
</dt>
<dt>
<span class="section">
<a href="pr01.html#sec.doku">5. Dokumentation und Hilfe</a>
</span>
</dt>
<dt>
<span class="section">
<a href="pr01.html#sec.kontakt">6. Downloads und Kontakt</a>
</span>
</dt>
<dt>
<span class="section">
<a href="pr01.html#sec.dank">7. Danksagungen</a>
</span>
</dt>
<dt>
<span class="section">
<a href="pr01.html#chap.vorwort-2te-auflage">8. Vorwort zur 2. Auflage</a>
</span>
</dt>
<dt>
<span class="section">
<a href="pr01.html#chap.vorwort-cc-ausgabe">9. Vorwort zur CreativeCommons-Ausgabe</a>
</span>
</dt>
</dl>
</dd>
<dt>
<span class="chapter">
<a href="ch01.html">1. Einführung und erste Schritte</a>
</span>
</dt>
<dd>
<dl>
<dt>
<span class="section">
<a href="ch01.html#sec.begriffe">1.1. Grundbegriffe</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch01.html#sec.erste-schritte">1.2. Erste Schritte mit Git</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch01.html#chap.git-config">1.3. Git konfigurieren</a>
</span>
</dt>
</dl>
</dd>
<dt>
<span class="chapter">
<a href="ch02.html">2. Grundlagen</a>
</span>
</dt>
<dd>
<dl>
<dt>
<span class="section">
<a href="ch02.html#sec.grundlagen">2.1. Git-Kommandos</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch02.html#sec.objektmodell">2.2. Das Objektmodell</a>
</span>
</dt>
</dl>
</dd>
<dt>
<span class="chapter">
<a href="ch03.html">3. Praktische Versionsverwaltung</a>
</span>
</dt>
<dd>
<dl>
<dt>
<span class="section">
<a href="ch03.html#sec.branches">3.1. Referenzen: Branches und Tags</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch03.html#sec.undo">3.2. Versionen wiederherstellen</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch03.html#sec.merge">3.3. Branches zusammenführen: Merges</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch03.html#sec.merge-conflicts">3.4. Merge-Konflikte lösen</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch03.html#sec.cherry-pick">3.5. Einzelne Commits übernehmen: Cherry-Pick</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch03.html#sec.visualization">3.6. Visualisierung von Repositories</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch03.html#sec.reflog">3.7. Reflog</a>
</span>
</dt>
</dl>
</dd>
<dt>
<span class="chapter">
<a href="ch04.html">4. Fortgeschrittene Konzepte</a>
</span>
</dt>
<dd>
<dl>
<dt>
<span class="section">
<a href="ch04.html#sec.rebase">4.1. Commits verschieben – Rebase</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch04.html#sec.rebase-i">4.2. Die Geschichte umschreiben – Interaktives Rebase</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch04.html#sec.blame">4.3. Wer hat diese Änderungen gemacht? – git blame</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch04.html#sec.ignore">4.4. Dateien ignorieren</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch04.html#sec.stash">4.5. Veränderungen auslagern – git stash</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch04.html#sec.notes">4.6. Commits annotieren – git notes</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch04.html#sec.multi-root">4.7. Mehrere Root-Commits</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch04.html#sec.bisect">4.8. Regressionen finden – git bisect</a>
</span>
</dt>
</dl>
</dd>
<dt>
<span class="chapter">
<a href="ch05.html">5. Verteiltes Git</a>
</span>
</dt>
<dd>
<dl>
<dt>
<span class="section">
<a href="ch05.html#sec.verteilte_systeme">5.1. Wie funktioniert verteilte Versionsverwaltung?</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch05.html#sec.clone">5.2. Repositories klonen</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch05.html#sec.git_fetch">5.3. Commits herunterladen</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch05.html#sec.hochladen">5.4. Commits hochladen: git push</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch05.html#sec.remotes-check">5.5. Remotes untersuchen</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch05.html#sec.multi-remote">5.6. Verteilter Workflow mit mehreren Remotes</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch05.html#sec.remotes-verwalten">5.7. Remotes verwalten</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch05.html#sec.remote-tags">5.8. Tags austauschen</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch05.html#sec.patch-queue">5.9. Patches per E-Mail</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch05.html#sec.dictator">5.10. Ein verteilter, hierarchischer Workflow</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch05.html#sec.subprojects">5.11. Unterprojekte verwalten</a>
</span>
</dt>
</dl>
</dd>
<dt>
<span class="chapter">
<a href="ch06.html">6. Workflows</a>
</span>
</dt>
<dd>
<dl>
<dt>
<span class="section">
<a href="ch06.html#sec.workflows-user">6.1. Anwender</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch06.html#sec.branch-modell">6.2. Ein Branching-Modell</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch06.html#sec.releases-management">6.3. Releases-Management</a>
</span>
</dt>
</dl>
</dd>
<dt>
<span class="chapter">
<a href="ch07.html">7. Git auf dem Server</a>
</span>
</dt>
<dd>
<dl>
<dt>
<span class="section">
<a href="ch07.html#sec.server">7.1. Einen Git-Server hosten</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch07.html#sec.gitolite">7.2. Gitolite: Git einfach hosten</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch07.html#sec.git-daemon">7.3. Git-Daemon: Anonymer, lesender Zugriff</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch07.html#sec.gitweb">7.4. Gitweb: Das integrierte Web-Frontend</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch07.html#sec.cgit">7.5. CGit – CGI for Git</a>
</span>
</dt>
</dl>
</dd>
<dt>
<span class="chapter">
<a href="ch08.html">8. Git automatisieren</a>
</span>
</dt>
<dd>
<dl>
<dt>
<span class="section">
<a href="ch08.html#sec.attributes">8.1. Git-Attribute – Dateien gesondert behandeln</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch08.html#sec.hooks">8.2. Hooks</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch08.html#sec.scripting">8.3. Eigene Git-Kommandos schreiben</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch08.html#sec.filter-branch">8.4. Versionsgeschichte umschreiben</a>
</span>
</dt>
</dl>
</dd>
<dt>
<span class="chapter">
<a href="ch09.html">9. Zusammenspiel mit anderen Versionsverwaltungssystemen</a>
</span>
</dt>
<dd>
<dl>
<dt>
<span class="section">
<a href="ch09.html#sec.subversion">9.1. Subversion</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch09.html#sec.fast-import">9.2. Eigene Importer</a>
</span>
</dt>
</dl>
</dd>
<dt>
<span class="chapter">
<a href="ch10.html">10. Shell-Integration</a>
</span>
</dt>
<dd>
<dl>
<dt>
<span class="section">
<a href="ch10.html#sec.bash-integration">10.1. Git und die Bash</a>
</span>
</dt>
<dt>
<span class="section">
<a href="ch10.html#sec.zsh-integration">10.2. Git und die Z-Shell</a>
</span>
</dt>
</dl>
</dd>
<dt>
<span class="chapter">
<a href="ch11.html">11. Github</a>
</span>
</dt>
<dt>
<span class="appendix">
<a href="apa.html">A. Installation</a>
</span>
</dt>
<dd>
<dl>
<dt>
<span class="section">
<a href="apa.html#linux">A.1. Linux</a>
</span>
</dt>
<dt>
<span class="section">
<a href="apa.html#sec.osx">A.2. Mac OS X</a>
</span>
</dt>
<dt>
<span class="section">
<a href="apa.html#sec.windows">A.3. Windows</a>
</span>
</dt>
</dl>
</dd>
<dt>
<span class="appendix">
<a href="apb.html">B. Struktur eines Repositorys</a>
</span>
</dt>
<dd>
<dl>
<dt>
<span class="section">
<a href="apb.html#sec.gc">B.1. Aufräumen</a>
</span>
</dt>
<dt>
<span class="section">
<a href="apb.html#sec.gc-performance">B.2. Performance</a>
</span>
</dt>
</dl>
</dd>
</dl></div>
<div class="navheader">
<table width="100%" summary="Navigation header">
<tr>
<td width="20%" align="left"><a accesskey="p" href="ch07.html">Zurück</a> </td>
<th width="60%" align="center"> </th>
<td width="20%" align="right"> <a accesskey="n" href="ch09.html">Weiter</a></td>
</tr>
</table>
<hr />
</div>
<div class="chapter">
<div class="titlepage">
<div>
<div>
<h1 class="title"><a id="ch.scripting"></a>Kapitel 8. Git automatisieren</h1>
</div>
</div>
</div>
<p>In diesem Kapitel stellen wir fortgeschrittene Techniken zum
Automatisieren von Git vor. Im ersten Abschnitt über
<span class="emphasis"><em>Git-Attribute</em></span> zeigen wir Ihnen, wie Sie Git anweisen, bestimmte
Dateien gesondert zu behandeln, zum Beispiel um bei Grafiken ein
externes Diff-Kommando aufzurufen.</p>
<p>Weiter geht es mit <span class="emphasis"><em>Hooks</em></span> – kleine Scripte, die beim Aufruf
verschiedener Git-Kommandos ausgeführt werden, beispielsweise um alle
Entwickler per E-Mail zu benachrichtigen, wenn neue Commits im
Repository eintreffen.</p>
<p>Danach geben wir eine grundlegende Einführung ins Scripting mit
Git und zeigen Ihnen nützliche <span class="emphasis"><em>Plumbing-Kommandos</em></span>.</p>
<p>Zum Abschluss stellen wir das mächtige <code class="literal">filter-branch</code>-Kommando
vor, mit dem Sie die Projektgeschichte im großen Stil umschreiben,
etwa um eine Datei mit einem Passwort aus <span class="emphasis"><em>allen</em></span> Commits zu
entfernen.</p>
<div class="section">
<div class="titlepage">
<div>
<div>
<h2 class="title" style="clear: both"><a id="sec.attributes"></a>8.1. Git-Attribute – Dateien gesondert behandeln</h2>
</div>
</div>
</div>
<p>Über <span class="emphasis"><em>Git-Attribute</em></span> können Sie einzelnen oder einer Gruppe von
Dateien bestimmte Eigenschaften zuweisen, so dass Git sie besonders
behandelt; Beispiele wären, für bestimmte Dateien das Zeilenende zu
forcieren oder sie als binär zu markieren.</p>
<p>Die Attribute schreiben Sie wahlweise in die Datei
<code class="literal">.gitattributes</code> oder <code class="literal">.git/info/attributes</code>.
Letztere gilt für ein Repository und wird nicht von Git verwaltet.
Eine Datei <code class="literal">.gitattributes</code> wird in der Regel eingecheckt, so
dass alle Entwickler diese Attribute verwenden. Außerdem können Sie in
Unterverzeichnissen weitere Attribut-Definitionen hinterlegen.</p>
<p>Eine Zeile in dieser Datei hat das Format:</p>
<pre class="screen"><muster> <attrib1> <attrib2> ...</pre>
<p>Ein Beispiel:</p>
<pre class="screen">*.eps binary
*.tex -text
*.c filter=indent</pre>
<p>In der Regel können Attribute gesetzt (z.B. <code class="literal">binary</code>),
aufgehoben (<code class="literal">-text</code>) oder auf einen Wert gesetzt werden
(<code class="literal">filter=indent</code>). Die Man-Page
<code class="literal">gitattributes(5)</code> beschreibt detailliert, wie Git die
Attribute interpretiert.</p>
<p>Ein Projekt, das parallel auf Windows- und Unix-Rechnern entwickelt
wird, leidet darunter, dass die Entwickler verschiedene Konventionen
für Zeilenenden verwenden. Dies ist bedingt durch das Betriebssystem:
Windows-Systeme verwenden einen Carriage Return, gefolgt von einem
Line Feed (CRLF), während unixoide Systeme nur einen Line Feed (LF)
verwenden.</p>
<p>Über geeignete Git-Attribute bestimmen Sie eine adäquate Policy – in
diesem Fall sind die Attribute <code class="literal">text</code> bzw. <code class="literal">eol</code>
zuständig. Das Attribut <code class="literal">text</code> bewirkt, dass die Zeilenenden
„normalisiert“ werden. Egal, ob der Editor eines Entwicklers
CRLF oder nur LF verwendet, Git wird im Blob nur die Version mit LF
speichern. Setzen Sie das Attribut auf <code class="literal">auto</code>, wird Git
diese Normalisierung nur ausführen, wenn die Datei auch wie Text
aussieht.</p>
<p>Das Attribut <code class="literal">eol</code> hingegen bestimmt, was bei einem Checkout
passiert. Unabhängig von der Einstellung <code class="literal">core.eol</code> des Nutzers
können Sie so für einige Dateien z.B. CRLF vorgeben (weil das Format
dies benötigt).</p>
<pre class="screen">*.txt text
*.csv eol=crlf</pre>
<p>Mit diesen Attributen werden <code class="literal">.txt</code>-Dateien intern immer mit LF
gespeichert und bei Bedarf (plattform- bzw. nutzerabhängig) als CRLF
ausgecheckt. CSV-Dateien hingegen werden auf allen Plattformen mit
CRLF ausgecheckt. (Intern wird Git all diese Blobs mit einfachen
LF-Endungen speichern.)</p>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a id="sec.smudge-clean"></a>8.1.1. Filter: Smudge und Clean</h3>
</div>
</div>
</div>
<p>Git bietet über <span class="emphasis"><em>Filter</em></span> eine Möglichkeit, Dateien nach einem
Checkout zu „verschmutzen“ (<span class="emphasis"><em>smudge</em></span>) und vor einem
<code class="literal">git add</code> wieder zu „säubern“ (<span class="emphasis"><em>clean</em></span>).</p>
<p>Die Filter erhalten keine Argumente, sondern nur den Inhalt des Blobs
auf Standard-In. Die Ausgabe des Programms wird als neuer Blob
verwendet.</p>
<p>Für einen Filter müssen Sie jeweils ein Smudge- und ein Clean-Kommando
definieren. Fehlt eine der Definitionen oder ist der Filter
<code class="literal">cat</code>, wird der Blob unverändert übernommen.</p>
<p>Welcher Filter für welche Art von Dateien verwendet wird, definieren
Sie über das Git-Attribut <code class="literal">filter</code>. Um beispielsweise C-Dateien
vor einem Commit automatisch richtig einzurücken, können Sie folgende
Filterdefinitionen verwenden (statt <code class="literal"><indent></code> sind
beliebige andere Namen möglich):</p>
<pre class="screen">$ <span class="strong"><strong>git config filter.<indent>.clean indent</strong></span>
$ <span class="strong"><strong>git config filter.<indent>.smudge cat</strong></span>
$ <span class="strong"><strong>echo '*.c filter=<indent>' > .git/info/attributes</strong></span></pre>
<p>Um eine C-Datei zu „säubern“, ruft Git nun automatisch das
Programm <code class="literal">indent</code> auf, das auf Standardsystemen installiert
sein sollte.<a href="#ftn.idm45240356161168" class="footnote" id="idm45240356161168"><sup class="footnote">[106]</sup></a></p>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a id="sec.smudge-clean-keywords"></a>8.1.2. Keywords in Dateien</h3>
</div>
</div>
</div>
<p>So lassen sich prinzipiell auch die bekannten Keyword-Expansionen
realisieren, so dass z.B. <code class="literal">$Version$</code> zu <code class="literal">$Version:
v1.5.4-rc2$</code> wird.</p>
<p>Sie definieren die Filter in Ihrer Konfiguration und statten dann
entsprechende Dateien mit diesem Git-Attribut aus. Das geht z.B. so:</p>
<pre class="screen">$ <span class="strong"><strong>git config filter.version.smudge ~/bin/git-version.smudge</strong></span>
$ <span class="strong"><strong>git config filter.version.clean ~/bin/git-version.clean</strong></span>
$ <span class="strong"><strong>echo '* filter=version' > .git/info/attributes</strong></span></pre>
<p>Ein Filter, der das <code class="literal">$Version$</code>-Keyword ersetzt bzw. wieder
aufräumt, könnte als Perl-Einzeiler realisiert werden; zunächst der
Smudge-Filter:</p>
<pre class="screen">#!/bin/sh
version=`git describe --tags`
exec perl -pe 's/$Version(:\s[^$]+)?$/$Version: '"$version"'$/g'</pre>
<p>Und der Clean-Filter:</p>
<pre class="screen">#!/usr/bin/perl -p
s/$Version: [^$]+$/$Version$/g</pre>
<p>Wichtig ist, dass mehrmalige Anwendung eines solchen Filters keine
unkontrollierten Veränderungen in der Datei vornimmt. Ein doppelter
Aufruf von Smudge sollte durch einen einfachen Aufruf von Clean wieder
behoben werden können.</p>
<div class="section">
<div class="titlepage">
<div>
<div>
<h4 class="title"><a id="sec.smudge-clean-dontuse"></a>Einschränkungen</h4>
</div>
</div>
</div>
<p>Das Konzept von Filtern in Git ist bewusst simpel gehalten und wird
auch in künftigen Versionen nicht erweitert werden. Die Filter
erhalten <span class="emphasis"><em>keine</em></span> Informationen über den Kontext, in dem sich Git
gerade befindet: Passiert ein Checkout? Ein Merge? Ein Diff? Sie
erhalten lediglich den Blob-Inhalt. Die Filter sollen also nur
<span class="emphasis"><em>kontextunabhängige</em></span> Manipulationen durchführen.</p>
<p>Zum Zeitpunkt, da Smudge aufgerufen wird, ist der <code class="literal">HEAD</code>
möglicherweise noch nicht auf dem aktuellen Stand (der obige Filter
würde bei einem <code class="literal">git checkout</code> eine falsche Versionsnummer in
die Datei schreiben, da er <span class="emphasis"><em>vor</em></span> dem Versetzen des <code class="literal">HEAD</code>
aufgerufen wird). Die Filter eignen sich also nur bedingt zur
Keyword-Expansion.</p>
<p>Das mag zwar Nutzer, die sich an dieses Feature in anderen
Versionskontrollsystemen gewöhnt haben, verärgern. Es gibt allerdings
keine guten Argumente, <span class="emphasis"><em>innerhalb</em></span> eines Versionskontrollsystems
eine solche Expansion durchzuführen. Die internen Mechanismen, die Git
verwendet, um zu überprüfen, ob Dateien verändert wurden, werden
lahmgelegt (da sie immer durch den Clean-Filter geschickt werden
müssen). Außerdem kann man aufgrund der Struktur von Git-Repositories
einen Blob durch die Commits bzw. Trees hindurch
„verfolgen“, kann also bei Bedarf die Zugehörigkeit einer
Datei zu einem Commit immer an ihrem Inhalt erkennen.</p>
<p>Eine Keyword-Expansion ist also nur <span class="emphasis"><em>außerhalb</em></span> von Git sinnvoll.
Dafür ist dann aber nicht Git zuständig, sondern ein entsprechendes
<code class="literal">Makefile</code>-Target oder ein Script. So kann beispielsweise ein
<code class="literal">make dist</code> alle Vorkommen von <code class="literal">VERSION</code> durch die
Ausgabe von <code class="literal">git describe --tags</code> ersetzen. Git wird die
Dateien als „geändert“ anzeigen. Sobald die Dateien verteilt
sind (z.B. als Tarball), kann mit <code class="literal">git reset --hard</code> wieder
aufgeräumt werden.</p>
<p>Alternativ sorgt das Attribut <code class="literal">export-subst</code> dafür, dass eine
Expansion der Form <code class="literal">$Format:<Pretty>$</code> durchgeführt wird.
Dabei muss <code class="literal"><Pretty></code> ein Format sein, das für <code class="literal">git log
--pretty=format:<Pretty></code> gültig ist, also z.B. <code class="literal">%h</code> für die
gekürzte Commit-Hash-Summe. Git expandiert diese Attribute nur, wenn
die Datei per <code class="literal">git archive</code>
(siehe <a class="xref" href="ch06.html#sec.release-create" title="6.3.2. Release erstellen">Abschnitt 6.3.2, „Release erstellen“</a>) verpackt wird.</p>
</div>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a id="sec.external-diff"></a>8.1.3. Eigene Diff-Programme</h3>
</div>
</div>
</div>
<p>Der interne Diff-Mechanismus von Git eignet sich sehr gut für alle
Arten von Plaintext. Er versagt aber bei Binärdateien – Git gibt
lediglich aus, ob sie sich unterscheiden oder nicht. Wenn Sie
allerdings ein Projekt haben, in dem Sie Binärdaten verwalten müssen,
wie z.B. PDF-, OpenOffice-Dokumente oder Bilder, dann ist es
sinnvoll, ein spezielles Programm zu definieren, das sinnvolle Diffs
dieser Dateien erstellt.</p>
<p>So gibt es beispielsweise <code class="literal">antiword</code> und <code class="literal">pdftotext</code>, um
Word-Dokumente und PDFs nach Plaintext zu konvertieren. Für
OpenOffice-Formate gibt es analoge Scripte. Bei Bildern können Sie
Kommandos aus der ImageMagick-Suite verwenden (siehe auch das Beispiel
weiter unten). Wenn Sie statistische Daten verwalten, können Sie die
geänderten Datensets nebeneinander plotten. Je nach Beschaffenheit
der Daten gibt es meist adäquate Möglichkeiten, Veränderungen zu
visualisieren.</p>
<p>Solche Konvertierungsprozesse sind natürlich verlustbehaftet: Sie
können diese Diff-Ausgabe nicht nutzen, um beispielsweise in einem
Merge-Konflikt sinnvoll Änderungen in den Dateien vorzunehmen. Aber um
einen schnellen Überblick zu erhalten, wer was geändert hat, reichen
solche Techniken allemal aus.</p>
<div class="section">
<div class="titlepage">
<div>
<div>
<h4 class="title"><a id="sec.external-diff-parameters"></a>API für externe Diff-Programme</h4>
</div>
</div>
</div>
<p>Git bietet eine simple API für eigene Diff-Filter. Einem Diff-Filter
werden immer die folgenden sieben Argumente übergeben:</p>
<div class="orderedlist">
<ol class="orderedlist" type="1">
<li class="listitem">
Pfad (Name der Datei im Git-Repository)
</li>
<li class="listitem">
alte Version der Datei
</li>
<li class="listitem">
alte SHA-1-ID des Blobs
</li>
<li class="listitem">
alte Unix-Rechte
</li>
<li class="listitem">
neue Version der Datei
</li>
<li class="listitem">
neue SHA-1-ID des Blobs
</li>
<li class="listitem">
neue Unix-Rechte
</li>
</ol>
</div>
<p>Die Argumente 2 und 5 sind möglicherweise temporäre Dateien, die
gelöscht werden, sobald sich das Diff-Programm wieder beendet; Sie
müssen sich also nicht um das Aufräumen kümmern.</p>
<p>Wenn eine der beiden Dateien nicht existiert (neu hinzugefügt oder
gelöscht), dann wird <code class="literal">/dev/null</code> als Dateiname übergeben. Der
entsprechende Blob ist dann <code class="literal">00000</code>…, auch in dem Fall,
dass eine Datei noch nicht als festes Objekt in der
Objektdatenbank liegt (also nur im Working Tree oder Index). Diese
Fälle muss das Diff-Kommando entsprechend behandeln können.</p>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h4 class="title"><a id="sec.diff-config"></a>Externe Diffs konfigurieren</h4>
</div>
</div>
</div>
<p>Es gibt zwei Möglichkeiten, ein externes Diff-Programm aufzurufen. Die
erste Methode ist temporär: Setzen Sie einfach vor dem Aufruf von
<code class="literal">git diff</code> die Umgebungsvariable <code class="literal">GIT_EXTERNAL_DIFF</code>
auf den Pfad zu Ihrem Programm:</p>
<pre class="screen">$ <span class="strong"><strong>GIT_EXTERNAL_DIFF=</pfad/zum/diff-kommando> git diff HEAD^</strong></span></pre>
<p>Die andere Möglichkeit ist persistent, erfordert aber ein wenig
Konfiguration. Zunächst definieren Sie ein eigenes Diff-Kommando
<code class="literal"><name></code>:</p>
<pre class="screen">$ <span class="strong"><strong>git config diff.<name>.command </pfad/zum/diff-kommando></strong></span></pre>
<p>Das Kommando muss mit den oben erwähnten sieben Argumenten umgehen
können. Nun müssen Sie über das Git-Attribut <code class="literal">diff</code> definieren,
welches Diff-Programm aufgerufen wird. Schreiben Sie dazu
z.B. folgende Zeilen in die Datei <code class="literal">.gitattributes</code>:</p>
<pre class="screen">*.jpg diff=imgdiff
*.pdf diff=pdfdiff</pre>
<p>Wenn Sie die Datei einchecken, müssen andere Nutzer auch entsprechende
Kommandos für <code class="literal">imgdiff</code> bzw. <code class="literal">pdfdiff</code> gesetzt haben,
sonst sehen sie die reguläre Ausgabe. Wollen Sie diese Einstellung nur
für ein Repository vorgeben, schreiben Sie diese Informationen nach
<code class="literal">.git/info/attributes</code>.</p>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h4 class="title"><a id="sec.diff-immages"></a>Bilder vergleichen</h4>
</div>
</div>
</div>
<p>Ein häufiger Anwendungsfall sind Bilder: Was hat sich zwischen zwei
Versionen eines Bildes geändert? Das zu visualisieren, ist nicht immer
einfach. Das Tool <code class="literal">compare</code> aus der ImageMagick-Suite markiert
für Bilder gleicher Größe die Stellen, die sich geändert haben. Auch
kann man die beiden Bilder hintereinander animieren und durch das
„Flackern“ erkennen, wo das Bild geändert wurde.</p>
<p>Stattdessen wollen wir ein Programm, das die beiden Bilder
gegenüberstellt. Zwischen den beiden Bildern wird eine Art
„Differenz“ dargestellt: Alle Bereiche, in denen Änderungen
aufgetreten sind, werden aus dem <span class="emphasis"><em>neuen</em></span> Bild auf weißen
Untergrund kopiert. Das Diff zeigt also, welche Bereiche hinzugekommen
sind.</p>
<p>Dafür speichern wir folgendes Script unter
<code class="literal">$HOME/bin/imgdiff</code>:<a href="#ftn.idm45240356091632" class="footnote" id="idm45240356091632"><sup class="footnote">[107]</sup></a></p>
<pre class="screen">#!/bin/sh
OLD="$2"
NEW="$5"
# "xc:none" ist "Nichts", entspricht einem fehlenden Bild
[ "$OLD" = "/dev/null" ] && OLD="xc:none"
[ "$NEW" = "/dev/null" ] && NEW="xc:none"
exec convert "$OLD" "$NEW" -alpha off \
\( -clone 0-1 -compose difference -composite -threshold 0 \) \
\( -clone 1-2 -compose copy_opacity -composite \
-compose over -background white -flatten \) \
-delete 2 -swap 1,2 +append \
-background white -flatten x:</pre>
<p>Zuletzt müssen wir noch das Diff-Kommando konfigurieren und dessen
Verwendung durch einen Eintrag in der Datei
<code class="literal">.git/info/attributes</code> sicherstellen.</p>
<pre class="screen">$ <span class="strong"><strong>git config diff.imgdiff.command ~/bin/imgdiff</strong></span>
$ <span class="strong"><strong>echo "*.gif diff=imgdiff" > .git/info/attributes</strong></span></pre>
<p>Als Beispiel verwenden wir die Ursprungsversionen des Tux.<a href="#ftn.idm45240356082944" class="footnote" id="idm45240356082944"><sup class="footnote">[108]</sup></a>
Zunächst fügen wir den schwarzweißen Tux ein:</p>
<pre class="screen">$ <span class="strong"><strong>wget http://www.isc.tamu.edu/~lewing/linux/sit3-bw-tran.1.gif \</strong></span>
<span class="strong"><strong>-Otux.gif</strong></span>
$ <span class="strong"><strong>git add tux.gif && git commit -m "tux hinzugefügt"</strong></span></pre>
<p>Im nächsten Commit wird er durch eine eingefärbte Version ersetzt:</p>
<pre class="screen">$ wget http://www.isc.tamu.edu/~lewing/linux/sit3-bw<span class="strong"><strong>o</strong></span>-tran.1.gif \
-Otux.gif
$ <span class="strong"><strong>git diff</strong></span></pre>
<p>Die Ausgabe des Kommandos <code class="literal">git diff</code> ist ein Fenster mit
folgendem Inhalt: Links die alte, rechts die neue Version, und
in der Mitte eine Maske derjenigen Teile des neuen Bildes, die anders
als das alte sind.</p>
<div class="figure">
<a id="fig.tux-diff"></a>
<p class="title">
<strong>Abbildung 8.1. Die Ausgabe von <code class="literal">git diff</code> mit dem eigenen Diff-Programm <code class="literal">imgdiff</code></strong>
</p>
<div class="figure-contents">
<div class="mediaobject">
<img src="bilder_ebook/tux-diff.png" width="486" alt="bilder_ebook/tux-diff.png" />
</div>
</div>
</div>
<br class="figure-break" />
<p>Das Beispiel mit dem Tux inkl. Anleitung finden Sie auch in einem Repository
unter: <a class="ulink" href="https://github.com/gitbuch/tux-diff-demo" target="_top">https://github.com/gitbuch/tux-diff-demo</a>.</p>
</div>
</div>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h2 class="title" style="clear: both"><a id="sec.hooks"></a>8.2. Hooks</h2>
</div>
</div>
</div>
<p>Hooks bieten einen Mechanismus, in wichtige Git-Kommandos
„einzuhaken“ und eigene Aktionen auszuführen. In der Regel
sind Hooks daher kleine Shell-Scripte, um automatisierte Aufgaben zu
erfüllen, wie z.B. E-Mails zu versenden, sobald neue Commits
hochgeladen werden, oder vor einem Commit auf Whitespace-Fehler zu
überprüfen und ggf. eine Warnung auszugeben.</p>
<p>Damit Hooks von Git ausgeführt werden, müssen sie im Verzeichnis
<code class="literal">hooks/</code> im Git-Verzeichnis liegen, also unter
<code class="literal">.git/hooks/</code> bzw. unter <code class="literal">hooks/</code> auf
oberster Ebene bei Bare Repositories. Zudem müssen sie ausführbar
sein.</p>
<p>Git installiert bei einem <code class="literal">git init</code> automatisch Beispiel-Hooks, diese
tragen aber die Endung <code class="literal"><hook>.sample</code> und werden daher ohne das Zutun des Nutzers
(Umbenennung der Dateien) nicht ausgeführt.</p>
<p>Einen mitgelieferten Hook aktivieren Sie also z.B. so:</p>
<pre class="screen">$ <span class="strong"><strong>mv .git/hooks/commit-msg.sample .git/hooks/commit-msg</strong></span></pre>
<p>Hooks kommen in zwei Klassen: solche, die lokal ausgeführt werden
(Commit-Nachrichten bzw. Patches überprüfen, Aktionen nach einem
Merge oder Checkout ausführen etc.), und solche, die
serverseitig ausgeführt werden, wenn Sie Änderungen per <code class="literal">git
push</code> veröffentlichen.<a href="#ftn.idm45240356057536" class="footnote" id="idm45240356057536"><sup class="footnote">[109]</sup></a></p>
<p>Hooks, deren Name mit <code class="literal">pre-</code> beginnt, können häufig dazu
benutzt werden, zu entscheiden, ob eine Aktion ausgeführt wird oder
nicht. Beendet sich ein <code class="literal">pre</code>-Hook nicht erfolgreich (d.h.
mit einem Exit-Status ungleich Null), wird die Aktion
abgebrochen. Eine technische Dokumentation der Funktionsweise finden
Sie in der Man-Page <code class="literal">githooks(5)</code>.</p>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a id="sec.hooks-commit"></a>8.2.1. Commits</h3>
</div>
</div>
</div>
<div class="variablelist">
<dl class="variablelist">
<dt>
<span class="term">
<code class="literal">pre-commit</code>
</span>
</dt>
<dd>
Wird aufgerufen, bevor die Commit-Nachricht abgefragt
wird. Beendet sich der Hook mit einem Wert ungleich Null, wird der
Commit-Vorgang abgebrochen. Der per Default installierte Hook
überprüft, ob eine neu hinzugefügte Datei Nicht-ASCII-Zeichen im
Dateinamen trägt, und ob in den geänderten Dateien Whitespace-Fehler vorhanden
sind. Mit der Option <code class="literal">-n</code> bzw. <code class="literal">--no-verify</code> überspringt <code class="literal">git commit</code>
diesen Hook.
</dd>
<dt>
<span class="term">
<code class="literal">prepare-commit-msg</code>
</span>
</dt>
<dd>
Wird ausgeführt, direkt bevor die Nachricht in
einem Editor angezeigt wird. Erhält bis zu drei Parameter, von denen
der erste die Datei ist, in der die Commit-Nachricht gespeichert ist,
so dass sie editiert werden kann. Der Hook kann z.B. automatisiert
Zeilen hinzufügen. Ein Exit-Status ungleich Null bricht den
Commit-Vorgang ab. Dieser Hook kann allerdings nicht übersprungen
werden und sollte daher nicht die Funktionalität von <code class="literal">pre-commit</code>
duplizieren oder ersetzen.
</dd>
<dt>
<span class="term">
<code class="literal">commit-msg</code>
</span>
</dt>
<dd>
Wird ausgeführt, nachdem die Commit-Nachricht
eingegeben wurde. Das einzige Argument ist die Datei, in der die
Nachricht gespeichert ist, so dass sie modifiziert werden kann
(Normalisierung). Dieser Hook kann durch <code class="literal">-n</code> bzw. <code class="literal">--no-verify</code>
übersprungen werden; beendet er sich nicht erfolgreich, bricht der
Commit-Vorgang ab.
</dd>
<dt>
<span class="term">
<code class="literal">post-commit</code>
</span>
</dt>
<dd>
Wird aufgerufen, nachdem ein Commit erstellt wurde.
</dd>
</dl>
</div>
<p>Diese Hooks agieren nur lokal und dienen dazu, bestimmte Richtlinien
bezüglich der Commits bzw. der Commit-Nachrichten durchzusetzen.
Besonders der <code class="literal">pre-commit</code>-Hook ist dabei hilfreich. Zum
Beispiel zeigen manche Editoren nicht adäquat an, wenn am Ende der
Zeile Leerzeichen sind oder Leerzeilen Leerzeichen enthalten. Das ist
wiederum störend, wenn andere Entwickler neben regulären Änderungen
auch noch Whitespace aufräumen müssen. Hier hilft Git mit folgendem
Kommando:</p>
<pre class="screen">$ <span class="strong"><strong>git diff --cached --check</strong></span>
hooks.tex:82: trailing whitespace.
<span class="strong"><strong>+</strong></span> auch noch Whitespace aufräumen müssen._</pre>
<p>Die Option <code class="literal">--check</code> lässt <code class="literal">git diff</code> überprüfen, ob
solche Whitespace-Fehler vorliegen, und beendet sich nur erfolgreich,
wenn die Änderungen fehlerfrei sind. Schreiben Sie dieses Kommando in
Ihren <code class="literal">pre-commit</code>-Hook, werden Sie immer gewarnt, wenn Sie
Whitespace-Fehler einchecken wollen. Sind Sie ganz sicher, können Sie
den Hook einfach temporär per <code class="literal">git commit -n</code> aussetzen.</p>
<p>Ganz analog können Sie auch für eine Scriptsprache Ihrer Wahl das
„Syntax überprüfen“-Kommando in diesem Hook speichern. So
zum Beispiel folgender Block für Perl-Scripte:</p>
<pre class="screen">git diff --diff-filter=MA --cached --name-only |
while read file; do
if [ -f $file ] && [ $(head -n 1 $file) = "#!/usr/bin/perl" ]; then
perl -c $file || exit 1
fi
done
true</pre>
<p>Die Namen aller im Index veränderten Dateien (Diff-Filter
<code class="literal">modified</code> und <code class="literal">added</code>, siehe auch
<a class="xref" href="ch08.html#sec.scripting-find-changes" title="8.3.4. Änderungen finden">Abschnitt 8.3.4, „Änderungen finden“</a>) werden
an eine Subshell weitergeleitet, die pro Datei überprüft, ob die erste
Zeile ein Perl-Script ist. Wenn ja, wird die Datei mit <code class="literal">perl
-c</code> überprüft. Falls sich ein Syntaxfehler in der Datei befindet,
gibt das Kommando eine entsprechende Fehlermeldung aus, und das
<code class="literal">exit 1</code> beendet den Hook, so dass Git den Commit-Vorgang
abbricht, noch bevor ein Editor geöffnet wird, um die Commit-Nachricht
einzugeben.</p>
<p>Das schließende <code class="literal">true</code> wird z.B. benötigt, wenn eine
Nicht-Perl-Datei editiert wurde: Dann schlägt das If-Konstrukt fehl,
die Shell gibt den Rückgabewert des letzten Kommandos wieder, und
obwohl es nichts zu bemängeln gibt, wird Git den Commit nicht
ausführen. Durch die Zeile <code class="literal">true</code> war der Hook erfolgreich,
wenn alle Durchläufe der <code class="literal">while</code>-Schleife erfolgreich waren.</p>
<p>Der Hook kann natürlich vereinfacht werden, wenn man annimmt, dass
alle Perl-Dateien als <code class="literal"><name>.pl</code> vorliegen. Dann reicht
der folgende Code:</p>
<pre class="screen">git ls-files -z -- '*.pl' | xargs -z -n 1 perl -c</pre>
<p>Weil Sie im Zweifel nur die von Git verwalteten Dateien überprüfen
wollen, eignet sich hier ein <code class="literal">git ls-files</code> besser als ein
simples <code class="literal">ls</code>, denn das würde auch nicht getrackte Dateien, die
auf <code class="literal">.pl</code> enden, auflisten.</p>
<p>Neben der Überprüfung der Syntax können Sie natürlich auch Programme
im Stil von Lint einsetzen, die den Quellcode auf
„unschöne“ oder nicht portable Konstrukte überprüfen.</p>
<p>Solche Hooks sind äußerst sinnvoll, um nicht versehentlich
fehlerhaften Code einzuchecken. Sind Warnungen unangebracht, können
Sie den Hook <code class="literal">pre-commit</code> ja immer über die Option <code class="literal">-n</code>
beim Committen überspringen.</p>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a id="sec.hooks-server"></a>8.2.2. Serverseitig</h3>
</div>
</div>
</div>
<p>Die folgenden Hooks werden auf Empfängerseite von <code class="literal">git
receive-pack</code> aufgerufen, nachdem der Nutzer im lokalen Repository
<code class="literal">git push</code> eingegeben hat.</p>
<p>Für einen Push-Vorgang erstellt <code class="literal">git send-pack</code> auf der lokalen
Seite <span class="emphasis"><em>ein</em></span> Packfile (siehe auch <a class="xref" href="ch02.html#sec.od" title="2.2.3. Die Objektdatenbank">Abschnitt 2.2.3, „Die Objektdatenbank“</a>), das
von <code class="literal">git receive-pack</code> auf der Empfängerseite entgegengenommen
wird. Ein solches Packfile enthält die neuen Werte einer oder mehrerer
Referenzen sowie die Commits, die das Empfänger-Repository benötigt,
um die Versionsgeschichte komplett abzubilden. Welche Commits das
sind, handeln die beiden Seiten vorher aus (ähnlich einer
Merge-Basis).</p>
<div class="variablelist">
<dl class="variablelist">
<dt>
<span class="term">
<code class="literal">pre-receive</code>
</span>
</dt>
<dd>
Der Hook wird einmal aufgerufen und erhält auf