Documentation
¶
Overview ¶
Package migrate upgrades a .spectackle workspace from an older schema stamp to the current one, in place, when the workspace is opened.
Why this is not the throwaway path T-0094 rejected ¶
T-0094 was rejected on the grounds that a one-off rewrite of one repository's records does not justify a permanent tool, and T-0137 was rejected in its favor. This is a different animal. An ID-scheme change (ADR-0013) reaches every workspace every user of a released tool already carries, and the schema stamp's documented answer to a mismatch is a hard error saying there is no migration (see workspace.SchemaStamp, spec.Load). Shipping the new scheme without a migration would turn every existing workspace into an unreadable one, and the honest advice to users would be to regenerate and lose their history.
Why it does not depend on the packages it rewrites ¶
The migration reads and writes raw bytes: work.md as text, journal.ndjson as generic JSON objects, spec.md and config.yaml as text with a front-matter stamp. It deliberately does not use internal/item, internal/journal or internal/spec, for two reasons that both matter.
The first is ordering. Those packages refuse a mismatched stamp — that is the very behavior being fixed — so a migration built on them could not read its own input. Nothing above them can help either: workspace.Detect fails on the old config stamp, so the migration has to run before a Root exists, on nothing but a directory path.
The second is fidelity. Parsing a journal event into the Go struct and re-marshalling it would silently drop any field the struct does not know and normalize the ones it does. A migration must be a rewrite of the fields it understands and a byte-faithful passthrough of everything else, so it decodes into an ordered generic form instead.
Properties ¶
Idempotent: a workspace already on the current stamp is untouched and reports nothing. Deterministic: new IDs are derived from each record's own creation timestamp in the journal plus its legacy ID as a seed (see ids.MintRecordIDFrom), so two clones migrating the same committed records converge instead of diverging and colliding on the next merge, and the archive keeps its chronology instead of collapsing into the migration moment. Atomic and recoverable: every new file content is computed in memory before anything is written, the originals are copied into a retained backup directory first, and a crash mid-write is detected and rolled back on the next open (see Recover).
Index ¶
Constants ¶
const ( From = "v0" To = "v1" )
From is the stamp this migration upgrades, To the stamp it produces. A bundle on any other stamp is refused exactly as before — there is one migration, not a chain, and pretending otherwise would silently mangle a workspace from a future version.
const Dot = ".spectackle"
Dot mirrors workspace.Dot. It is duplicated rather than imported to keep this package free of the dependency that would make the import cycle (see the package comment: internal/item imports internal/workspace, and this package must not import either).
Variables ¶
var ErrUnknownStamp = errors.New("migrate: unknown schema stamp")
ErrUnknownStamp reports a bundle stamped with something this migration does not understand — a workspace written by a newer version, most likely.
Functions ¶
Types ¶
type Report ¶
type Report struct {
Migrated bool // false = already current, nothing written
Remap map[string]string // legacy ID -> new ID, every record found
Files []string // workspace-relative paths rewritten, sorted
Events int // journal events whose content changed
Backup string // workspace-relative path of the retained backup
}
Report describes what a Run did, or would have done.
func Recover ¶
Recover rolls back an interrupted migration: a retained backup with no completion marker means the process died between the first and last write, so the workspace is a mix of old and new files. Restoring the backup returns it to a state that loads, which Run then migrates again from scratch.
Reported as Migrated when it actually restored something.