Scalanie
Pitmaster scala gałęzie metodą trójstronną, podobnie jak git merge. Wykrywa możliwość przewinięcia do przodu, oblicza bazy scalania, scala drzewa i zawartość oraz generuje standardowe znaczniki konfliktu, gdy pliki są w konflikcie.
Podstawowe scalanie
$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
Klasa MergeResult to obiekt wartości tylko do odczytu:
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)
Gdy scalanie zatrzyma się z powodu konfliktów, ich stan jest zapisywany w indeksie i drzewie roboczym. Rozwiąż konflikty w plikach, dodaj je do indeksu za pomocą add() i wywołaj $repo->mergeContinue() albo wywołaj $repo->mergeAbort(), aby przywrócić stan sprzed scalania.
Przed scalaniem zatwierdź lub odłóż lokalne zmiany na stos: scalanie odtwarza drzewo robocze na podstawie wyniku i nadpisuje niezatwierdzone zmiany w śledzonych plikach (#2).
Scalanie z przewinięciem do przodu
Jeśli bieżący HEAD jest przodkiem docelowej gałęzi, Pitmaster wykonuje scalanie przez przewinięcie do przodu: po prostu przesuwa HEAD do docelowego commita bez tworzenia commita scalającego.
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
Odwrotny przypadek nie jest jeszcze wykrywany: scalenie gałęzi, którą HEAD już zawiera, tworzy nowy commit scalający z niezmienionym drzewem, zamiast wyświetlić komunikat „Already up to date” (#6).
Scalenie trójstronne
Gdy gałęzie rozeszły się, Pitmaster wykonuje scalanie trójstronne:
- Znajduje bazę scalania (najniższego wspólnego przodka)
- Porównuje bazę z naszą gałęzią i bazę z ich gałęzią na poziomie drzewa
- Dla każdego pliku zmienionego w obu gałęziach wykonuje scalanie zawartości
- Tworzy commit scalający z dwoma rodzicami
Before: After merge:
C---D (feature) C---D
/ / \
A---B---E---F (main) A---B---E---F---G (main)
Baza scalania
Baza scalania to najbliższy wspólny przodek dwóch commitów. Pitmaster znajduje ją, przechodząc przez graf commitów. Jeśli istnieje kilka baz scalania (historia typu criss-cross), merge() najpierw je scala i używa wyniku jako bazy.
$base = $repo->mergeBase(
$repo->resolve('main'),
$repo->resolve('feature/login'),
);
if ($base !== null) {
echo "Common ancestor: {$base->hex}\n";
}
Aby uzyskać bezpośredni dostęp do algorytmu 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);
Sprawdzanie scalania gałęzi
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
}
Znaczniki konfliktu
Gdy plik został zmodyfikowany w obu gałęziach, a zmiany na siebie nachodzą, Pitmaster generuje standardowe znaczniki konfliktu. merge() oznacza strony jako HEAD i nazwą scalanej gałęzi:
<<<<<<< HEAD
return $this->value + 1;
=======
return $this->value * 2;
>>>>>>> feature
Klasa ConflictMarker otacza dwie wersje znacznikami:
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');
Trójstronne scalanie zawartości
Klasa ThreeWayMerge scala zawartość blobów (nie plików):
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
Przyjmuje trzy ciągi znaków (wersję bazową, naszą wersję i ich wersję), opcjonalne etykiety oraz styl konfliktu (domyślnie 'merge' lub 'diff3', aby uwzględnić sekcję bazową).
Strategie scalania
Scalanie rekurencyjne
Strategia domyślna ($repo->merge($branch)), obsługuje historie scalania typu criss-cross z wieloma bazami scalania. CLI przyjmuje dla niej także -s ort i -s recursive.
Scalanie naszej wersji
Zapisuje commit scalający, który pozostawia drzewo HEAD bez zmian. Przydatne do zachowania historii gałęzi bez uwzględniania zmian.
$result = $repo->merge('feature/old', 'ours');
Scalanie typu octopus
Scala dwie lub więcej gałęzi w jeden commit scalający. Jeśli wystąpi konflikt w którejkolwiek gałęzi, zwraca wynik z konfliktami, nie zapisując stanu scalania ani nie tworząc commita.
$result = $repo->mergeOctopus(['feature/a', 'feature/b', 'feature/c']);
Klasy RecursiveMerge, OursMerge i OctopusMerge w Pitmaster\Merge to pomocnicze narzędzia do scalania zawartości; Repository samodzielnie implementuje powyższe strategie.
Rerere (ponowne użycie zapisanego rozwiązania)
Pitmaster zawiera samodzielną klasę Rerere, która przechowuje rozwiązania w układzie rr-cache używanym przez 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() jej nie wywołuje: rozwiązania są zapisywane i odtwarzane tylko wtedy, gdy zrobi to Twój kod. isEnabled() szuka dosłownego tekstu rerere.enabled = true w .git/config i nie rozpoznaje sekcji [rerere], którą zapisuje git config rerere.enabled true (#20).
Przebieg scalania na poziomie drzewa
Pełny proces scalania:
MergeBase::findAll()znajduje wspólnych przodków- Drzewa bazy, naszej gałęzi i ich gałęzi są porównywane ścieżka po ścieżce
- W przypadku plików zmienionych tylko w jednej gałęzi: przyjmuje tę zmianę
- W przypadku plików zmienionych w obu gałęziach:
- Jeśli obie zmiany są takie same: przyjmuje dowolną z nich (są zgodne)
- Jeśli zmiany się różnią: uruchamia
ThreeWayMerge::merge()na zawartości
- Jeśli wszystkie scalania przebiegną bez konfliktów: buduje scalone drzewo i tworzy commit scalający
- Jeśli wystąpią konflikty: zwraca
MergeResultwraz zclean = falsei ścieżkami konfliktów
Użycie CLI
./vendor/bin/pitmaster merge feature/login
Dane wyjściowe po pomyślnym zakończeniu:
Merge successful.
Wynik w przypadku konfliktu:
CONFLICT in: src/File.php, src/Other.php