Die vorherigen Kapitel haben die technischen Mechanismen von Git erklärt – Commits, Branches, Merges, Tags, Konflikte. Dieses Kapitel fokussiert auf die organisatorischen und sozialen Aspekte: Wie arbeiten Teams effektiv mit Git zusammen? Wie werden Qualität und Sicherheit gewährleistet? Welche Konventionen und Praktiken haben sich bewährt?
Git ist ein technisches Tool, aber erfolgreiche Kollaboration erfordert mehr als technisches Wissen. Sie erfordert Konventionen, Prozesse, Kommunikation und Disziplin. Die folgenden Best Practices sind aus jahrelanger Team-Erfahrung destilliert.
Pull Requests (GitHub) bzw. Merge Requests (GitLab) sind nicht Teil von Git selbst, sondern Features der Hosting-Plattformen. Aber sie sind so fundamental für moderne Team-Workflows, dass sie essentiell sind.
Ein Pull Request (PR) ist eine formale Anfrage: “Ich möchte, dass diese Änderungen in diesen Branch integriert werden.” Er schafft einen Raum für: - Code Review: Andere können den Code prüfen, kommentieren, Verbesserungen vorschlagen - Diskussion: Entscheidungen werden dokumentiert - Automatisierte Checks: CI/CD läuft, Tests werden ausgeführt - Gatekeeper-Funktion: Änderungen können nur nach Approval integriert werden
Titel: Klar und beschreibend
✓ "Add OAuth2 authentication for Google and GitHub"
✗ "Update auth"
Beschreibung: Erklärt das “Warum”, nicht nur das “Was”
## Problem
Users currently can only authenticate via username/password, which is inconvenient and less secure.
## Solution
Implements OAuth2 flow for Google and GitHub providers. Users can now login with existing accounts.
## Changes
- New `AuthController` with OAuth2 endpoints
- Google and GitHub provider configurations
- Updated login UI with provider buttons
- Tests for OAuth2 flow
## Testing
- Manual: Tested with personal Google and GitHub accounts
- Automated: Added integration tests for OAuth flow
## Screenshots
[Screenshot of new login UI]
## Checklist
- [x] Tests added
- [x] Documentation updated
- [x] No breaking changes
- [x] Ready for review
Closes #234Diese Beschreibung gibt Kontext, erklärt Entscheidungen, macht Review einfach.
Scope: Ein PR sollte ein logisches Feature/Fix sein
✓ Single feature, 200 lines, 5 files
✗ Three features, refactoring, 2000 lines, 50 files
Große PRs sind schwer zu reviewen. Besser: Mehrere kleinere PRs.
Früh öffnen (Draft PRs): Öffne PR früh als “Draft” oder “WIP”. Team sieht, woran du arbeitest. Frühe Feedback-Möglichkeit.
Self-Review first: Review deinen eigenen Code, bevor du um Review bittest. Finde offensichtliche Probleme selbst.
Link Issues: Closes #123,
Fixes #456 – verknüpfe PR mit Issues für Traceability.
Keep PRs updated: Wenn main sich
ändert, halte PR aktuell (rebase oder merge main in
PR-Branch).
Respond to feedback: Kommentare beantworten, Änderungen machen, Resolution dokumentieren.
One commit after review (optional): Manche Teams squashen nach Review zu einem Commit. Andere behalten Historie. Team-Entscheidung.
Test locally: Teste nicht nur “es funktioniert”, sondern auch Edge Cases. Reviewer sollten funktionierenden Code sehen.
Code Review ist kritisch für Codequalität, Wissensaustausch und Team-Kohäsion. Aber schlecht gemacht, ist es frustrierend und kontraproduktiv.
Fokus auf Code, nicht Person:
✗ "You don't understand how authentication works"
✓ "This authentication approach has a security issue: ..."
Erkläre das “Warum”:
✗ "Change this to async/await"
✓ "Using async/await here makes error handling clearer and matches our convention in other controllers"
Unterscheide Kritizität:
[nitpick] Variable name could be more descriptive
[suggestion] Consider extracting this into a helper function
[blocking] This SQL query is vulnerable to injection
Lobe guten Code:
"Nice! This refactoring makes the logic much clearer"
"Great test coverage for edge cases"
Frage statt zu befehlen:
✗ "Move this to a separate file"
✓ "Would it make sense to extract this into a separate file for reusability?"
Review zeitnah: PRs, die Tage liegen, blockieren Entwicklung. Review innerhalb von 24 Stunden (ideal: 4 Stunden).
Nicht defensiv reagieren: Review ist nicht persönlich. Es verbessert den Code.
Frage nach, wenn unklar: “Kannst du erklären, was du mit ‘simpler Approach’ meinst?”
Diskutiere konstruktiv: Wenn du nicht zustimmst, erkläre warum. Aber sei offen für andere Perspektiven.
Danke für Reviews: Reviewer investieren Zeit. “Thanks for catching that bug!” schafft positive Kultur.
Markiere Resolved: Wenn Feedback umgesetzt, markiere Kommentare als “Resolved”.
PRs sollten automatisierte Checks durchlaufen, bevor manuelles Review:
Linting: Code-Style automatisch prüfen
lint:
script:
- npm run lint
only:
- merge_requestsTests: Alle Tests müssen passen
test:
script:
- npm test
only:
- merge_requestsCoverage: Mindest-Coverage sicherstellen
coverage:
script:
- npm run test:coverage
coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
only:
- merge_requestsSecurity Scans: Bekannte Vulnerabilities finden
security:
script:
- npm audit
allow_failure: true
only:
- merge_requestsDiese Checks sollten Merge blockieren, wenn fehlgeschlagen. Reviewer sollten nur Code sehen, der bereits Basis-Checks besteht.
Branch Protection Rules definieren, wer was in welchen Branches tun
darf. Sie sind essentiell, um main (und andere kritische
Branches) zu schützen.
Require Pull Request: - Direkte Commits auf
main verboten - Alle Änderungen gehen durch PR
Require Approvals: - Mindestens N Approvals nötig (typisch: 1-2) - Optional: Approval von Code Owner nötig
Require Status Checks: - CI muss passen - Linting muss passen - Tests müssen passen - Security Scans müssen passen
Require Up-to-Date Branch: - Branch muss mit
main synchron sein - Verhindert Integration von Code, der
gegen aktuellen main nicht getestet wurde
Restrict Who Can Push: - Nur bestimmte Rollen (Maintainer, Admin) können mergen - Verhindert versehentliche Merges
Require Signed Commits (optional): - Alle Commits müssen GPG-signiert sein - Stellt Autoren-Identität sicher
branches:
main:
protection:
required_pull_request_reviews:
required_approving_review_count: 2
dismiss_stale_reviews: true
require_code_owner_reviews: true
required_status_checks:
strict: true
contexts:
- "ci/test"
- "ci/lint"
- "security/scan"
enforce_admins: true
restrictions:
users: []
teams: ["maintainers"].github/CODEOWNERS oder .gitlab/CODEOWNERS
definiert, wer für welche Teile des Repos verantwortlich ist:
# Root-Level Owners
* @team-leads
# Frontend Team
/frontend/** @frontend-team
# Backend Team
/backend/** @backend-team
# Database
/db/** @database-admins
/backend/models/** @database-admins
# Security-Critical
/auth/** @security-team
/payment/** @security-team
# Specific Files
package.json @tech-lead
.gitlab-ci.yml @devops-team
PRs, die diese Dateien ändern, werden automatisch an entsprechende Teams assigned. Code Owner Approval kann verpflichtend gemacht werden.
Wir haben Conventional Commits in Kapitel 1600 behandelt. Hier weitere Praktiken:
Ein Commit sollte eine logische Änderung repräsentieren:
✓ Commit 1: "Add user authentication model"
✓ Commit 2: "Add authentication endpoints"
✓ Commit 3: "Add authentication UI"
✗ Commit 1: "Add auth, fix bug, update docs, refactor utils"
Atomare Commits ermöglichen: - Selektives Cherry-Picking: Einen spezifischen Fix isolieren - Einfaches Revert: Problematische Änderung rückgängig machen - Klare Historie: Verstehen, warum Änderungen gemacht wurden
Jeder Commit sollte ein funktionierender Zustand sein: - Code kompiliert - Tests passen - Applikation startet
Warum? Git Bisect: Automatische Fehlersuche durch Historie. Wenn Commits gebrochen sind, ist Bisect nutzlos.
# Bisect findet Commit, der Bug eingeführt hat
git bisect start
git bisect bad HEAD
git bisect good v1.0.0
# Git checked out Commits, du testest, gibst "good" oder "bad"
# Git findet den exakten CommitWenn Intermediate-Commits gebrochen sind, funktioniert das nicht.
Wir haben das Format behandelt. Hier: Was gehört in gute Messages?
Imperativ Mood:
✓ "Add OAuth2 authentication"
✗ "Added OAuth2 authentication"
✗ "Adds OAuth2 authentication"
Körper erklärt “Warum”, nicht “Was”:
Add rate limiting to API endpoints
Without rate limiting, our API is vulnerable to DoS attacks.
A single client could overwhelm the server with requests.
This commit adds rate limiting using Redis:
- 100 requests per minute per IP
- 1000 requests per hour per API key
- Configurable limits via environment variables
Closes #456
Das “Was” ist aus dem Diff ersichtlich. Das “Warum” ist dokumentiert.
Referenziere Issues/Tickets:
Closes #123
Fixes #456
Refs #789
Automatische Verlinkung, Traceability.
Während Entwicklung sind “WIP” oder “Fix typo” Commits OK. Vor Merge: Clean up.
Interactive Rebase für Cleanup:
git rebase -i main
# Editor:
pick abc123 Add OAuth2 feature
squash def456 Fix typo
squash ghi789 Respond to review
reword jkl012 Update tests
# Resultat: Saubere Commits in PROder: Squash Merge beim PR-Merge (ein Commit auf
main).
.gitignore definiert, was nicht tracked wird:
# Dependencies
node_modules/
vendor/
__pycache__/
# Build Artifacts
dist/
build/
*.pyc
*.o
*.class
# IDE
.vscode/
.idea/
*.swp
# OS
.DS_Store
Thumbs.db
# Environment
.env
.env.local
# Logs
*.log
logs/
# Test Coverage
coverage/
.coverage
Regel: Alles, was automatisch generiert wird, gehört nicht ins Repo.
Build Artifacts: dist/,
build/, Compiled Code → Wird in CI gebaut, nicht
committed
Dependencies: node_modules/,
vendor/ → Werden aus package.json /
requirements.txt installiert
IDE-spezifische Dateien: .vscode/,
.idea/ → Jeder Entwickler hat eigene Präferenzen
Environment-Variablen: .env → Enthalten
oft Secrets, gehören nicht ins Repo
Logs: *.log → Werden zur Laufzeit
generiert
OS-Dateien: .DS_Store,
Thumbs.db → OS-spezifisch, irrelevant für Projekt
Large Binary Files: Videos, Images (wenn nicht Teil des Produkts) → Git ist nicht für große Binaries optimiert
Source Code: Offensichtlich
Configuration Templates: .env.example
(mit Platzhaltern)
DATABASE_URL=postgresql://user:pass@localhost/db
API_KEY=your-api-key-here
Build Configuration: package.json,
Dockerfile, Makefile
CI/CD Configuration: .gitlab-ci.yml,
.github/workflows/
Documentation: README.md,
docs/, API docs
Tests: tests/, spec/
Schema/Migrations: Database migrations, Schema definitions
Git tracked keine leeren Verzeichnisse. Wenn du eine brauchst:
mkdir logs
touch logs/.gitkeep
git add logs/.gitkeep.gitkeep ist Konvention (Git ignoriert den Namen), hält
Verzeichnis.
Wenn große Binaries nötig sind (Assets, Datasets):
# Git LFS installieren
git lfs install
# Dateitypen tracken
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "data/*.csv"
# .gitattributes committed automatisch
git add .gitattributes
git commit -m "Configure Git LFS"
# Große Dateien normal adden
git add assets/huge-image.psd
git commit -m "Add design asset"Git LFS speichert große Dateien extern, Git selbst hat nur Pointer. Reduziert Repo-Größe massiv.
Versehentlich Secrets committen ist ein häufiges und schwerwiegendes Problem:
# NIEMALS SO!
API_KEY = "sk_live_a1b2c3d4e5f6"
DATABASE_PASSWORD = "supersecret123"Einmal committed, ist der Secret in der Historie – selbst wenn später gelöscht. Jeder mit Zugriff auf das Repo kann ihn finden.
Code:
import os
API_KEY = os.environ['API_KEY']
DATABASE_PASSWORD = os.environ['DATABASE_PASSWORD']Lokal: .env File (nicht committed)
API_KEY=sk_test_xyz
DATABASE_PASSWORD=localpass
Production: Environment Variables im Deployment-System (Kubernetes Secrets, AWS Parameter Store, etc.)
Für Teams: - Vault (HashiCorp): Centralized secrets management - SOPS: Encrypted secrets in Git (encrypted values are safe) - AWS Secrets Manager / Azure Key Vault: Cloud-native solutions
Git-Crypt: Transparent encryption for secret files
git-crypt init
git-crypt add-gpg-user user@example.com
echo "secrets.env filter=git-crypt diff=git-crypt" >> .gitattributes
# secrets.env wird automatisch verschlüsseltCommitte ein Template:
# .env.example
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
API_KEY=your-api-key-here
STRIPE_SECRET=sk_test_xxx
Entwickler kopieren zu .env und füllen echte Werte ein
(nicht committed).
Tools wie detect-secrets, gitleaks scannen Commits auf Secrets:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/Yelp/detect-secrets
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']Hook verhindert Commit, wenn Secret detected.
Sofort handeln: 1. Secret rotieren:
Alten Secret invalidieren, neuen generieren 2. Historie
säubern: git filter-branch oder BFG Repo-Cleaner
3. Force-Push: Neue Historie pushen (Team warnen!)
# BFG Repo-Cleaner (einfacher als filter-branch)
bfg --replace-text passwords.txt repo.git
cd repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --forceAber: Der Secret war bereits im Repo. Wenn gepusht, assume compromised. Rotation ist Pflicht.
Konsistente Namen erleichtern Orientierung:
feature/user-authentication
feature/JIRA-123-payment-integration
bugfix/login-error
bugfix/JIRA-456-cart-calculation
hotfix/critical-security-patch
release/v2.1.0
experiment/new-architecture
Präfixe ermöglichen Branch-Policies (z.B. nur hotfix/*
darf direkt in main).
Manche Teams nutzen Präfixe für Commit-Messages:
[AUTH] Add OAuth2 authentication
[API] Refactor endpoint error handling
[DB] Optimize user query performance
[UI] Update login page design
Hilft, Scope schnell zu erkennen (besonders bei Monorepos mit vielen Komponenten).
Regelmäßige Team-Syncs zu Git-Status: - Welche Branches sind long-running? - Welche PRs brauchen dringende Reviews? - Gibt es Blocker? - Planen wir große Merges?
Verhindert Überraschungen und Koordinationsprobleme.
Wenn große Feature-Branches parallel entwickelt werden: 1.
Wöchentlich main in Feature-Branches mergen 2. Bei
Konflikten: Sofort lösen, nicht aufstauen 3. Kommunikation: “Ich ändere
Modul X diese Woche”
Zwei Entwickler, ein Computer. Git-Implikationen:
Co-Authored Commits:
git commit -m "Add OAuth2 authentication
Co-authored-by: Jane Doe <jane@example.com>"GitHub/GitLab zeigen beide als Autoren.
Branch-Übergabe:
# Person A arbeitet
git push origin feature/auth
# Person B übernimmt
git checkout feature/auth
git pull origin feature/authMehrere Entwickler, ein Computer (oder Screen-Sharing). Ähnliche Patterns:
Multiple Co-Authors:
git commit -m "Refactor authentication module
Co-authored-by: Jane <jane@example.com>
Co-authored-by: Bob <bob@example.com>
Co-authored-by: Alice <alice@example.com>"Frequent Commits: In Mob-Sessions häufiger committen (jede 10-15 Min), um Arbeit zu sichern und bei Rotation zu synchronisieren.
Wir haben Trunk-Based Development (TBD) in Kapitel 1400 theoretisch behandelt. Praktische Umsetzung:
# Feature-Branch erstellen (von main)
git checkout -b feature/quick-fix
# Arbeiten (maximal 1-2 Tage)
git commit -m "feat: add validation"
git push origin feature/quick-fix
# PR öffnen, Review, Merge
# Branch löschen
# Zeit seit Erstellung: < 48hRegel: Wenn ein Feature länger dauert, teile es in kleinere Inkremente. Nutze Feature Flags.
// Code wird deployed, aber disabled
if (featureFlags.newCheckout) {
return <NewCheckoutFlow />
} else {
return <OldCheckoutFlow />
}Prozess: 1. Code für neues Feature wird schrittweise
in main gemerged (hinter Flag) 2. Feature entwickelt sich
über mehrere Merges (jeweils kleine PRs) 3. Wenn fertig: Flag aktivieren
(gradual rollout möglich) 4. Später: Flag und alten Code entfernen
Vorteil: Keine lange Branches, keine riesigen Merges, kontinuierliche Integration.
“Integrate mindestens täglich” ist nicht Empfehlung, sondern
Definition von CI. In TBD: - Merge in main mindestens
einmal pro Tag pro Entwickler - main ist immer deploybar -
CI/CD läuft bei jedem Commit
Erfordert Disziplin und robuste Tests, aber eliminiert Integration Hell.
Wenn mehrere Projekte in einem Repo:
monorepo/
├── services/
│ ├── api/
│ ├── worker/
│ └── admin/
├── libraries/
│ ├── shared-ui/
│ └── utils/
├── tools/
│ ├── build/
│ └── deploy/
└── docs/
/services/api/ @backend-team
/services/worker/ @backend-team
/services/admin/ @frontend-team
/libraries/shared-ui/ @frontend-team
/libraries/utils/ @platform-team
Entwickler können nur Teile des Repos auschecken:
git clone --filter=blob:none --no-checkout repo
cd repo
git sparse-checkout init --cone
git sparse-checkout set services/api libraries/utils
git checkout mainNur services/api und libraries/utils werden
ausgecheckt. Hilfreich bei riesigen Monorepos.
Git ist ein technisches Tool, aber erfolgreiche Nutzung in Teams erfordert soziale und organisatorische Praktiken:
Pull Requests sind Kommunikationswerkzeuge, nicht nur Merge-Mechanismen. Sie ermöglichen Review, Diskussion, Qualitätskontrolle.
Branch Protection schützt kritische Branches vor versehentlichen oder unbedachten Änderungen. Sie sind Guardrails, nicht Hindernisse.
Commit-Hygiene macht Historie lesbar und nützlich. Gute Commits sind Dokumentation.
Repository-Hygiene hält Repos sauber, schlank,
sicher. .gitignore, keine Secrets, keine Artifacts.
Security ist nicht optional. Secrets Management, Pre-Commit Hooks, schnelle Reaktion auf Leaks.
Team-Kommunikation verhindert Konflikte und Koordinationsprobleme. Syncs, Naming-Conventions, transparente Arbeit.
Automatisierung entlastet Menschen von repetitiven Checks. CI/CD, Linting, Security Scans laufen automatisch.
Diese Praktiken sind nicht Bürokratie. Sie sind das Fundament skalierbarer, qualitativ hochwertiger Softwareentwicklung. Ein Team, das diese Praktiken internalisiert, arbeitet schneller, produziert besseren Code, und hat weniger Stress.
Git ist mächtig. Mit den richtigen Praktiken wird es von komplex zu intuitiv, von hinderlich zu unterstützend. Master nicht nur die Technik – master auch die Zusammenarbeit. Beides zusammen macht erfolgreiche Softwareentwicklung aus.