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 init erstellt ein neues Repository und erzeugt ein verstecktes Verzeichnis .git.
  • git add README.md fü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 auf git log schaust, 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> oder git switch <name> verschiebt HEAD zu diesem Branch.
  • -c in git switch ist 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 -m fü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

  1. Erstelle immer einen neuen Branch aus main für jeden Task: git switch -c feature/xxx.
  2. Kleine, häufige Commits mit klaren Nachrichten.
  3. 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
  4. Löse Konflikte (falls vorhanden) lokal und führe die Tests erneut aus.
  5. Push und erstelle einen PR, lasse CI Tests auf dem Merge-Commit ausführen, bevor du auf main mergst.

Hinweis: In einem Team sollte man nicht direkt auf main committen — immer über Branch + PR, um Code-Review und CI-Checks zu ermöglichen.

7. Häufige Fehler

  • Vergessen, git add vor dem Commit auszuführen: Git meldet nothing 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 status und git branch überprüfen, um sicherzustellen, dass du im richtigen Branch bist, bevor du merge ausfü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 --force nach einem Rebase eines geteilten Branches löscht die Historie deiner Teamkollegen. Verwende nur --force-with-lease und 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.

gitversion-controlbranchmergedevops