Merge model¶
The merge model is designed to be deterministic and reviewable.
flowchart LR
I["image.yaml"] --> M["merge"]
A1["by-first-axis/value.yaml"] --> M
A2["by-later-axis/value.yaml"] --> M
M --> R["rendered IC config"]
Precedence is axis declaration order¶
Fragments apply in the order axes are declared in matrix:. A later axis wins later $set conflicts and appends later to lists.
Here by-edition/... applies before by-channel/.... This is intentional: adding a directory or changing alphabetical order cannot silently change precedence. Authors control precedence where they declare the axes.
A fragment can target a combination of axes (by-edition+arch/pro+arm64.yaml, a conjunction) or
several values of one axis (by-channel/stable+edge.yaml, a disjunction). Such fragments sort after the
single-axis ones they refine (more axes = more specific = later), and a narrower single-value fragment wins
over a broader disjunction on the same axis. tailor explain <image> --cell <slug> prints the exact order;
see image.yaml and Merge directives.
Fragment sort order¶
For a given cell, the fragments that apply are sorted by these keys, in order, and merged base → last:
- Arity — how many axes the fragment constrains. A single-axis fragment
(
by-edition/pro.yaml) applies before a multi-axis one (by-edition+arch/pro+arm64.yaml), so the more specific composite wins. - Axis declaration order — among fragments of equal arity, the axis declared earlier in
matrix:applies first (later-declared axes win later conflicts). - Breadth — a broader disjunction (
by-channel/stable+edge.yaml) applies before a narrower single value (by-channel/edge.yaml) on the same axis, so the single value wins.
Worked example¶
For the cell edition=pro, channel=edge, these fragments merge in this order (each may override the
previous):
image.yaml(the base)by-edition/pro.yaml(arity 1, first-declared axis)by-channel/stable+edge.yaml(arity 1, later axis, broad)by-channel/edge.yaml(arity 1, later axis, narrow — beats the disjunction above)by-edition+channel/pro+edge.yaml(arity 2 — most specific, wins last)
Maps, lists, and scalars¶
- Maps deep-merge.
- Lists append by default;
$prepend/$appendadd to either end and$replace/$removerewrite or trim. - Differing scalar assignments conflict unless the later assignment uses
$set. - Set a key's value to the bare token
$unsetto remove the inherited key entirely.
This makes accidental double ownership loud while keeping additive IC lists easy. See
docs/reference/directives.md for every directive.
Why no IC-aware merge?¶
tailor does not know that an IC list contains packages, partitions, filesystems, or services. It does not merge list items by id or deduplicate packages. If a structured list must change, own the whole list in the fragment or use $replace.
That keeps tailor a thin wrapper over Image Customizer rather than a second IC schema implementation.