合并
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 已包含的分支时,会创建一个树未变化的新合并提交,而不是报告“已经是最新状态”(问题 #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');
Octopus 合并
将两个或更多分支合并到一个合并提交中。如果任何分支发生冲突,则返回未清理的结果,不会写入合并状态或创建提交。
$result = $repo->mergeOctopus(['feature/a', 'feature/b', 'feature/c']);
Pitmaster\Merge 中的 RecursiveMerge、OursMerge 和 OctopusMerge 类是内容级辅助工具;Repository 自行实现上述策略。
Rerere(重用已记录的解决方案)
Pitmaster 包含一个独立的 Rerere 类,用于按照 git 的 rr-cache 布局存储解决方案:
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() 会在 .git/config 中查找字面文本 rerere.enabled = true,并且无法识别 git config rerere.enabled true 写入的 [rerere] 部分(问题 #20)。
树级合并流程
完整合并流程:
MergeBase::findAll()定位共同祖先- 按路径逐一比较基准树、本方树和对方树
- 对只在一个分支中更改的文件:采用该更改
- 对在两个分支中都更改的文件:
- 如果两边的更改相同:采用任一方的更改(两者一致)
- 如果更改不同:对内容运行
ThreeWayMerge::merge()
- 如果所有合并均无冲突:构建合并后的树并创建合并提交
- 如果出现任何冲突:返回带有
clean = false和冲突路径的MergeResult
CLI 用法
./vendor/bin/pitmaster merge feature/login
成功时的输出:
Merge successful.
冲突时的输出:
CONFLICT in: src/File.php, src/Other.php