Злиття
Pitmaster виконує злиття гілок за допомогою тристороннього злиття, як git 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
Клас MergeResult — це об’єкт-значення лише для читання:
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)
Якщо злиття зупиняється через конфлікти, їхній стан записується до індексу й робочого дерева. Вирішіть конфлікти у файлах, виконайте add() і викличте $repo->mergeContinue() або викличте $repo->mergeAbort(), щоб відновити стан до злиття.
Перш ніж виконувати злиття, зафіксуйте локальні зміни або сховайте їх: злиття перебудовує робоче дерево за результатом і перезаписує незбережені зміни у відстежуваних файлах (#2).
Злиття з перемотуванням уперед
Якщо поточний HEAD є предком цільової гілки, Pitmaster виконує злиття з перемотуванням уперед: просто переміщує HEAD до цільового коміту, не створюючи коміту злиття.
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
Зворотний випадок поки що не виявляється: злиття гілки, яка вже міститься в HEAD, створює новий коміт злиття з незміненим деревом замість повідомлення «Already up to date» (#6).
Тристороннє злиття
Якщо гілки розійшлися, Pitmaster виконує тристороннє злиття:
- Знаходить базу злиття (найближчого спільного предка)
- Порівнює дерево від бази до нашої гілки та від бази до їхньої гілки
- Для кожного файлу, зміненого в обох гілках, виконує злиття на рівні вмісту
- Створює коміт злиття з двома батьківськими комітами
Before: After merge:
C---D (feature) C---D
/ / \
A---B---E---F (main) A---B---E---F---G (main)
База злиття
База злиття — це найновіший спільний предок двох комітів. Pitmaster знаходить її, проходячи граф комітів. Якщо баз злиття кілька (історія перехресних злиттів), merge() спочатку зливає їх, а потім використовує результат як базу.
$base = $repo->mergeBase(
$repo->resolve('main'),
$repo->resolve('feature/login'),
);
if ($base !== null) {
echo "Common ancestor: {$base->hex}\n";
}
Для безпосереднього доступу до алгоритму 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);
Перевірка злиття гілок
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
}
Маркери конфліктів
Якщо файл змінено в обох гілках і ці зміни перетинаються, Pitmaster створює стандартні маркери конфліктів. merge() позначає сторони як HEAD та назву гілки злиття:
<<<<<<< HEAD
return $this->value + 1;
=======
return $this->value * 2;
>>>>>>> feature
Клас ConflictMarker обрамляє дві версії маркерами:
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');
Тристороннє злиття на рівні вмісту
Клас ThreeWayMerge зливає вміст blob-об’єктів (не файлів):
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
Він приймає три рядки (базову версію, нашу версію та їхню версію), необов’язкові мітки й стиль конфлікту (за замовчуванням 'merge' або 'diff3', щоб включити базовий розділ).
Стратегії злиття
Рекурсивне злиття
Стратегія за замовчуванням ($repo->merge($branch)) обробляє історії перехресних злиттів із кількома базами злиття. CLI також приймає для неї -s ort і -s recursive.
Злиття «наша версія»
Записує коміт злиття, залишаючи дерево HEAD незмінним. Корисно для збереження історії гілки без включення змін.
$result = $repo->merge('feature/old', 'ours');
Злиття «восьминіг»
Зливає дві або більше гілок в один коміт злиття. Якщо в будь-якій гілці є конфлікти, повертає неузгоджений результат, не записуючи стан злиття й не створюючи коміт.
$result = $repo->mergeOctopus(['feature/a', 'feature/b', 'feature/c']);
Класи RecursiveMerge, OursMerge і OctopusMerge у Pitmaster\Merge — це допоміжні засоби для злиття на рівні вмісту; Repository самостійно реалізує наведені вище стратегії.
Rerere (повторне використання записаного вирішення)
Pitmaster містить окремий клас Rerere, який зберігає вирішення у форматі rr-cache від 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() не викликає його: вирішення записуються й відтворюються лише тоді, коли це робить ваш код. isEnabled() шукає буквальний текст rerere.enabled = true у .git/config і не розпізнає розділ [rerere], який записує git config rerere.enabled true (#20).
Процес злиття на рівні дерева
Повний процес злиття:
MergeBase::findAll()знаходить спільних предків- Дерева бази, нашої та їхньої гілок порівнюються за шляхами
- Для файлів, змінених лише в одній гілці: застосовується ця зміна
- Для файлів, змінених в обох гілках:
- Якщо обидві гілки змінили файл однаково: застосовується будь-яка з цих змін (вони збігаються)
- Якщо зміни відрізняються: до вмісту застосовується
ThreeWayMerge::merge()
- Якщо всі злиття завершилися без конфліктів: створюється об’єднане дерево й коміт злиття
- Якщо є конфлікти: повертається
MergeResultзclean = falseі шляхами файлів із конфліктами
Використання CLI
./vendor/bin/pitmaster merge feature/login
Вивід у разі успіху:
Merge successful.
Вивід за наявності конфлікту:
CONFLICT in: src/File.php, src/Other.php