Merge
Pitmaster führt Branch-Merges mit einem Drei-Wege-Merge aus, wie git merge. Es erkennt Fast-Forward-Möglichkeiten, berechnet Merge-Basen, führt Merges auf Tree- und Inhaltsebene durch und erzeugt bei Dateikonflikten standardmäßige Konfliktmarkierungen.
Einfacher Merge
$result = $repo->merge('feature/login');
if ($result->clean) {
echo "Merged successfully: {$result->commitId->hex}\n";
} else {
echo "Conflicts:\n";
foreach ($result->conflictPaths as $path) {
echo " {$path}\n";
}
}
MergeResult
Die Klasse MergeResult ist ein schreibgeschütztes Wertobjekt:
use Pitmaster\Merge\MergeResult;
$result->clean; // bool: true if merge completed without conflicts
$result->commitId; // ?ObjectId: the merge commit (null if conflicts)
$result->conflictPaths; // array: list of file paths with conflicts
$result->mergedContents; // array: path => content with conflict markers (if conflicts)
Wenn der Merge wegen Konflikten angehalten wird, wird der Konfliktstatus in den Index und den Arbeitsbaum geschrieben. Löse die Konflikte in den Dateien, add() sie und rufe $repo->mergeContinue() auf, oder rufe $repo->mergeAbort() auf, um den Zustand vor dem Merge wiederherzustellen.
Übernimm lokale Änderungen vor dem Merge in einen Commit oder stash sie: Der Merge erstellt den Arbeitsbaum anhand des Ergebnisses neu und überschreibt nicht übernommene Änderungen an getrackten Dateien (#2).
Fast-Forward-Merges
Wenn der aktuelle HEAD ein Vorfahr des Ziel-Branchs ist, führt Pitmaster einen Fast-Forward-Merge aus: Es verschiebt HEAD einfach zum Ziel-Commit, ohne einen Merge-Commit zu erstellen.
Before: After fast-forward:
A---B (main) A---B---C---D (main)
\
C---D (feature)
$result = $repo->merge('feature');
// $result->clean === true
// $result->commitId points to D
// No merge commit created
Der umgekehrte Fall wird noch nicht erkannt: Beim Mergen eines Branchs, den HEAD bereits enthält, wird ein neuer Merge-Commit mit unverändertem Tree erstellt, statt „Bereits aktuell“ zu melden (#6).
Drei-Wege-Merge
Wenn Branches auseinanderlaufen, führt Pitmaster einen Drei-Wege-Merge aus:
- Merge-Basis finden (niedrigster gemeinsamer Vorfahr)
- Basis mit unserem und deren Tree auf Tree-Ebene vergleichen
- Für jede Datei, die in beiden Branches geändert wurde, einen Merge auf Inhaltsebene ausführen
- Einen Merge-Commit mit zwei Eltern erstellen
Before: After merge:
C---D (feature) C---D
/ / \
A---B---E---F (main) A---B---E---F---G (main)
Merge-Basis
Die Merge-Basis ist der jüngste gemeinsame Vorfahr zweier Commits. Pitmaster findet sie, indem es den Commit-Graphen durchläuft. Bei mehreren Merge-Basen (Criss-Cross-Verlauf) führt merge() sie zuerst zusammen und verwendet das Ergebnis als Basis.
$base = $repo->mergeBase(
$repo->resolve('main'),
$repo->resolve('feature/login'),
);
if ($base !== null) {
echo "Common ancestor: {$base->hex}\n";
}
Für den direkten Zugriff auf den MergeBase-Algorithmus:
use Pitmaster\Merge\MergeBase;
$finder = new MergeBase($repo->objectDatabase());
$base = $finder->find($commitA, $commitB);
// All merge bases (criss-cross histories can have more than one)
$bases = $finder->findAll($commitA, $commitB);
// Check ancestry
$isAncestor = $finder->isAncestor($oldCommit, $newCommit);
Branch-Merge-Prüfung
if ($repo->isBranchMerged('feature/done')) {
// All commits in feature/done are reachable from default branch
$repo->deleteBranch('feature/done');
}
// Check against a specific target
if ($repo->isBranchMerged('feature/done', 'develop')) {
// Merged into develop
}
Konfliktmarkierungen
Wenn eine Datei in beiden Branches geändert wurde und sich die Änderungen überschneiden, erzeugt Pitmaster standardmäßige Konfliktmarkierungen. merge() kennzeichnet die Seiten mit HEAD und dem Namen des zusammengeführten Branchs:
<<<<<<< HEAD
return $this->value + 1;
=======
return $this->value * 2;
>>>>>>> feature
Die Klasse ConflictMarker schließt zwei Versionen in Markierungen ein:
use Pitmaster\Merge\ConflictMarker;
$marked = ConflictMarker::mark($oursContent, $theirsContent, 'HEAD', 'feature');
// diff3 style, with the base section
$marked = ConflictMarker::mark($oursContent, $theirsContent, 'HEAD', 'feature', $baseContent, 'base');
Drei-Wege-Merge auf Inhaltsebene
Die Klasse ThreeWayMerge führt Blob-Inhalte zusammen (keine Dateien):
use Pitmaster\Merge\ThreeWayMerge;
$result = ThreeWayMerge::merge($baseContent, $oursContent, $theirsContent, 'HEAD', 'feature');
$result['content']; // merged text, with conflict markers if needed
$result['clean']; // true when nothing conflicted
$result['conflicts']; // number of conflict regions
Sie nimmt drei Strings entgegen (die Basisversion, unsere Version und deren Version), außerdem optionale Bezeichnungen und einen Konfliktstil (standardmäßig 'merge' oder 'diff3', um den Basisabschnitt einzuschließen).
Merge-Strategien
Rekursiver Merge
Die Standardstrategie ($repo->merge($branch)) verarbeitet Criss-Cross-Merge-Verläufe mit mehreren Merge-Basen. Die CLI akzeptiert dafür auch -s ort und -s recursive.
Ours-Merge
Erstellt einen Merge-Commit, der den Tree von HEAD unverändert lässt. Nützlich, um den Branch-Verlauf zu erhalten, ohne Änderungen zu übernehmen.
$result = $repo->merge('feature/old', 'ours');
Octopus-Merge
Führt zwei oder mehr Branches in einem Merge-Commit zusammen. Bei Konflikten in einem Branch gibt die Strategie ein nicht bereinigtes Ergebnis zurück, ohne den Merge-Status zu speichern oder einen Commit zu erstellen.
$result = $repo->mergeOctopus(['feature/a', 'feature/b', 'feature/c']);
Die Klassen RecursiveMerge, OursMerge und OctopusMerge in Pitmaster\Merge sind Hilfsklassen für Merges auf Inhaltsebene; Repository implementiert die oben genannten Strategien selbst.
Rerere (aufgezeichnete Konfliktlösung wiederverwenden)
Pitmaster enthält eine eigenständige Klasse Rerere, die Konfliktlösungen im Layout von gits rr-cache speichert:
use Pitmaster\Merge\Rerere;
$rerere = new Rerere($repo->gitDir());
if ($rerere->isEnabled()) { // rerere.enabled = true in .git/config
$rerere->record($conflicted, $resolved); // remember a resolution
$resolved = $rerere->resolve($conflicted); // replay it, or null
}
merge() ruft sie nicht auf: Konfliktlösungen werden nur aufgezeichnet und erneut angewendet, wenn dein Code das veranlasst. isEnabled() sucht in .git/config nach dem wörtlichen Text rerere.enabled = true und erkennt den Abschnitt [rerere], den git config rerere.enabled true schreibt, nicht (#20).
Merge-Ablauf auf Tree-Ebene
Der vollständige Merge-Prozess:
MergeBase::findAll()ermittelt die gemeinsamen Vorfahren- Die Trees der Basis, unserer Version und deren Version werden Pfad für Pfad verglichen
- Bei Dateien, die nur in einem Branch geändert wurden: Diese Änderung übernehmen
- Bei Dateien, die in beiden Branches geändert wurden:
- Bei identischen Änderungen eine der beiden übernehmen
- Bei unterschiedlichen Änderungen
ThreeWayMerge::merge()auf den Inhalt anwenden
- Wenn alle Merges konfliktfrei sind: den zusammengeführten Tree erstellen und einen Merge-Commit anlegen
- Bei Konflikten:
MergeResultmitclean = falseund den Konfliktpfaden zurückgeben
Verwendung von CLI
./vendor/bin/pitmaster merge feature/login
Ausgabe bei Erfolg:
Merge successful.
Ausgabe bei Konflikten:
CONFLICT in: src/File.php, src/Other.php