Git Grundlagen: commit, branch und merge
1. Überblick
Git ist ein Versionskontrollsystem, das in fast jedem Projekt verwendet wird, aber viele Anfänger kennen nur ein paar Befehle und verstehen die dahinterliegende Logik nicht. Dieser Artikel behandelt drei Kernkonzepte: commit (einen Snapshot speichern), branch (unabhängigen Arbeitszweig) und merge (Branches zusammenführen). Nach dem Lesen wirst du selbstbewusst neue Branches erstellen, Commits richtig durchführen, Konflikte beim Merge lösen und wissen, wann du merge statt rebase verwenden solltest.
2. Commit – einen Snapshot speichern
2.1. Was ist ein Commit?
Jeder Commit ist eine “Momentaufnahme” des gesamten Dateizustands zu einem bestimmten Zeitpunkt, zusammen mit einem Zeiger auf den übergeordneten Commit. Git speichert nicht Diffs, sondern Snapshots — das macht Branching und Merging schnell und effizient.
# Initialisiere das Repo und führe den ersten Commit durch
git init order-service
cd order-service
echo "# Order Service" > README.md
git add README.md
git commit -m "docs: README für Order Service hinzufügen"
Erklärung:
git initerstellt ein neues Repository und erzeugt ein verstecktes Verzeichnis.git.git add README.mdfügt die Datei zur staging area (Warteschlange für Commits) hinzu.git commit -m "..."erstellt einen Snapshot und fügt eine Nachricht hinzu, die die Änderungen beschreibt.
Überprüfe den Verlauf:
git log --oneline
a1b2c3d docs: README für Order Service hinzufügen
2.2. Gute Commit-Nachrichten schreiben
Ein Commit sollte die Fragen beantworten: “Was tut diese Änderung und warum?” Gängige Konvention (Conventional Commits):
feat: API zur Bestellerstellung hinzufügen
fix: Fehler beim falschen Berechnen der Gesamtsumme beim Coupon-Angebot behoben
refactor: OrderValidator aus OrderService extrahiert
Hinweis: Vermeide vage Commit-Nachrichten wie
"update"oder"fix bug". Ein paar Monate später, wenn du aufgit logschaust, wirst du nicht mehr wissen, was dieser Commit repariert hat.
2.3. Teilweise Commits mit git add -p
Wenn eine Datei mehrere unabhängige Änderungen enthält, committen sie nicht alle auf einmal.
git add -p src/main/java/com/nonladev/OrderService.java
Git wird dich fragen, welche “Hunks” (Änderungsbereiche) du zur Staging Area hinzufügen möchtest, was es ermöglicht, sie in kleine, leicht zu überprüfende und leicht rückgängig zu machende Commits zu unterteilen.
3. Branch – unabhängig auf mehreren Branches arbeiten
3.1. Branches erstellen und wechseln
Ein Branch ist nur ein leichter Zeiger auf einen bestimmten Commit. Das Erstellen eines neuen Branches kopiert nicht den gesamten Code, sondern erstellt einfach einen zusätzlichen Zeiger.
# Neuen Branch erstellen und dorthin wechseln
git branch feature/add-payment
git checkout feature/add-payment
# Oder kombiniere die beiden obigen Befehle (Git ≥ 2.23)
git switch -c feature/add-payment
Erklärung:
git branch <name>erstellt einen Branch, aber du bleibst im alten Branch.git checkout <name>odergit switch <name>verschiebtHEADzu diesem Branch.-cingit switchist ein Shortcut zum gleichzeitigen Erstellen und Wechseln.
3.2. Branches auflisten
git branch -v
* feature/add-payment d4e5f6a PaymentService Skeleton hinzufügen
main a1b2c3d docs: README für Order Service hinzufügen
Das * zeigt den aktuellen Branch (wo HEAD gerade steht).
3.3. Branches mit Konvention benennen
Klar benannte Branches helfen dem gesamten Team zu verstehen, was jeder Branch tut:
feature/add-payment # neue Funktionalität hinzufügen
fix/order-total-wrong # Bug reparieren
refactor/extract-validator
hotfix/prod-null-pointer
3.4. Branches nach Fertigstellung löschen
# Lösche einen local Branch, der bereits gemergt wurde
git branch -d feature/add-payment
# Lösche einen Branch, der nicht gemergt wurde (Force-Löschung, sei vorsichtig)
git branch -D feature/add-payment
-d (Kleinbuchstaben) ist sicherer, da Git dich warnt, wenn der Branch noch nirgendwo gemergt wurde.
4. Merge – Branches zusammenführen
4.1. Fast-forward Merge
Wenn main keine neuen Commits seit dem Abzweigen des Branches hat, muss Git nur den main-Zeiger auf den letzten Commit des Feature-Branches verschieben — das heißt Fast-forward.
git switch main
git merge feature/add-payment
Updating a1b2c3d..d4e5f6a
Fast-forward
PaymentService.java | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
Es wird kein neuer Merge-Commit erstellt; die Historie bleibt eine gerade Linie.
4.2. Merge Commit (3-Way Merge)
Wenn beide Branches unabhängige Commits seit dem Verzweigungspunkt haben, erstellt Git einen Merge-Commit mit zwei Eltern, der den Inhalt beider Branches zusammenführt.
git switch main
git commit -am "fix: Validierung der Artikelmenge korrigiert" # main hat einen neuen Commit
git switch feature/add-payment
git switch main
git merge feature/add-payment
Merge made by the 'recursive' strategy.
PaymentService.java | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
Git erstellt automatisch einen Merge-Commit mit der Standard-Nachricht Merge branch 'feature/add-payment'.
4.3. Konflikte beim Merge lösen
Konflikte treten auf, wenn zwei Branches denselben Codebereich bearbeitet haben. Beispiel in der Klasse OrderService:
// In main
public class OrderService {
public BigDecimal calculateTotal(Order order) {
return order.getSubtotal().multiply(BigDecimal.valueOf(1.1)); // 10% Steuern hinzufügen
}
}
// In feature/add-payment
public class OrderService {
public BigDecimal calculateTotal(Order order) {
return order.getSubtotal().subtract(order.getDiscount()); // Rabatt abziehen
}
}
Beim Merge kann Git nicht selbst entscheiden, welche Zeile behalten werden soll, daher erscheinen Marker in der Datei:
public class OrderService {
public BigDecimal calculateTotal(Order order) {
<<<<<<< HEAD
return order.getSubtotal().multiply(BigDecimal.valueOf(1.1));
=======
return order.getSubtotal().subtract(order.getDiscount());
>>>>>>> feature/add-payment
}
}
Du musst die Logik manuell zusammenführen (z.B. zuerst Rabatt abziehen, dann Steuern hinzufügen), die Marker <<<<<<<, =======, >>>>>>> löschen und dann:
git add OrderService.java
git commit
Hinweis: Nach dem Resolve eines Konflikts brauchst du kein
-mfür den Commit; Git öffnet einen Editor mit einer Standard-Nachricht, die deutlich macht, dass dies ein Merge-Commit ist — behalte diese Nachricht, es sei denn, du möchtest weitere Notizen hinzufügen.
4.4. Merge vs Rebase
| Kriterium | merge |
rebase |
|---|---|---|
| Historie | Bleibt unverändert, hat Merge-Commit | Schreibt Historie neu, flacher Branch |
| Sicherheit | Ändert alte Commits nicht | Ändert alte Commits (andere Hashes) |
| Verwendung | Branch wird öffentlich geteilt (main, develop) |
Persönlicher Branch, möchte saubere Historie vor PR |
# Rebase den feature Branch auf das neueste main
git switch feature/add-payment
git rebase main
Hinweis: Rebaste keinen Branch, der bereits gepusht wurde und von anderen gepullt wird — du wirst ihre Historie durcheinanderbringen und verursachst zahlreiche Konflikte, wenn sie wieder pullen.
5. Ein Merge-Szenario mit JUnit testen
Obwohl Git kein Java-Code ist, kannst du durchaus Tests schreiben, um zu bestätigen, dass die Geschäftslogik nach dem Merge zweier Branches nicht kaputt ist — genau deshalb führt CI immer Tests auf Merge-Commits aus. Beispiel OrderService nach Resolve des Konflikts aus Abschnitt 4.3:
public class OrderService {
public BigDecimal calculateTotal(Order order) {
BigDecimal afterDiscount = order.getSubtotal().subtract(order.getDiscount());
return afterDiscount.multiply(BigDecimal.valueOf(1.1));
}
}
class OrderServiceTest {
private final OrderService orderService = new OrderService();
@Test
void shouldApplyDiscountThenTax() {
Order order = new Order(
BigDecimal.valueOf(100), // Subtotal
BigDecimal.valueOf(10) // Rabatt
);
BigDecimal total = orderService.calculateTotal(order);
assertThat(total).isEqualByComparingTo(BigDecimal.valueOf(99.0));
}
}
Erklärung: Subtotal 100 minus Rabatt 10 ergibt 90, multipliziert mit 10% Steuern ergibt 99. Dieser Test ist die “Sicherheitsnetzwerk”, das sicherstellt, dass das Merge der beiden Logiken (Steuern und Rabatt) sich nicht gegenseitig aufheben.
✓ shouldApplyDiscountThenTax()
6. Empfohlener Workflow für kleine Teams
- Erstelle immer einen neuen Branch aus
mainfür jeden Task:git switch -c feature/xxx. - Kleine, häufige Commits mit klaren Nachrichten.
- Bevor du einen Pull Request erstellst, aktualisiere deinen Branch mit dem neuesten
main:git switch feature/add-payment git fetch origin git merge origin/main - Löse Konflikte (falls vorhanden) lokal und führe die Tests erneut aus.
- Push und erstelle einen PR, lasse CI Tests auf dem Merge-Commit ausführen, bevor du auf
mainmergst.
Hinweis: In einem Team sollte man nicht direkt auf
maincommitten — immer über Branch + PR, um Code-Review und CI-Checks zu ermöglichen.
7. Häufige Fehler
- Vergessen,
git addvor dem Commit auszuführen: Git meldetnothing to commit, working tree clean, obwohl du die Datei bearbeitet hast — weil die Datei noch nicht in der Staging Area ist. - Versehentliche Zusammenführung des falschen Branches: Immer
git statusundgit branchüberprüfen, um sicherzustellen, dass du im richtigen Branch bist, bevor dumergeausführst. - Konfliktmarker bleiben im Code zurück: Wenn du
<<<<<<<vergisst zu löschen, wird der Code nicht kompiliert — baue und teste immer sofort nach dem Resolve eines Konflikts. - Force Push auf einen geteilten Branch:
git push --forcenach einem Rebase eines geteilten Branches löscht die Historie deiner Teamkollegen. Verwende nur--force-with-leaseund nur auf persönlichen Branches.
8. Fazit
Commit, Branch und Merge sind drei tägliche Git-Operationen. Eine sichere Beherrschung von Commits hält die Code-Historie klar, Branches ermöglichen parallele Arbeit ohne gegenseitige Beeinträchtigung, und das Verständnis von Merging (und Konfliktlösung) gibt dir Sicherheit beim Zusammenführen von Code von mehreren Personen. Übe dich in kleinen Commits, benenne Branches nach Konvention und führe immer Tests durch, bevor du auf main mergst.