Teil B — Wie man arbeitet. Zurück zur Übersicht.
Lernziel
Nach diesem Kapitel kannst du ein Repository sinnvoll strukturieren, brauchbare Commits schreiben und die gängigen Branching-Strategien gegeneinander abwägen. Du weißt, warum langlebige Branches Continuous Integration unmöglich machen, und hast den KFZ-Rechner unter Versionskontrolle gebracht.
Alles Weitere in dieser Unterlage setzt eines voraus: Es gibt genau einen Ort, an dem der aktuelle Stand steht, und dieser Ort kennt seine eigene Geschichte.
Ohne Versionskontrolle gibt es keine Pipeline, keine Nachvollziehbarkeit, kein Rollback und keine Automatisierung. Der Ordner anwendung_final_v3_neu_KORRIGIERT ist kein Scherz aus einer Präsentation, sondern der Normalzustand in erschreckend vielen Fachabteilungen.
Git ist dabei nicht einfach ein Ablageort. Es ist die Quelle der Wahrheit — und später auch der Auslöser für alles, was automatisch passiert.
# Repository anlegen git init git config user.name "Vorname Nachname" git config user.email "vorname.nachname@firma.de" # Stand ansehen git status git diff git log --oneline --graph --decorate --all # Änderungen aufnehmen git add datei.py git add -p # abschnittsweise - sehr zu empfehlen git commit -m "Nachricht" # Mit dem Server abgleichen git pull --rebase git push # Zweig anlegen und wechseln git switch -c feature/schadenfreiheitsklasse git switch main # Zusammenführen git merge feature/schadenfreiheitsklasse
git add -p geht die Änderungen abschnittsweise durch und fragt jeweils, ob sie in den Commit sollen. Das erzwingt, dass man den eigenen Code noch einmal liest, bevor man ihn festschreibt — und verhindert zuverlässig, dass Debug-Ausgaben oder auskommentierte Experimente mitwandern. Wer sich diese eine Angewohnheit aneignet, hebt die Qualität seiner Commits sofort.
| Gehört hinein | Gehört nicht hinein |
|---|---|
| Quellcode | Passwörter, Schlüssel, Zertifikate |
| Konfigurationsvorlagen | echte Konfiguration mit Zugangsdaten |
| Containerfile, Pipeline-Definition | Build-Ergebnisse, kompilierte Dateien |
| Datenbankmigrationen | Abhängigkeiten (node_modules, venv) |
| Tests und Testdaten | Produktivdaten |
| Dokumentation | große Binärdateien |
__pycache__/ *.pyc .venv/ .env *.log .idea/ .vscode/ dist/
Ein einmal eingecheckter Zugangsschlüssel ist kompromittiert — auch nach dem Löschen. Git vergisst nichts; der alte Commit bleibt in der Historie und in jedem geklonten Repository auf jedem Notebook erhalten.
Die einzig richtige Reaktion lautet: den Schlüssel oder das Passwort sofort ändern. Das Bereinigen der Historie ist optional und aufwendig, das Wechseln des Geheimnisses ist Pflicht.
Vorbeugen lässt sich mit einem Pre-Commit-Hook und Werkzeugen wie gitleaks — mehr dazu in Kapitel 14.
Ein Commit sollte eine abgeschlossene, für sich verständliche Änderung enthalten. Die Nachricht erklärt das Warum; das Was steht bereits im Diff.
Schlecht Gut
────────────────────────── ─────────────────────────────────────
"fix" "Division durch null bei SF-Klasse 0
"Änderungen" verhindern"
"asdf"
"funktioniert jetzt endlich" "Prämienberechnung auf Dezimaltyp
"WIP" umgestellt
Fließkomma führte bei Beträgen über
10.000 EUR zu Rundungsdifferenzen
von bis zu 2 Cent.
Siehe Ticket #4711."
Verbreitet und praktisch ist die Konvention Conventional Commits:
feat: neue Funktion fix: Fehlerbehebung docs: nur Dokumentation refactor: Umbau ohne Verhaltensänderung test: Tests ergänzt oder geändert chore: Wartungsarbeiten, Abhängigkeiten Beispiel: feat(praemie): Rabatt fuer Wenigfahrer ergaenzen fix(api): Zeitzone bei Vertragsbeginn korrigieren
Der Nutzen ist nicht kosmetisch: Aus solchen Nachrichten lassen sich Änderungsprotokolle automatisch erzeugen, und die Versionsnummer kann maschinell abgeleitet werden — feat erhöht die Nebenversion, fix die Patch-Nummer.
MAJOR . MINOR . PATCH │ │ └── Fehlerbehebung, abwärtskompatibel │ └────────── neue Funktion, abwärtskompatibel └────────────────── Änderung, die Bestehendes bricht 2.4.7 -> 2.4.8 Fehler behoben 2.4.8 -> 2.5.0 Funktion ergänzt 2.5.0 -> 3.0.0 Schnittstelle geändert
git tag -a v2.5.0 -m "Rabatt für Wenigfahrer" git push --tags
Der entscheidende Punkt ist die dritte Stelle von links: Eine erhöhte Hauptversion ist ein Versprechen an alle, die das System benutzen — hier musst du etwas anpassen. Wer das ernst nimmt, erspart seinen Anwendern böse Überraschungen.
Hier wird es entscheidungsrelevant, denn die Wahl bestimmt, ob Continuous Integration überhaupt möglich ist.
2010 von Vincent Driessen beschrieben. Sieht mehrere dauerhafte Zweige vor: main, develop, dazu feature/, release/ und hotfix/.
main ──●──────────────●──────────────●──► nur Releases
\ / /
release \ ●───● /
\ / /
develop ──●───●──●──────●───●───────●────►
\ \ /
feature ●───●───● ●
Passt zu: Software mit mehreren parallel gepflegten Versionen, langen Freigabezyklen, ausgelieferten Produkten beim Kunden.
Passt nicht zu: Continuous Delivery. Der Autor selbst hat 2020 einen Hinweis nachgeschoben, dass GitFlow für Anwendungen mit laufender Auslieferung nicht die richtige Wahl sei — genau der Fall, um den es in dieser Unterlage geht.
Ein dauerhafter Zweig main, der jederzeit auslieferbar ist. Kurzlebige Feature-Branches, Pull Request, Zusammenführung, Auslieferung.
main ──●───●───●───●───●───●───●──► jederzeit auslieferbar
\ / \ /
feature ●───● ●───●
(1-2 Tage)
Passt zu: Webanwendungen, internen Diensten, allem mit einer einzigen produktiven Version. Für den KFZ-Rechner ist das die richtige Wahl.
Die konsequenteste Variante: Alle arbeiten am selben Zweig, Branches leben Stunden statt Tage. Unfertige Funktionen werden über Feature Flags verborgen statt in Zweigen versteckt.
main ──●─●─●─●─●─●─●─●─●─●──► mehrere Commits pro Tag
\_/ \_/
wenige Stunden
Passt zu: erfahrenen Teams mit guter Testabdeckung. Es ist die Strategie mit den nachweislich besten Werten in der DORA-Forschung — setzt aber Vertrauen in die eigene Testautomatisierung voraus.
| GitFlow | GitHub Flow | Trunk-based | |
|---|---|---|---|
| Lebensdauer eines Zweigs | Wochen | 1–2 Tage | Stunden |
| Konfliktrisiko | hoch | gering | sehr gering |
| Eignung für CI | schlecht | gut | ideal |
| Nötige Testabdeckung | mittel | gut | sehr gut |
| Mehrere Versionen parallel | ja | nein | nein |
| Einstiegshürde | hoch | niedrig | mittel |
Der Kern der Sache: Continuous Integration bedeutet wörtlich fortlaufendes Zusammenführen. Ein Feature-Branch, der drei Wochen lebt, ist genau das Gegenteil — dort integriert niemand, dort divergiert etwas.
Je länger ein Zweig lebt, desto größer der Unterschied zum Hauptzweig, desto schmerzhafter die Zusammenführung. Teams, die wochenlange Branches führen, verbringen einen erheblichen Teil ihrer Zeit mit Konfliktauflösung und nennen das dann Entwicklung.
Faustregel: Kein Zweig lebt länger als zwei Tage. Was länger dauert, ist zu groß geschnitten.
Der Pull Request — bei GitLab Merge Request — ist mehr als eine Formalie. Er erfüllt drei Zwecke gleichzeitig: Qualitätssicherung, Wissensverteilung und, in regulierten Umgebungen, den Nachweis des Vier-Augen-Prinzips. Dazu mehr in Kapitel 17.
Was ein Prüfer tun sollte:
Was ein Prüfer nicht tun sollte:
# 1. Repository anlegen mkdir kfz-rechner && cd kfz-rechner git init -b main # 2. Struktur erzeugen mkdir -p src tests docs cat > src/praemie.py <<'EOF' """Prämienberechnung für die KFZ-Versicherung.""" from decimal import Decimal GRUNDBEITRAG = Decimal("480.00") SF_RABATT = { 0: Decimal("1.00"), 1: Decimal("0.85"), 5: Decimal("0.60"), 10: Decimal("0.40"), } def berechne_praemie(sf_klasse: int, fahrzeugalter: int) -> Decimal: """Errechnet den Jahresbeitrag in Euro.""" if sf_klasse < 0: raise ValueError("SF-Klasse darf nicht negativ sein") faktor = SF_RABATT.get(sf_klasse, Decimal("0.50")) beitrag = GRUNDBEITRAG * faktor if fahrzeugalter > 10: beitrag *= Decimal("0.90") return beitrag.quantize(Decimal("0.01")) EOF # 3. .gitignore anlegen cat > .gitignore <<'EOF' __pycache__/ *.pyc .venv/ .env EOF # 4. Erster Commit git add . git commit -m "feat: Prämienberechnung als erste Fassung Grundbeitrag mit SF-Rabatt und Altersnachlass. Dezimaltyp statt Fließkomma, um Rundungsdifferenzen bei der Beitragsberechnung auszuschließen." # 5. Feature-Branch, Änderung, Zusammenführung git switch -c feat/wenigfahrer # ... Rabatt für Wenigfahrer ergänzen ... git add -p git commit -m "feat(praemie): Rabatt für Wenigfahrer ergänzen" git switch main git merge --no-ff feat/wenigfahrer git branch -d feat/wenigfahrer # 6. Version markieren git tag -a v0.1.0 -m "Erste lauffähige Fassung" git log --oneline --graph --all
Zusatzaufgabe: Lege das Repository auf einem Git-Server an (Gitea, GitLab oder GitHub) und richte einen Pull Request ein. Lass jemanden aus deinem Umfeld eine Anmerkung hinterlassen und arbeite sie ein.
| Anti-Pattern | Folge | Abhilfe |
|---|---|---|
| Langlebige Feature-Branches | Zusammenführungskonflikte, keine echte Integration | Zweige unter zwei Tagen, Feature Flags |
| Sammelcommits | „Änderungen an 47 Dateien„ — nicht prüfbar, nicht zurückrollbar | Kleine, thematische Commits |
Direkt auf main pushen | Prüfung entfällt, Historie wird unsauber | Branch-Schutz aktivieren |
| Geheimnisse im Repository | Kompromittierung, oft jahrelang unbemerkt | Secret-Scanning, .gitignore, Tresor |
| Build-Ergebnisse eingecheckt | Repository wird riesig, ständige Konflikte | .gitignore, Artefakt-Registry |
–force auf gemeinsame Zweige | überschreibt fremde Arbeit | –force-with-lease, besser gar nicht |
| Riesige Pull Requests | Prüfung wird zur Formalie | Änderungen aufteilen |
Wie lange lebt in deinem Umfeld der durchschnittliche Feature-Branch — und was wäre nötig, damit er nur noch zwei Tage lebt?
Die Antwort führt fast immer zu denselben zwei Punkten: kleinere Arbeitspakete und bessere Tests. Womit wir beim nächsten Kapitel wären.
Weiter mit Kapitel 04 — Testen und Qualität