-
Notifications
You must be signed in to change notification settings - Fork 2
/
ch02.html
2071 lines (2065 loc) · 111 KB
/
ch02.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 2. Grundlagen</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="ch01.html" title="Kapitel 1. Einführung und erste Schritte" />
<link rel="next" href="ch03.html" title="Kapitel 3. Praktische Versionsverwaltung" />
<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="ch01.html">Zurück</a> </td>
<th width="60%" align="center"> </th>
<td width="20%" align="right"> <a accesskey="n" href="ch03.html">Weiter</a></td>
</tr>
</table>
<hr />
</div>
<div class="chapter">
<div class="titlepage">
<div>
<div>
<h1 class="title"><a id="ch.interna"></a>Kapitel 2. Grundlagen</h1>
</div>
</div>
</div>
<p>In diesem Kapitel stellen wir Ihnen die wichtigsten Git-Kommandos vor,
mit Hilfe derer Sie Ihre Projektdateien in Git verwalten. Unabdingbar
für eine fortgeschrittene Nutzung ist das Verständnis des
Git-Objektmodells; dieses wichtige Konzept behandeln wir im zweiten Abschnitt
des Kapitels. Mögen die Ausführungen zunächst allzu theoretisch
scheinen, so möchten wir Sie Ihnen dennoch sehr ans Herz legen. Alle
weiteren Aktionen werden Ihnen mit dem Wissen um diese Hintergründe
deutlich leichter von der Hand gehen.</p>
<div class="section">
<div class="titlepage">
<div>
<div>
<h2 class="title" style="clear: both"><a id="sec.grundlagen"></a>2.1. Git-Kommandos</h2>
</div>
</div>
</div>
<p>Die Kommandos, die Sie zum Einstieg kennengelernt haben (vor allem
<code class="literal">add</code> und <code class="literal">commit</code>), arbeiten auf dem Index. Im
Folgenden werden wir uns genauer mit dem Index auseinandersetzen und
die erweiterte Benutzung dieser Kommandos behandeln.</p>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a id="sec.index"></a>2.1.1. Index</h3>
</div>
</div>
</div>
<p>Der Inhalt von Dateien liegt für Git auf drei Ebenen, dem
<span class="emphasis"><em>Working Tree</em></span>, dem <span class="emphasis"><em>Index</em></span> und dem <span class="emphasis"><em>Repository</em></span>. Der
Working Tree entspricht den Dateien, wie sie auf dem Dateisystem Ihres
Arbeitsrechners liegen – wenn Sie also Dateien mit einem Editor
bearbeiten, mit <code class="literal">grep</code> darin suchen etc., operieren Sie immer
auf dem Working Tree.</p>
<p>Das Repository ist der Sammelbehälter für Commits, also Änderungen,
versehen mit Angaben zu Autor, Datum und Beschreibung. Die Commits
ergeben zusammen die <span class="emphasis"><em>Versionsgeschichte</em></span>.</p>
<p>Git führt nun, im Gegensatz zu vielen anderen
Versionskontrollsystemen, eine Neuerung ein, den Index. Es handelt
sich um eine etwas schwierig greifbare Zwischenebene zwischen Working
Tree und Repository. Er dient dazu, Commits vorzubereiten. Das
bedeutet, dass Sie nicht immer <span class="emphasis"><em>alle</em></span> Änderungen, die Sie an
einer Datei vorgenommen haben, auch als Commit einchecken müssen.</p>
<p>Die Git-Kommandos <code class="literal">add</code> und <code class="literal">reset</code> agieren (in ihrer
Grundform) auf dem Index und bringen Änderungen in den Index ein bzw.
löschen diese wieder; erst das Kommando <code class="literal">commit</code> überträgt die
Datei, wie sie im Index vorgehalten wird, in das Repository (<a class="xref" href="ch02.html#fig.index" title="Abbildung 2.1. Kommandos add, reset und commit">Abbildung 2.1, „Kommandos <code class="literal">add</code>, <code class="literal">reset</code> und <code class="literal">commit</code>“</a>).</p>
<div class="figure">
<a id="fig.index"></a>
<p class="title">
<strong>Abbildung 2.1. Kommandos <code class="literal">add</code>, <code class="literal">reset</code> und <code class="literal">commit</code></strong>
</p>
<div class="figure-contents">
<div class="mediaobject">
<img src="bilder_ebook/index.png" width="486" alt="bilder_ebook/index.png" />
</div>
</div>
</div>
<br class="figure-break" />
<p>Im Ausgangszustand, das heißt wenn <code class="literal">git status</code> die Nachricht
<code class="literal">nothing to commit</code> ausgibt, sind Working Tree und Index mit
<code class="literal">HEAD</code> synchronisiert. Der Index ist also nicht
„leer“, sondern enthält die Dateien im gleichen Zustand, wie
sie im Working Tree vorliegen.</p>
<p>In der Regel ist dann der Arbeitsablauf folgender: Zuerst nehmen Sie mit
einem Editor eine Veränderung am Working Tree vor. Diese Veränderung
wird durch <code class="literal">add</code> in den Index übernommen und schließlich per
<code class="literal">commit</code> im Repository abgespeichert.</p>
<p>Sie können sich die Unterschiede zwischen diesen drei Ebenen jeweils
durch das <code class="literal">diff</code>-Kommando anzeigen lassen. Ein simples
<code class="literal">git diff</code> zeigt die Unterschiede zwischen Working Tree und
Index an – also die Unterschiede zwischen den (tatsächlichen) Dateien
auf Ihrem Arbeitssystem und den Dateien, wie sie eingecheckt würden,
wenn Sie <code class="literal">git commit</code> aufrufen würden.</p>
<p>Das Kommando <code class="literal">git diff --staged</code> zeigt hingegen die
Unterschiede zwischen Index (der auch <span class="emphasis"><em>Staging Area</em></span> genannt
wird) und Repository an, also die Unterschiede, die ein Commit ins
Repository übertragen würde. Im Ausgangszustand, wenn Working Tree und Index
mit <code class="literal">HEAD</code> synchron sind, erzeugen weder <code class="literal">git diff</code> noch
<code class="literal">git diff --staged</code> eine Ausgabe.</p>
<p>Wollen Sie alle
Änderungen an allen Dateien übernehmen, gibt es zwei
Abkürzungen: Zunächst die Option <code class="literal">-u</code> bzw. <code class="literal">--update</code>
von <code class="literal">git add</code>. Dadurch werden alle Veränderungen in den Index
übertragen, aber noch kein Commit erzeugt. Weiter abkürzen können Sie
mit der Option <code class="literal">-a</code> bzw. <code class="literal">--all</code> von <code class="literal">git
commit</code>. Dies ist eine Kombination aus <code class="literal">git add -u</code> und
<code class="literal">git commit</code>, wodurch alle Veränderungen an allen Dateien in
einem Commit zusammengefasst werden – Sie umgehen den Index.
Vermeiden Sie es, sich diese Optionen zur Angewohnheit zu machen – sie
sind zwar gelegentlich als Abkürzung ganz praktisch, verringern aber
die Flexibilität.</p>
<div class="section">
<div class="titlepage">
<div>
<div>
<h4 class="title"><a id="sec.diff-color-words"></a>Diff auf Wortbasis</h4>
</div>
</div>
</div>
<p>Ein alternatives Ausgabeformat für <code class="literal">git diff</code> ist das
sog. <span class="emphasis"><em>Word-Diff</em></span>, das über die Option
<code class="literal">--word-diff</code> zur Verfügung steht. Statt der entfernten
und hinzugefügten Zeilen zeigt die Ausgabe von <code class="literal">git diff</code>
mit einer entsprechenden Syntax sowie farblich kodiert die
hinzugekommenen (grün) und entfernten (rot)
Wörter.<a href="#ftn.idm45240359715520" class="footnote" id="idm45240359715520"><sup class="footnote">[12]</sup></a> Das ist dann praktisch, wenn Sie in
einer Datei nur einzelne Wörter ändern, beispielsweise bei der
Korrektur von AsciiDoc- oder LaTeX-Dokumenten, denn ein Diff ist schwierig zu lesen,
wenn sich hinzugefügte und entfernte Zeile nur durch ein einziges Wort
unterscheiden:</p>
<pre class="screen">$ <span class="strong"><strong>git diff</strong></span>
...
- die Option `--color-words` zur Verfgung steht. Statt der entfernten
+ die Option `--color-words` zur Verfügung steht. Statt der entfernten
...</pre>
<p>Verwenden Sie hingegen die Option <code class="literal">--word-diff</code>, so werden nur geänderte
Wörter entsprechend markiert angezeigt; außerdem werden Zeilenumbrüche
ignoriert, was ebenfalls sehr praktisch ist, weil eine Neuausrichtung
der Wörter nicht als Änderung in die Diff-Ausgabe eingeht:</p>
<pre class="screen">$ <span class="strong"><strong>git diff --word-diff</strong></span>
...
--color-words zur [-Verfgung-]{<code class="literal">Verfügung</code>} steht.
...</pre>
<div class="tip" style="margin-left: 0; margin-right: 10%;">
<h3 class="title">Tipp</h3>
<p>Falls Sie viel mit Fließtext arbeiten, bietet es sich an, ein Alias zur
Abkürzung dieses Befehls einzurichten, so dass Sie beispielsweise nur
noch <code class="literal">git dw</code> eingeben müssen:</p>
<pre class="screen">$ <span class="strong"><strong>git config --global alias.dw "diff --word-diff"</strong></span></pre>
</div>
</div>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a id="sec.add-p"></a>2.1.2. Commits schrittweise erstellen</h3>
</div>
</div>
</div>
<p>Warum aber sollte man Commits schrittweise erstellen – will man nicht
immer alle Änderungen auch einchecken?</p>
<p>Ja, natürlich will man seine Änderungen in der Regel vollständig
übernehmen. Es kann allerdings sinnvoll sein, sie in Schritten
einzupflegen, um etwa die Entwicklungsgeschichte besser abzubilden.</p>
<p>Ein Beispiel: Sie haben in den vergangenen drei Stunden intensiv an
Ihrem Software-Projekt gearbeitet, haben aber, weil es so spannend
war, vergessen, die vier neuen Features in handliche Commits zu
verpacken. Zudem sind die Features über diverse Dateien verstreut.</p>
<p>Im besten Fall wollen Sie also selektiv arbeiten, d.h. nicht alle
Veränderungen aus einer Datei in einen Commit übernehmen, sondern nur
bestimmte Zeilen (Funktionen, Definitionen, Tests, …), und das auch
noch aus verschiedenen Dateien.</p>
<p>Der Index von Git bietet dafür die gewünschte Flexibilität. Sie
sammeln einige Änderungen im Index und verpacken sie in einem Commit – alle anderen Änderungen bleiben aber nach wie vor in den Dateien
erhalten.</p>
<p>Wir wollen das anhand des „Hello World!“-Beispiels aus dem
vorigen Kapitel illustrieren. Zur Erinnerung der Inhalt der Datei
<code class="literal">hello.pl</code>:</p>
<pre class="screen"># Hello World! in Perl
print "Hello World!\n";</pre>
<p>Nun präparieren wir die Datei so, dass sie mehrere unabhängige
Veränderungen hat, die wir <span class="emphasis"><em>nicht</em></span> in einem einzelnen Commit
zusammenfassen wollen. Zunächst fügen wir eine <span class="emphasis"><em>Shebang</em></span>-Zeile
am Anfang hinzu.<a href="#ftn.idm45240359696336" class="footnote" id="idm45240359696336"><sup class="footnote">[13]</sup></a> Außerdem
kommt eine Zeile hinzu, die den Autor benennt, sowie eine
Perl-Anweisung <code class="literal">use strict</code>, die den Perl-Interpreter anweist,
bei der Syntaxanalyse möglichst streng zu sein. Wichtig ist für unser
Beispiel, dass die Datei an mehreren Stellen verändert wurde:</p>
<pre class="screen">#!/usr/bin/perl
# Hello World! in Perl
# Author: Valentin Haenel
use strict;
print "Hello World!\n";</pre>
<p>Mit einem einfachen <code class="literal">git add hello.pl</code> würden alle neuen Zeilen
dem Index hinzugefügt – der Stand der Datei im Index wäre also der
gleiche wie im Working Tree. Stattdessen verwenden wir die Option
<code class="literal">--patch</code> bzw. kurz <code class="literal">-p</code>.<a href="#ftn.idm45240359689488" class="footnote" id="idm45240359689488"><sup class="footnote">[14]</sup></a> Dies hat zur Folge, dass
wir interaktiv gefragt werden, welche Veränderungen wir dem Index
hinzufügen wollen. Git bietet uns jede Veränderung einzeln an, und wir
können von Fall zu Fall entscheiden, wie wir mit dieser verfahren
wollen:</p>
<pre class="screen">$ <span class="strong"><strong>git add -p</strong></span>
diff --git a/hello.pl b/hello.pl
index c6f28d5..908e967 100644
--- a/hello.pl
+++ b/hello.pl
@@ -1,2 +1,5 @@
+#!/usr/bin/perl
# Hello World! in Perl
+# Author: Valentin Haenel
+use strict;
print "Hello World!\n";
Stage this hunk [y,n,q,a,d,/,s,e,?]?</pre>
<p>Hier zeigt Git alle Änderungen an, da sie im Code sehr nah
beieinander liegen. Bei weit auseinanderliegenden oder auf
verschiedene Dateien verteilten Veränderungen werden sie getrennt
angeboten. Der Begriff <span class="emphasis"><em>Hunk</em></span> bezeichnet lose zusammenhängende
Zeilen im Quellcode. Wir haben an dieser Stelle unter anderem folgende Optionen:</p>
<pre class="screen">Stage this hunk[y,n,q,a,d,/,s,e,?]?</pre>
<p>Die Optionen sind jeweils nur einen Buchstaben lang und schwierig zu
merken. Eine kleine Erinnerung erhalten Sie immer durch <span class="emphasis"><em>[?]</em></span>.
Die wichtigsten Optionen haben wir im Folgenden
zusammengefasst.</p>
<div class="variablelist">
<dl class="variablelist">
<dt>
<span class="term">
<code class="literal">y</code> (<span class="emphasis"><em>yes</em></span>)
</span>
</dt>
<dd>
Übernimm den aktuellen Hunk in den Index.
</dd>
<dt>
<span class="term">
<code class="literal">n</code> (<span class="emphasis"><em>no</em></span>)
</span>
</dt>
<dd>
Übernimm den aktuellen Hunk nicht.
</dd>
<dt>
<span class="term">
<code class="literal">q</code> (<span class="emphasis"><em>quit</em></span>)
</span>
</dt>
<dd>
Übernimm weder den aktuellen Hunk noch einen der folgenden.
</dd>
<dt>
<span class="term">
<code class="literal">a</code> (<span class="emphasis"><em>all</em></span>)
</span>
</dt>
<dd>
Übernimm den aktuellen Hunk und alle, die folgen (in der aktuellen Datei).
</dd>
<dt>
<span class="term">
<code class="literal">s</code> (<span class="emphasis"><em>split</em></span>)
</span>
</dt>
<dd>
Versuche, den aktuellen Hunk zu teilen.
</dd>
<dt>
<span class="term">
<code class="literal">e</code> (<span class="emphasis"><em>edit</em></span>)
</span>
</dt>
<dd>
Editiere den aktuellen Hunk.<a href="#ftn.idm45240359664656" class="footnote" id="idm45240359664656"><sup class="footnote">[15]</sup></a>
</dd>
</dl>
</div>
<p>In dem Beispiel teilen wir den aktuellen Hunk und geben
<code class="literal">s</code> für <span class="emphasis"><em>split</em></span> ein.</p>
<pre class="screen">Stage this hunk [y,n,q,a,d,/,s,e,?]? <span class="strong"><strong>[s]</strong></span>
Split into 2 hunks.
@@ -1 +1,2 @@
+#!/usr/bin/perl
# Hello World! in Perl</pre>
<p>Git bestätigt, dass der Hunk erfolgreich geteilt werden konnte, und
bietet uns nun ein Diff an, das nur die Shebang-Zeile
enthält.<a href="#ftn.idm45240359657728" class="footnote" id="idm45240359657728"><sup class="footnote">[16]</sup></a> Wir geben <code class="literal">y</code> für
<span class="emphasis"><em>yes</em></span> an und beim nächsten Hunk <code class="literal">q</code> für <span class="emphasis"><em>quit</em></span>. Um
zu überprüfen, ob alles geklappt hat, verwenden wir <code class="literal">git diff</code>
mit der Option <code class="literal">--staged</code>, die den Unterschied zwischen
Index und <code class="literal">HEAD</code> (dem neuesten Commit)
anzeigt:</p>
<pre class="screen">$ <span class="strong"><strong>git diff --staged</strong></span>
diff --git a/hello.pl b/hello.pl
index c6f28d5..d2cc6dc 100644
--- a/hello.pl
+++ b/hello.pl
@@ -1,2 +1,3 @@
+#!/usr/bin/perl
# Hello World! in Perl
print "Hello World!\n";</pre>
<p>Um zu sehen, welche Veränderungen sich noch <span class="emphasis"><em>nicht</em></span> im Index
befinden, reicht ein einfacher Aufruf von <code class="literal">git diff</code>, der uns
zeigt, dass sich – wie erwartet – noch zwei Zeilen im Working Tree
befinden:</p>
<pre class="screen">$ <span class="strong"><strong>git diff</strong></span>
diff --git a/hello.pl b/hello.pl
index d2cc6dc..908e967 100644
--- a/hello.pl
+++ b/hello.pl
@@ -1,3 +1,5 @@
#!/usr/bin/perl
# Hello World! in Perl
+# Author: Valentin Haenel
+use strict;
print "Hello World!\n";</pre>
<p>An dieser Stelle könnten wir einen Commit erzeugen, wollen zur
Demonstration aber noch einmal von vorn beginnen. Darum setzen wir
mit <code class="literal">git reset HEAD</code> den Index zurück.</p>
<pre class="screen">$ <span class="strong"><strong>git reset HEAD</strong></span>
Unstaged changes after reset:
M hello.pl</pre>
<p>Git bestätigt und nennt die Dateien, in denen sich Veränderungen
befinden; in diesem Fall ist es nur die eine.</p>
<p>Das Kommando <code class="literal">git reset</code> ist gewissermaßen das Gegenstück zu
<code class="literal">git add</code>: Statt Unterschiede aus dem Working Tree in den Index
zu übertragen, überträgt <code class="literal">reset</code> Unterschiede aus dem
Repository in den Index. Änderungen <span class="emphasis"><em>in den</em></span> Working Tree zu
übertragen, ist möglicherweise destruktiv, da Ihre Änderungen
verlorengehen könnten. Daher ist dies nur mit der Option
<code class="literal">--hard</code> möglich, die wir in <a class="xref" href="ch03.html#sec.reset" title="3.2.3. Reset und der Index">Abschnitt 3.2.3, „Reset und der Index“</a>
behandeln.</p>
<p>Sollten Sie häufiger <code class="literal">git add -p</code> verwenden, ist es nur eine
Frage der Zeit, bis Sie versehentlich einen Hunk auswählen, den Sie
eigentlich gar nicht wollten. Sollte der Index leer gewesen sein, ist
dies kein Problem, da Sie ihn ja zurücksetzen können, um von vorn
anzufangen. Problematisch wird es erst, wenn Sie bereits viele
Veränderungen im Index aufgezeichnet haben und diese nicht verlieren
möchten, Sie also einen bestimmten Hunk aus dem Index entfernen, ohne
die anderen Hunks anfassen zu wollen.</p>
<p>Analog zu <code class="literal">git add -p</code> gibt es daher den Befehl <code class="literal">git
reset -p</code>, der einzelne Hunks wieder aus dem Index entfernt. Um das
zu demonstrieren, übernehmen wir zunächst alle Veränderungen mit
<code class="literal">git add hello.pl</code> und starten <code class="literal">git reset -p</code>.</p>
<pre class="screen">$ <span class="strong"><strong>git reset -p</strong></span>
diff --git a/hello.pl b/hello.pl
index c6f28d5..908e967 100644
--- a/hello.pl
+++ b/hello.pl
@@ -1,2 +1,5 @@
+#!/usr/bin/perl
# Hello World! in Perl
+# Author: Valentin Haenel
+use strict;
print "Hello World!\n";
Unstage this hunk [y,n,q,a,d,/,s,e,?]?</pre>
<p>Wie bei dem Beispiel mit <code class="literal">git add -p</code> bietet Git nach und nach
Hunks an, jedoch sind es diesmal alle Hunks im Index. Entsprechend
lautet die Frage: <code class="literal">Unstage this hunk [y,n,q,a,d,/,s,e,?]?</code>, also
ob wir den Hunk wieder aus dem Index herausnehmen möchten. Wie gehabt,
erhalten wir durch die Eingabe des Fragezeichens eine erweiterte
Beschreibung der verfügbaren Optionen. Wir drücken an dieser Stelle
einmal <code class="literal">s</code> für <span class="emphasis"><em>split</em></span>, einmal <code class="literal">n</code> für <span class="emphasis"><em>no</em></span>
und einmal <code class="literal">y</code> für <span class="emphasis"><em>yes</em></span>. Damit sollte sich jetzt nur die
Shebang-Zeile im Index befinden:</p>
<pre class="screen">$ <span class="strong"><strong>git diff --staged</strong></span>
diff --git a/hello.pl b/hello.pl
index c6f28d5..d2cc6dc 100644
--- a/hello.pl
+++ b/hello.pl
@@ -1,2 +1,3 @@
+#!/usr/bin/perl
# Hello World! in Perl
print "Hello World!\n";</pre>
<div class="tip" style="margin-left: 0; margin-right: 10%;">
<h3 class="title">Tipp</h3>
<p>Bei den interaktiven Modi von <code class="literal">git add</code> und <code class="literal">git
reset</code> müssen Sie nach Eingabe einer Option die Enter-Taste
drücken. Mit folgender Konfigurationseinstellung sparen Sie sich
diesen zusätzlichen Tastendruck.</p>
<pre class="screen">$ <span class="strong"><strong>git config --global interactive.singlekey true</strong></span></pre>
</div>
<p>Ein Wort der Warnung:
Ein <code class="literal">git add -p</code> kann dazu verleiten, Versionen einer Datei
einzuchecken, die nicht lauffähig oder syntaktisch korrekt sind
(z.B. weil Sie eine wesentliche Zeile vergessen haben). Verlassen
Sie sich daher nicht darauf, dass Ihr Commit korrekt ist, nur weil
<code class="literal">make</code> – was auf den Dateien des Working Tree arbeitet! – erfolgreich durchläuft. Auch wenn ein späterer Commit das Problem
behebt, stellt dies unter anderem bei der automatisierten Fehlersuche
via Bisect (siehe <a class="xref" href="ch04.html#sec.bisect" title="4.8. Regressionen finden – git bisect">Abschnitt 4.8, „Regressionen finden – git bisect“</a>) ein Problem dar.</p>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a id="sec.commit"></a>2.1.3. Commits erstellen</h3>
</div>
</div>
</div>
<p>Sie wissen nun, wie Sie Änderungen zwischen Working Tree, Index und
Repository austauschen. Wenden wir uns nun dem Kommando <code class="literal">git
commit</code> zu, mit dem Sie Änderungen im Repository
„festschreiben“.</p>
<p>Ein Commit hält den Stand aller Dateien Ihres Projekts zu einem
bestimmten Zeitpunkt fest und enthält zudem
Metainformationen:<a href="#ftn.idm45240359613952" class="footnote" id="idm45240359613952"><sup class="footnote">[17]</sup></a></p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; ">
<li class="listitem">
Name des Autors und E-Mail-Adresse
</li>
<li class="listitem">
Name des Committers und E-Mail-Adresse
</li>
<li class="listitem">
Erstellungsdatum
</li>
<li class="listitem">
Commit-Datum
</li>
</ul>
</div>
<p>Tatsächlich ist es so, dass der Name des Autors <span class="emphasis"><em>nicht</em></span> der Name
des Committers (der den Commit einpflegt) sein muss. Häufig werden
Commits von Maintainern integriert oder bearbeitet (z.B.
durch <code class="literal">rebase</code>, was auch die Committer-Informationen anpasst,
siehe <a class="xref" href="ch04.html#sec.rebase" title="4.1. Commits verschieben – Rebase">Abschnitt 4.1, „Commits verschieben – Rebase“</a>). Die Committer-Informationen sind aber
in der Regel von nachrangiger Bedeutung – die meisten Programme
zeigen nur den Autor und das Datum der Commit-Erstellung an.</p>
<p>Wenn Sie einen Commit erstellen, verwendet Git die im vorherigen
Abschnitt konfigurierten Einstellungen <code class="literal">user.name</code> und
<code class="literal">user.email</code>, um den Commit zu kennzeichnen.</p>
<p>Bei einem Aufruf von <code class="literal">git commit</code> ohne zusätzliche Argumente
fasst Git alle Veränderungen im Index zu einem Commit zusammen und
öffnet einen Editor, mit dem Sie eine Commit-Message erstellen. Die
Nachricht enthält jedoch immer eine mit Rautezeichen (<code class="literal">#</code>)
auskommentierte Anleitung bzw. Informationen darüber, welche Dateien
durch den Commit geändert werden. Rufen Sie <code class="literal">git commit -v</code>
auf, erhalten Sie unterhalb der Anleitung noch ein Diff der
Änderungen, die Sie einchecken werden. Das ist vor allem praktisch, um
einen Überblick über die Änderungen zu behalten und die
Auto-Vervollständigungsfunktion Ihres Editors zu verwenden.</p>
<p>Sobald Sie den Editor beenden, erstellt Git den Commit. Geben Sie
keine Commit-Nachricht an oder löschen den gesamten Inhalt der Datei,
bricht Git ab und erstellt keinen Commit.</p>
<p>Wollen Sie nur eine Zeile schreiben, bietet sich die Option
<code class="literal">--message</code> oder kurz <code class="literal">-m</code> an, mit der Sie direkt auf
der Kommandozeile die Nachricht angeben und so den Editor umgehen:</p>
<pre class="screen">$ <span class="strong"><strong>git commit -m "Dies ist die Commit-Nachricht"</strong></span></pre>
<div class="section">
<div class="titlepage">
<div>
<div>
<h4 class="title"><a id="sec.ci-amend"></a>Einen Commit verbessern</h4>
</div>
</div>
</div>
<p>Wenn Sie vorschnell <code class="literal">git commit</code> eingegeben haben, den Commit aber noch geringfügig verbessern wollen, hilft die
Option <code class="literal">--amend</code> („berichtigen“). Die Option
veranlasst Git, die Änderungen im Index dem eben getätigten Commit
„hinzuzufügen“.<a href="#ftn.idm45240359593472" class="footnote" id="idm45240359593472"><sup class="footnote">[18]</sup></a> Außerdem können Sie die
Commit-Nachricht anpassen. Beachten Sie, dass sich die SHA-1-Summe des
Commits in jedem Fall ändert.</p>
<p>Mit dem Aufruf <code class="literal">git commit --amend</code> verändern Sie nur den
aktuellen Commit auf einem Branch. Wie Sie weiter zurückliegende
Commits verbessern, beschreibt <a class="xref" href="ch04.html#sec.rebase-onto-ci-amend" title="4.1.9. Einen Commit verbessern">Abschnitt 4.1.9, „Einen Commit verbessern“</a>.</p>
<div class="tip" style="margin-left: 0; margin-right: 10%;">
<h3 class="title">Tipp</h3>
<p>Der Aufruf von <code class="literal">git commit --amend</code> startet automatisch einen Editor, so
dass Sie auch noch die Commit-Nachricht bearbeiten können. Häufig wollen
Sie aber nur noch eine kleine Korrektur an einer Datei vornehmen, ohne die
Nachricht anzupassen. Für die Autoren bewährt sich in dieser Situation
ein Alias <code class="literal">fixup</code>:</p>
<pre class="screen">$ <span class="strong"><strong>git config --global alias.fixup "commit --amend --no-edit"</strong></span></pre>
</div>
</div>
<div class="section">
<div class="titlepage">
<div>
<div>
<h4 class="title"><a id="sec.commit-msg"></a>Gute Commit-Nachrichten</h4>
</div>
</div>
</div>
<p>Wie sollte eine Commit-Nachricht aussehen? An der äußeren Form lässt
sich nicht viel ändern: Die Commit-Nachricht muss mindestens eine
Zeile lang sein, die am besten aber maximal 50 Zeichen umfasst. Das
macht Auflistungen der Commits besser lesbar. Sofern Sie eine
genauere Beschreibung hinzufügen wollen (was äußerst empfehlenswert
ist!), trennen Sie diese von der ersten Zeile durch eine Leerzeile.
Keine Zeile sollte – wie auch bei E-Mails üblich – länger als 76
Zeichen sein.</p>
<p>Commit-Nachrichten folgen oft den Gewohnheiten oder Besonderheiten
eines Projekts. Möglicherweise gibt es Konventionen, wie zum Beispiel
Referenzen zum Bugtracking- oder Ticket-System oder ein Link zur
entsprechenden API-Dokumentation.</p>
<p>Beachten Sie die folgenden Punkte beim Verfassen einer
Commit-Beschreibung:</p>
<div class="itemizedlist">
<ul class="itemizedlist" style="list-style-type: disc; ">
<li class="listitem">
Erstellen Sie niemals leere Commit-Nachrichten. Auch
Commit-Nachrichten wie <code class="literal">Update</code>, <code class="literal">Verbesserung</code>,
<code class="literal">Fix</code> etc. sind ebenso aussagekräftig wie eine leere
Nachricht – dann können Sie es auch gleich lassen.
</li>
<li class="listitem">
Ganz wichtig: Beschreiben Sie, <span class="emphasis"><em>warum</em></span> etwas verändert
wurde und welche Implikationen das haben kann. <span class="emphasis"><em>Was</em></span> verändert
wurde, ist immer aus dem Diff ersichtlich!
</li>
<li class="listitem">
Seien Sie kritisch und vermerken Sie, wenn Sie glauben,
dass noch Verbesserungsbedarf besteht oder der Commit möglicherweise
an anderer Stelle Fehler einführt.
</li>
<li class="listitem">
Die erste Zeile sollte nicht länger als 50 Zeichen sein,
damit bleibt die Ausgabe der Versionsgeschichte stets gut formatiert
und lesbar.
</li>
<li class="listitem">
Wird die Nachricht länger, sollte in der ersten Zeile eine
kurze Zusammenfassung (mit den wichtigen Schlagwörtern) stehen.
Nach einer Leerzeile folgt dann eine umfangreiche Beschreibung.
</li>
</ul>
</div>
<p>Wir können nicht häufig genug betonen, wie wichtig eine gute
Commit-Beschreibung ist. Beim Commit sind einem Entwickler die
Änderungen noch gut im Gedächtnis, aber schon nach wenigen Tagen ist
die Motivation dahinter oft vergessen. Auch Ihre Kollegen oder
Projektmitstreiter werden es Ihnen danken, weil sie Änderungen viel
schneller erfassen können.</p>
<p>Eine gute Commit-Nachricht zu schreiben hilft auch, kurz darüber zu
reflektieren, was schon geschafft ist und was noch ansteht. Vielleicht
merken Sie beim Schreiben, dass Sie noch ein wesentliches Detail
vergessen haben.</p>
<p>Man kann auch über eine Zeitbilanz argumentieren: Die Zeit, die Sie
benötigen, um eine gute Commit-Nachricht zu schreiben, beläuft sich
auf ein bis zwei Minuten. Um wie viel Zeit wird sich die Fehlersuche
aber verringern, wenn jeder Commit gut dokumentiert ist? Wie viel Zeit
sparen Sie anderen (und sich selbst), wenn Sie zu einem – möglicherweise schwer verständlichen – Diff noch eine gute
Beschreibung mitliefern? Auch das Blame-Tool, das jede Zeile einer Datei mit
dem Commit, der sie zuletzt geändert hat, annotiert, wird bei
ausführlichen Commit-Beschreibungen zu einem unerlässlichen Hilfsmittel
werden (siehe <a class="xref" href="ch04.html#sec.blame" title="4.3. Wer hat diese Änderungen gemacht? – git blame">Abschnitt 4.3, „Wer hat diese Änderungen gemacht? – git blame“</a>).</p>
<p>Wenn Sie nicht gewöhnt sind, ausführliche Commit-Nachrichten zu
schreiben, fangen Sie heute damit an. Übung macht den Meister, und
wenn Sie sich erst einmal daran gewöhnt haben, geht die Arbeit schnell
von der Hand – Sie selbst und andere profitieren davon.</p>
<p>Das Repository des Git-Projekts ist ein Paradebeispiel für gute