Documentation
¶
Overview ¶
Package merge is the pure, deterministic three-way reconcile engine at the heart of Milestone S1 team sync. Given a path's three versions (the client's base ancestor, the hub's current HEAD, and the client's incoming change) it decides what the hub should land: a fast-forward, an additive append-merge, a clean delete, or a true conflict that keeps the hub version and parks the loser in a *.sync-conflict sibling. It does no I/O: the hub feeds it bytes read from git (S1.3) so every case is table-testable.
Two SPEC 6.4/6.5 invariants drive it:
- Append-merge first. Two writers that only ADD blocks to a shared page merge with no conflict (the flywheel's core write pattern), order- and side-independent, deduped by content hash. Only a true overwrite of existing content conflicts.
- Never lose data. A conflict keeps the hub version live and writes the incoming version to a sibling a human resolves; a delete that races an edit keeps the edit.
Line endings are normalized to LF for hashing and equality only; block bytes are preserved (SPEC 6.5: do not rewrite a teammate's CRLF).
Index ¶
Constants ¶
const MaxNoteBytes = 1 << 20
MaxNoteBytes caps a mergeable note; larger or binary content is rejected upstream (SPEC: binaries are out of v1).
Variables ¶
This section is empty.
Functions ¶
func BasePath ¶
BasePath reverses SiblingPath: given a sync-conflict sibling path it returns the base note path the loser was parked next to (ok=false if the name is not a sibling). It strips from the LAST marker on the extension-stripped stem, so a sanitized user containing '-' (e.g. alice-smith) and a base whose own stem has hyphens both reverse correctly. A recovered base that still looks like a sibling (a doubly-nested/garbage name) is refused, since no real base note can.
func IsText ¶
IsText reports whether content is small enough and free of NUL bytes to be a mergeable markdown note (binaries are rejected; SPEC v1 has no blob support).
func SiblingPath ¶
SiblingPath names a conflict sibling next to path, e.g. notes/x.md -> notes/x.sync-conflict-20260616-alice-1a2b3c4d.md. The short hash of the losing content makes the name unique per distinct version (so two conflicts on the same path/user/day never overwrite each other) AND idempotent (re-syncing the same conflict reuses the same sibling rather than spawning a new one every time).
Types ¶
type Resolution ¶
type Resolution struct {
Path string
Action Action
Content []byte // ActionUpsert: the (possibly merged) content to write at Path
SiblingPath string // ActionConflict: where the losing version goes
SiblingContent []byte // ActionConflict: the losing (incoming) content
}
Resolution is what the hub should land for one path.