Merge-Konflikte sind keine Fehler – sie sind Git’s ehrliche Antwort auf eine unlösbare Situation. Wenn zwei Entwickler dieselben Zeilen unterschiedlich ändern, kann Git nicht wissen, welche Version korrekt ist. Die Entscheidung muss ein Mensch treffen. Das ist nicht frustrierend, sondern genau richtig: Git schützt uns vor automatischen, potenziell falschen Entscheidungen.
Dieses Kapitel erklärt die technische Mechanik von Konflikten, die
verschiedenen Strategien zur Konfliktlösung, und fortgeschrittene
Features wie rerere, das sich Konfliktlösungen merkt und
automatisch wiederanwendet.
Wir haben bereits den Three-Way-Merge kennengelernt: Git vergleicht drei Versionen: 1. Base: Der gemeinsame Ancestor beider Branches 2. Ours: Der aktuelle Branch (wo wir gerade sind) 3. Theirs: Der Branch, den wir mergen
Ours (main) Theirs (feature)
\ /
\ /
Base (common ancestor)
Git analysiert Änderungen: - Base → Ours: Was haben wir geändert? - Base → Theirs: Was haben sie geändert?
Wenn Änderungen nicht überlappen, kombiniert Git sie automatisch. Aber wenn beide Seiten dieselben Zeilen ändern, entsteht ein Konflikt.
Base:
def calculate(a, b):
return a + bOurs (main): Docstring hinzufügen
def calculate(a, b):
"""Add two numbers."""
return a + bTheirs (feature): Typehints hinzufügen
def calculate(a: int, b: int):
return a + bGit kann diese Änderungen kombinieren – verschiedene Bereiche der Funktion:
def calculate(a: int, b: int):
"""Add two numbers."""
return a + bKein Konflikt, automatischer Merge.
Base:
def calculate(a, b):
return a + bOurs (main): Multiplikation
def calculate(a, b):
return a * bTheirs (feature): Division
def calculate(a, b):
return a / bGit kann nicht entscheiden: * oder /? Beide
Seiten haben dieselbe Zeile unterschiedlich geändert.
Konflikt.
Wir haben im Staging-Area-Kapitel gesehen, dass der Index bei Konflikten drei Versionen derselben Datei speichert:
$ git merge feature
Auto-merging calculate.py
CONFLICT (content): Merge conflict in calculate.py
Automatic merge failed; fix conflicts and then commit the result.
$ git ls-files -s
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 1 calculate.py
100644 a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 2 calculate.py
100644 7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6 3 calculate.pyDrei Einträge: - Stage 1: Base (common ancestor) - Stage 2: Ours (current branch) - Stage 3: Theirs (merging branch)
Diese drei Versionen sind vollständig im Index gespeichert. Wir können sie einzeln extrahieren:
# Base-Version anzeigen
git show :1:calculate.py
# Ours-Version anzeigen
git show :2:calculate.py
# Theirs-Version anzeigen
git show :3:calculate.pyDas Working Directory enthält die Datei mit Konfliktmarkern – eine vierte Version, die alle drei kombiniert zeigt.
Die Datei im Working Directory sieht so aus:
def calculate(a, b):
<<<<<<< HEAD
return a * b
=======
return a / b
>>>>>>> featureDie Marker: - <<<<<<< HEAD: Beginn
der “Ours”-Version (aktueller Branch) - =======: Trenner
zwischen Ours und Theirs -
>>>>>>> feature: Ende der
“Theirs”-Version (gemergter Branch)
Zwischen <<<<<<< und
=======: Was wir haben (Ours). Zwischen
======= und >>>>>>>: Was
sie haben (Theirs).
Mit git config merge.conflictstyle diff3 zeigt Git auch
die Base-Version:
def calculate(a, b):
<<<<<<< HEAD
return a * b
||||||| base
return a + b
=======
return a / b
>>>>>>> featureJetzt sehen wir: - Ours: a * b (wir
haben + zu * geändert) -
Base: a + b (Original) -
Theirs: a / b (sie haben + zu
/ geändert)
Diese Darstellung hilft zu verstehen, was jede Seite geändert hat – nicht nur, was unterschiedlich ist.
Der klassische Ansatz:
# 1. Konflikt ist aufgetreten
$ git merge feature
CONFLICT (content): Merge conflict in calculate.py
# 2. Dateien mit Konflikten identifizieren
$ git status
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: calculate.py
# 3. Datei editieren, Konfliktmarker entfernen, Lösung wählen
$ nano calculate.py
# Entscheide: Multiplikation oder Division? Oder beides?
# Beispiel-Lösung: Neuer Parameter für Operation
def calculate(a, b, operation='add'):
if operation == 'multiply':
return a * b
elif operation == 'divide':
return a / b
else:
return a + b
# 4. Als resolved markieren
$ git add calculate.py
# 5. Index prüfen (Stage 0 = resolved)
$ git ls-files -s
100644 9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0 0 calculate.py
# 6. Merge abschließen
$ git commit -m "Merge feature: Resolve calculate() conflict
Combined multiplication and division into configurable operation.
Default behavior maintained as addition."Die Konfliktmarker sind nur im Working Directory.
Sie sind nicht Teil der Git-Historie. Sobald git add
ausgeführt wird, ist der Konflikt resolved.
Manchmal wissen wir im Voraus, welche Seite gewinnen soll. Git bietet Strategien, um systematisch eine Seite zu bevorzugen.
Wichtig: Es gibt zwei verschiedene Mechanismen mit ähnlichen Namen:
# Merge, aber nehme bei ALLEN Konflikten "ours"
git merge -s ours feature
# Warnung: Dies ignoriert feature komplett!
# Erstellt Merge-Commit, aber nimmt keinen Code von feature-s ours ist eine Strategie-Option. Sie
erstellt einen Merge-Commit mit zwei Parents, ignoriert aber alle
Änderungen von feature. Nützlich, um Historie zu vereinen
ohne Code zu integrieren (z.B. “wir haben feature verworfen, aber wollen
Historie behalten”).
Es gibt keine -s theirs Strategie.
Warum? Weil das identisch zu git merge -s ours auf dem
anderen Branch wäre – man würde einfach zu feature
checkouten und von dort mergen.
# Merge, bei Konflikten bevorzuge "ours"
git merge -X ours feature
# Merge, bei Konflikten bevorzuge "theirs"
git merge -X theirs feature-X ours und -X theirs sind
Merge-Optionen (recursive strategy options). Sie führen
den normalen Three-Way-Merge durch, aber bei Konflikten wird automatisch
eine Seite gewählt.
Szenario: Feature-Branch, der lange divergiert ist.
Wir wissen, dass main bei Konflikten korrekt ist.
# Von main aus: Nehme bei Konflikten immer main-Version
git merge -X ours feature
# Oder umgekehrt: Von feature aus, nehme main-Version
git checkout feature
git merge -X theirs mainBeispiel-Konflikt:
Ohne -X:
<<<<<<< HEAD
timeout = 30
=======
timeout = 60
>>>>>>> featureMit git merge -X ours feature:
timeout = 30Mit git merge -X theirs feature:
timeout = 60Keine Konfliktmarker, automatisch resolved.
-X ours: - Backporting Fixes zu Maintenance-Branches (main gewinnt bei Konflikten) - Reverting problematischer Changes (alte Version gewinnt) - “We know our version is correct”
-X theirs: - Accepting upstream changes (z.B. merging vendor branch) - “Their version is definitiv korrekt” - Bulk-Updates von Dependencies
Vorsicht: Diese Optionen sind stumpf. Sie lösen alle Konflikte automatisch. Review ist essentiell, um sicherzustellen, dass keine wichtigen Änderungen verloren gingen.
Während eines Konflikts kann man einzelne Dateien komplett aus einer Seite nehmen:
# Konflikt existiert
$ git status
both modified: config.yaml
both modified: database.py
# Für config.yaml: Nehme ours (aktueller Branch)
$ git checkout --ours config.yaml
$ git add config.yaml
# Für database.py: Nehme theirs (gemergter Branch)
$ git checkout --theirs database.py
$ git add database.py
# Merge abschließen
$ git commitDas ist praktisch, wenn einzelne Dateien klar zu einer Seite gehören, andere aber manuelle Resolution brauchen.
Rerere (Reuse Recorded Resolution) ist ein mächtiges, oft übersehenes Feature. Git merkt sich, wie Konflikte gelöst wurden, und wendet dieselbe Lösung automatisch an, wenn der Konflikt erneut auftritt.
Stell dir vor: 1. Feature-Branch hat Konflikt mit main
2. Du löst den Konflikt manuell 3. Du rebasest den Feature-Branch 4.
Derselbe Konflikt tritt wieder auf 5. Du musst ihn
erneut manuell lösen
Bei langlebigen Feature-Branches und mehrfachen Rebases kann derselbe Konflikt dutzende Male auftreten. Frustrierend.
# Global aktivieren
git config --global rerere.enabled true
# Für aktuelles Repository
git config rerere.enabled trueBeim ersten Auftreten eines Konflikts: 1. Git speichert die
Konflikt-Situation (Ours, Theirs) in .git/rr-cache/ 2. Du
löst den Konflikt manuell 3. Git speichert deine Lösung
Bei erneutem Auftreten desselben Konflikts: 1. Git erkennt: “Dieser
Konflikt ist bekannt” 2. Git wendet die gespeicherte Lösung automatisch
an 3. Du musst nur noch git add ausführen
# Rerere aktivieren
$ git config rerere.enabled true
# Erster Merge mit Konflikt
$ git merge feature
CONFLICT (content): Merge conflict in calculate.py
Recorded preimage for 'calculate.py'
# Konflikt manuell lösen
$ nano calculate.py
$ git add calculate.py
$ git commit -m "Merge feature"
Recorded resolution for 'calculate.py'.
# Später: Rebase, derselbe Konflikt
$ git rebase main
CONFLICT (content): Merge conflict in calculate.py
Resolved 'calculate.py' using previous resolution.
$ git status
Unmerged paths:
both modified: calculate.py
$ cat calculate.py
# Die Datei enthält bereits die Lösung von vorher!
# Keine Konfliktmarker, bereits resolved
$ git add calculate.py
$ git rebase --continueGit hat die Lösung erinnert und automatisch angewendet. Enorme Zeitersparnis bei iterativen Rebases.
# Alle gespeicherten Resolutions anzeigen
$ git rerere status
# Detaillierte Informationen
$ git rerere diff
# Rerere-Cache leeren (alle Resolutions vergessen)
$ git rerere gc
# Spezifischen Konflikt vergessen
$ git rerere forget <file>Lange Feature-Branches mit regelmäßigem Rebase: Rerere macht wiederholte Rebases schmerzlos.
Maintenance-Branches: Backporting Fixes zu mehreren Release-Branches – derselbe Konflikt mehrfach.
Cherry-Picking: Ähnliche Commits auf verschiedene Branches cherry-picken.
Large Refactorings: Große Umstrukturierungen, die in kleinen Schritten integriert werden.
Team-Workflows: Mehrere Entwickler treffen auf
dieselben Konflikte, rerere teilt Lösungen (via
.git/rr-cache/ im Repo committen, falls gewünscht).
Aktiviere es global:
git config --global rerere.enabled true – es gibt keinen
Nachteil.
Review trotzdem: Rerere wendet die alte Lösung an. Wenn sich der Kontext geändert hat, könnte die alte Lösung falsch sein. Immer kurz prüfen.
Rerere-Cache teilen (optional):
.git/rr-cache/ kann ins Repository committet werden, um
Resolutions mit dem Team zu teilen. Kontrovers, aber in manchen Teams
nützlich.
Git integriert mit vielen Merge-Tools für visuelle Konfliktlösung:
# Verfügbare Tools anzeigen
$ git mergetool --tool-help
# Tool konfigurieren
$ git config --global merge.tool vimdiff
# Oder: meld, kdiff3, p4merge, beyondcompare, etc.
# No backup files after resolve
$ git config --global mergetool.keepBackup false# Konflikt aufgetreten
$ git merge feature
CONFLICT (content): Merge conflict in calculate.py
# Merge-Tool starten
$ git mergetool
# Tool öffnet sich, zeigt drei Panels:
# - Local (Ours)
# - Base (Common Ancestor)
# - Remote (Theirs)
# Plus: Result-Panel zum EditierenModerne Tools wie VSCode, IntelliJ, Sublime Merge haben integrierte Merge-Views mit 3-Way-Diff. Oft intuitiver als command-line editing.
VSCode: Excellent built-in merge editor, zeigt Ours/Base/Theirs side-by-side
Meld: Open-source, drei-Panel-View, syntax-highlighting
Beyond Compare: Commercial, sehr mächtig, unterstützt complex merges
P4Merge: Kostenlos, gut für große Dateien
Nicht alle Konflikte sind Content-Konflikte. Git erkennt verschiedene Arten:
Beide Seiten ändern dieselben Zeilen:
CONFLICT (content): Merge conflict in file.txt
Standard-Fall, den wir bisher behandelt haben.
Eine Seite ändert eine Datei, die andere löscht sie:
CONFLICT (modify/delete): file.txt deleted in feature and modified in HEAD.
Lösung:
# Behalten (Ours)
git add file.txt
# Löschen (Theirs)
git rm file.txtBeide Seiten erstellen eine neue Datei mit demselben Namen, aber unterschiedlichem Inhalt:
CONFLICT (add/add): Merge conflict in newfile.txt
Git behandelt dies wie Content-Konflikt, beide Versionen werden in Konfliktmarkern dargestellt.
Beide Seiten benennen dieselbe Datei um, aber zu unterschiedlichen Namen:
CONFLICT (rename/rename): file.txt renamed to fileA.txt in HEAD and to fileB.txt in feature.
Lösung:
# Eine Version behalten
git add fileA.txt
git rm fileB.txt
# Oder beide umbenennen
git mv fileA.txt file_main.txt
git mv fileB.txt file_feature.txt
git add file_main.txt file_feature.txtEine Seite erstellt ein Verzeichnis, die andere eine Datei mit demselben Namen:
CONFLICT (directory/file): There is a directory with name 'config' in HEAD.
Selten, aber kompliziert. Meist: Eine Seite wählen oder umstrukturieren.
# Feature-Branch arbeitet lange
# Regelmäßig main pullen und mergen/rebasen
# Option 1: Merge main in feature (behält feature-Historie)
git checkout feature
git merge main
# Option 2: Rebase feature auf main (lineare Historie)
git checkout feature
git rebase mainKleinere, häufigere Integrationen → kleinere, einfachere Konflikte.
Ein Commit, der 20 Dateien ändert, führt zu großen Konflikten. Besser: 20 Commits, jeder fokussiert.
“Ich refactore Modul X diese Woche” → Andere vermeiden große Änderungen in Modul X → Weniger Konflikte.
Statt Feature-Branch monatelang zu entwickeln: 1. Feature Flag im
Code: if (featureEnabled) { newCode() } else { oldCode() }
2. Häufig in main mergen (Code ist da, aber disabled) 3. Keine lange
Divergenz, keine großen Konflikte
Manche Dateien sollten nicht manuell gemerged werden:
package-lock.json, Cargo.lock: Bei Konflikt: Regenerate.
# Bei Konflikt in package-lock.json
git checkout --theirs package-lock.json
npm install # Regeneriert Lock-File
git add package-lock.jsonGenerated Code: Regenerieren, nicht mergen.
Binary Files: Keine Konfliktlösung möglich. Eine Version wählen.
# Konflikt aufgetreten, zu komplex
$ git merge --abort
# Zurück zum Zustand vor dem Merge$ git rebase --abort
# Zurück zum Zustand vor dem Rebase# Konflikt-Resolution war falsch
# Zurück zu Konflikt-Zustand
$ git checkout -m <file>
# Die Datei hat wieder KonfliktmarkerManchmal ist der automatisch erkannte Common Ancestor nicht ideal. Man kann einen Custom Base angeben:
git merge-base --merge-base <base> HEAD feature | xargs git merge-recursiveNützlich bei komplexen Histories mit Criss-Cross-Merges.
# Drei Versionen extrahieren
git show :1:file.txt > file.base
git show :2:file.txt > file.ours
git show :3:file.txt > file.theirs
# Mit externem Tool mergen
merge-tool file.base file.ours file.theirs -o file.result
# Ergebnis zurück
cp file.result file.txt
git add file.txtNützlich für spezielle Dateiformate, die Git nicht versteht.
Für systematische Konflikte (z.B. immer bestimmte Dateien aus theirs nehmen):
#!/bin/bash
# resolve-conflicts.sh
# Package-Locks immer regenerieren
git checkout --theirs package-lock.json
npm install
git add package-lock.json
# Config immer aus ours
git checkout --ours config/production.yaml
git add config/production.yaml
# Auto-commit wenn alle resolved
if ! git ls-files -u | grep -q .; then
git commit -m "Merge: Auto-resolved standard conflicts"
fiPrevention is better than cure: Regelmäßig integrieren, kleinere Commits, Feature Flags.
Understand the conflict: Nutze diff3
style, schaue alle drei Versionen an.
Use tools: Merge-Tools sind oft intuitiver als Text-Editor mit Conflict-Markers.
Enable rerere: Spart enorm Zeit bei wiederholten Konflikten.
Know your strategies: -X ours,
-X theirs, --ours, --theirs –
wisse, wann welche.
Review resolutions: Automatische Lösungen (rerere, -X) können falsch sein im neuen Kontext.
Communicate: Team-Koordination verhindert viele Konflikte.
Test after resolution: Conflicts-resolved ≠ code works. Immer testen nach Konfliktlösung.
Document complex resolutions: Wenn eine Konfliktlösung nicht-trivial war, erkläre sie in der Commit-Message.
Merge-Konflikte sind kein Zeichen schlechter Entwicklung. Sie sind ein Zeichen paralleler Arbeit – und das ist gut. Git’s Job ist nicht, alle Konflikte zu lösen, sondern uns ehrlich zu sagen, wo menschliche Entscheidung nötig ist.
Die Tools und Strategien in diesem Kapitel machen Konfliktlösung von frustrierend zu handhabbar: - Three-Way-Merge zeigt uns alle drei Perspektiven - Merge-Strategien automatisieren systematische Entscheidungen - Rerere eliminiert wiederholte Arbeit - Merge-Tools machen komplexe Konflikte visuell handhabbar - Proaktive Strategien verhindern viele Konflikte von vornherein
Master diese Techniken, und Konflikte werden von Hindernissen zu Routineaufgaben. Sie sind Teil des Workflows, nicht Blocker. Und mit Erfahrung wird klar: Ein gut gelöster Konflikt führt oft zu besserem Code als beide Original-Versionen allein.
Git respektiert deine Intelligenz. Es löst nicht jeden Konflikt automatisch, weil es nicht kann. Aber es gibt dir alle Tools, um informierte, korrekte Entscheidungen zu treffen. Nutze sie.