MAX_WALK
MAX_WALK = 5000
How many commits either side's walk reads before the plan gives up, when nothing says.
Plans and applies a merge of one config branch back into the trunk.
Configuration only, and the refusal names what it refused. A configuration object is captured WHOLE on every save, so its value at three commits is three complete documents and a three-way merge over them is well defined. Content entities and table rows are captured as FIELD DELTAS against a parent, so the same operation over them would mean replaying two divergent delta chains and inventing a value wherever they disagree - which is not a merge, it is data loss with extra steps. So a branch that received anything outside the configuration realm makes the plan refuse and names the realm and the subjects, rather than merging the config and quietly dropping the rest.
The target is always the trunk. A merge writes configuration to the live site, and the live
site is what refs/heads/main describes. Merging into some other ref would write values the trunk
does not have and leave the ref every restore resolves through describing a site that no longer
exists.
The apply is a restricted logical restore, not a second write path. That is what gives the merge the forced pre-write snapshot, the per-object failure reporting and the audit row for free, and it is why there is no option here to skip the snapshot: the one moment somebody needs an undo is right after a merge they did not mean to run.
Three commits come out of an applied merge, in this order: the snapshot the restore forced, the ordinary commit that seals the configuration it wrote, and a two-parent merge commit whose second parent is the branch tip. That last one carries no segment; its whole job is to make the branch's commits reachable from the trunk, so a prune keeps them and a later merge of the same branch can see it has already been brought in.
$branches : \Drupal\strata\Branch\BranchStore
$refs : \Drupal\strata\Tree\RefStore
$commits : \Drupal\strata\Tree\CommitLog
$index : \Drupal\strata\Tree\CommitIndex
$mergeBase : \Drupal\strata\Branch\MergeBase
$threeWay : \Drupal\strata\Branch\ThreeWayMerge
$replayer : \Drupal\strata\Restore\Replayer
$segments : \Drupal\strata\Segment\SegmentReader
$restore : \Drupal\strata\Restore\LogicalRestore
$flusher : \Drupal\strata\Flush\Flusher
$branchIndex : \Drupal\strata\Branch\BranchIndex
__construct(\Drupal\strata\Branch\BranchStore $branches, \Drupal\strata\Tree\RefStore $refs, \Drupal\strata\Tree\CommitLog $commits, \Drupal\strata\Tree\CommitIndex $index, \Drupal\strata\Branch\MergeBase $mergeBase, \Drupal\strata\Branch\ThreeWayMerge $threeWay, \Drupal\strata\Restore\Replayer $replayer, \Drupal\strata\Segment\SegmentReader $segments, \Drupal\strata\Restore\LogicalRestore $restore, \Drupal\strata\Flush\Flusher $flusher, \Drupal\strata\Branch\BranchIndex $branchIndex, \Drupal\Core\Session\AccountProxyInterface $currentUser, \Psr\Log\LoggerInterface $logger, int $maxWalk = \self::MAX_WALK) : mixed
Constructs a merger.
| \Drupal\strata\Branch\BranchStore | $branches | Resolves the branch being merged. |
| \Drupal\strata\Tree\RefStore | $refs | Resolves the trunk's tip. |
| \Drupal\strata\Tree\CommitLog | $commits | Reads the commits either side added. |
| \Drupal\strata\Tree\CommitIndex | $index | Where the merge commit is mirrored, and what MergeBase walks. |
| \Drupal\strata\Branch\MergeBase | $mergeBase | Finds the commit the two sides last agreed at. |
| \Drupal\strata\Branch\ThreeWayMerge | $threeWay | Merges one object across the three. |
| \Drupal\strata\Restore\Replayer | $replayer | Reconstructs each object at each of the three commits. |
| \Drupal\strata\Segment\SegmentReader | $segments | Reads what each commit on either side actually touched. |
| \Drupal\strata\Restore\LogicalRestore | $restore | Writes the merged configuration back, with its forced snapshot. |
| \Drupal\strata\Flush\Flusher | $flusher | Seals the configuration the merge wrote before the merge commit names it. |
| \Drupal\strata\Branch\BranchIndex | $branchIndex | Kept in step when a branch moves. |
| \Drupal\Core\Session\AccountProxyInterface | $currentUser | Attributes the merge to whoever ran it. |
| \Psr\Log\LoggerInterface | $logger | Records the outcome. |
| int | $maxWalk | How many commits either side is read for before the plan gives up. Each one costs a segment read, so this bounds object reads rather than queries. |
branch(string $name, string|null $from = null) : \Drupal\strata\Branch\Branch
Cuts a branch off a commit and indexes it.
| string | $name | The branch name. |
| string|null | $from | Commit to fork from, or NULL for the trunk's current tip. |
When the name is taken, the trunk has no tip to fork from, or the commit is not present.
The branch.
plan(string $name, \Drupal\strata\Branch\MergeStrategy $strategy = \Drupal\strata\Branch\MergeStrategy::REFUSE) : \Drupal\strata\Branch\MergePlan
Works out what merging a branch into the trunk would do.
| string | $name | The branch name. |
| \Drupal\strata\Branch\MergeStrategy | $strategy | What to do with a key both sides changed. |
The plan, which carries its own refusal when it could not be built.
apply(\Drupal\strata\Branch\MergePlan $plan, bool $apply = true) : \Drupal\strata\Branch\MergeResult
Applies a plan that has already been made.
The plan is applied as it was printed rather than recomputed after the answer, which is what makes the manifest an operator approved the thing that happens.
| \Drupal\strata\Branch\MergePlan | $plan | The plan. |
| bool | $apply | FALSE to report what the plan would do and write nothing. |
What was written, or what would be.
merge(string $name, \Drupal\strata\Branch\MergeStrategy $strategy = \Drupal\strata\Branch\MergeStrategy::REFUSE, bool $apply = true) : \Drupal\strata\Branch\MergeResult
Merges a branch in one call.
| string | $name | The branch name. |
| \Drupal\strata\Branch\MergeStrategy | $strategy | What to do with a key both sides changed. |
| bool | $apply | FALSE to plan and write nothing. |
What was written, or what would be.
build(\Drupal\strata\Branch\Branch $branch, string $target, string $ours, \Drupal\strata\Branch\MergeStrategy $strategy, float $started) : \Drupal\strata\Branch\MergePlan
Builds the plan once both tips are known.
| \Drupal\strata\Branch\Branch | $branch | The branch being merged. |
| string | $target | Ref name being merged into. |
| string | $ours | The trunk's tip. |
| \Drupal\strata\Branch\MergeStrategy | $strategy | What to do with a key both sides changed. |
| float | $started | When planning began. |
When a walk cannot be completed, which the caller turns into a refusal.
The plan.
restorePlan(\Drupal\strata\Branch\MergePlan $plan) : \Drupal\strata\Restore\RestorePlan
The restricted restore plan a merge applies.
Concurrent-change detection is switched off, by passing no plan time. It compares each subject's last-written timestamp against the moment the plan was built, and the restore's own forced snapshot writes every pending subject between those two moments, so every object in the plan would look like somebody else's edit. The three-way comparison against the merge base is the stronger check and it has already run.
| \Drupal\strata\Branch\MergePlan | $plan | The plan. |
A plan holding one config subject per object the merge writes.
seal(\Drupal\strata\Branch\MergePlan $plan) : string|null
Writes the two-parent commit that records the merge.
| \Drupal\strata\Branch\MergePlan | $plan | The plan that was applied. |
The merge commit id, or NULL when the trunk lost its tip mid-merge or another writer moved it.
touched(string $from, array$behind) : list<string>
Every subject one side touched since the merge base.
Read from the segments the commits on that side sealed, rather than by comparing the two whole subject sets. A site has thousands of subjects and a branch touches a handful, so the difference is between reading a handful of segments and materializing the whole site three times.
| string | $from | Tip to walk back from. |
| array |
$behind | Commits at or behind the merge base, which end the walk. |
When the walk passes $maxWalk, which means the branch is further from the trunk than a merge will look.
Subject paths, such as config/system.site, with duplicates removed.
subjectsIn(\Drupal\strata\Tree\Commit $commit) : list<string>
The subjects one commit's segment names.
| \Drupal\strata\Tree\Commit | $commit | The commit. |
When a segment will not read. A merge over a side whose history is partly unreadable would compare against a smaller set of objects than the branch actually touched, and would silently leave the rest behind.
Subject paths, empty when the commit sealed no segment.
refuseOtherRealms(\Drupal\strata\Branch\Branch $branch, list$subjects) : list<string>
One refusal line per realm the branch touched that a branch cannot carry.
| \Drupal\strata\Branch\Branch | $branch | The branch being merged. |
| list |
$subjects | Subject paths the branch touched. |
The refusals, empty when the branch touched configuration only.
readable(string $subject, array$results) : string|null
Why one object cannot be merged, when a replay could only partly reconstruct it.
A merge over a partial document would read every field the replay could not read as a deletion, so it would produce a config object that is neither side's and write it over a live one.
| string | $subject | The subject path. |
| array |
$results | Commit id keyed to what the replay produced there. |
The refusal, or NULL when every side read completely.
dataOf(\Drupal\strata\Restore\ReplayResult|null $result) : array<string,mixed>|null
The data a replay produced, or NULL when the object was not there.
| \Drupal\strata\Restore\ReplayResult|null | $result | What the replay produced. |
The configuration data, or NULL for an object that did not exist at that commit.