Mergen
Pitmaster voert branch-merges uit met een three-way merge, net als git merge. Het detecteert mogelijkheden voor fast-forward, berekent mergebasissen, voert merges op tree- en inhoudsniveau uit en genereert standaardconflictmarkeringen wanneer bestanden conflicteren.
Eenvoudige 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
De klasse MergeResult is een alleen-lezen value object:
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)
Wanneer de merge stopt vanwege conflicten, wordt de conflictstatus naar de index en de werkboom geschreven. Los de bestanden op, add() ze en roep $repo->mergeContinue() aan, of roep $repo->mergeAbort() aan om de status van vóór de merge te herstellen.
Commit of stash lokale wijzigingen voordat u gaat mergen: de merge bouwt de werkboom opnieuw op aan de hand van het resultaat en overschrijft niet-vastgelegde wijzigingen in bijgehouden bestanden (#2).
Fast-forward-merges
Wanneer de huidige HEAD een voorouder is van de doelbranch, voert Pitmaster een fast-forward-merge uit: het verplaatst HEAD simpelweg naar de doelcommit zonder een mergecommit aan te maken.
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
Het omgekeerde geval wordt nog niet gedetecteerd: een branch mergen die HEAD al bevat, maakt een nieuwe mergecommit met een ongewijzigde tree in plaats van "Already up to date" te melden (#6).
Drieweg-samenvoeging
Wanneer branches uiteen zijn gelopen, voert Pitmaster een three-way merge uit:
- Zoek de mergebasis (laagste gemeenschappelijke voorouder)
- Vergelijk de tree van de basis met die van ons en die van hen
- Voer voor elk bestand dat in beide branches is gewijzigd een merge op inhoudsniveau uit
- Maak een mergecommit met twee ouders
Before: After merge:
C---D (feature) C---D
/ / \
A---B---E---F (main) A---B---E---F---G (main)
Mergebasis
De mergebasis is de meest recente gemeenschappelijke voorouder van twee commits. Pitmaster vindt deze door de commitgraaf te doorlopen. Als er meerdere mergebasissen zijn (criss-cross-geschiedenis), voegt merge() die eerst samen en gebruikt het resultaat als basis.
$base = $repo->mergeBase(
$repo->resolve('main'),
$repo->resolve('feature/login'),
);
if ($base !== null) {
echo "Common ancestor: {$base->hex}\n";
}
Voor directe toegang tot het algoritme van MergeBase:
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-mergecontrole
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
}
Conflictmarkeringen
Wanneer een bestand in beide branches is gewijzigd en de wijzigingen elkaar overlappen, genereert Pitmaster standaardconflictmarkeringen. merge() labelt de kanten met HEAD en de naam van de samengevoegde branch:
<<<<<<< HEAD
return $this->value + 1;
=======
return $this->value * 2;
>>>>>>> feature
De klasse ConflictMarker plaatst twee versies tussen markeringen:
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');
Three-way merge op inhoudsniveau
De klasse ThreeWayMerge voegt blob-inhoud samen (geen bestanden):
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
Deze neemt drie strings (de basisversie, onze versie en hun versie), plus optionele labels en een conflictstijl (standaard 'merge', of 'diff3' om het basisgedeelte op te nemen).
Mergestrategieën
Recursieve merge
De standaardstrategie ($repo->merge($branch)) verwerkt criss-cross-mergegeschiedenissen met meerdere mergebasissen. De CLI accepteert hiervoor ook -s ort en -s recursive.
Ours-merge
Legt een mergecommit vast die de tree van HEAD ongewijzigd laat. Handig om de branchgeschiedenis bij te houden zonder wijzigingen op te nemen.
$result = $repo->merge('feature/old', 'ours');
Octopus-merge
Voegt twee of meer branches samen in één mergecommit. Als een branch conflicten oplevert, retourneert deze een niet-schone uitkomst zonder de mergestatus op te slaan of een commit aan te maken.
$result = $repo->mergeOctopus(['feature/a', 'feature/b', 'feature/c']);
De klassen RecursiveMerge, OursMerge en OctopusMerge in Pitmaster\Merge zijn helpers voor merges op inhoudsniveau; Repository implementeert de bovenstaande strategieën zelf.
Rerere (opgeslagen oplossing opnieuw gebruiken)
Pitmaster bevat een zelfstandige klasse Rerere die oplossingen opslaat in de rr-cache-indeling van git:
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() roept deze niet aan: oplossingen worden alleen vastgelegd en opnieuw afgespeeld wanneer uw code dat doet. isEnabled() zoekt naar de letterlijke tekst rerere.enabled = true in .git/config en herkent het gedeelte [rerere] niet dat git config rerere.enabled true schrijft (#20).
Mergeverloop op tree-niveau
Het volledige mergeproces:
MergeBase::findAll()zoekt de gemeenschappelijke voorouders- De trees van de basis, ons en hen worden pad voor pad vergeleken
- Voor bestanden die slechts in één branch zijn gewijzigd: neem die wijziging over
- Voor bestanden die in beide branches zijn gewijzigd:
- Als beide op dezelfde manier zijn gewijzigd: neem een van beide (ze komen overeen)
- Als de wijzigingen verschillen: voer
ThreeWayMerge::merge()uit op de inhoud
- Als alle merges zonder conflicten verlopen: bouw de samengevoegde tree op en maak een mergecommit
- Bij conflicten: retourneer
MergeResultmetclean = falseen de conflictpaden
Gebruik van CLI
./vendor/bin/pitmaster merge feature/login
Uitvoer bij succes:
Merge successful.
Uitvoer bij conflict:
CONFLICT in: src/File.php, src/Other.php