Skip to content

What is preserved, and what is not

The guarantee, stated precisely.

Preserved

Every byte you did not touch. The engine edits the source rather than re-emitting a tree, and a transaction's output is the original bytes with the edit's footprint replaced. A parse followed by an emit is the identity function. That covers, without a list of exceptions: comments and their column alignment, blank lines, key order, indentation width, quoting style, block scalars and their chomping indicators, flow collections across several lines, anchors, aliases and merge keys, explicit tags, directives, CRLF and mixed line endings, a missing final newline, a byte order mark, every document of a multi-document stream and the bytes between them, and any character the source contains.

The meaning of what you did touch. A written value reads back as the same type and value under the document's own YAML version: a string stays a string however it is spelt, a float stays a float, 0.1 stays 0.1. An existing scalar keeps its quoting style where that style can still hold the new value, its tag where the new value has the same type, and its anchor always.

Comment ownership. Every comment has one owner before the edit and the same owner after it, or the edit does not commit. Comment ownership has the rules.

Changed by an edit, and only by an edit

A replaced scalar's bytes. A new entry's bytes, written in block style at the document's own indentation step, appended after the last existing entry and before a comment that closes the mapping. A removed entry's bytes, with the comments it owned. A replaced mapping's entries, where the keys the new value does not mention are removed, the keys it does mention keep their comments, and the order is the new value's. A replaced sequence's items, all of them, with their comments.

A new float is spelt positionally, 1.5 and 3.0, switching to a signed exponent only where Go's own %g would. A new multiline string is a literal block, |, |- or |+ as its trailing breaks require, except where a block cannot go, where it is double-quoted with escapes. A new value containing a character a reader cannot see, such as a bidi override, is double-quoted so the escape makes it visible. Every rule, for every kind of value over every kind of existing scalar, is in How a written value is spelt.

Anchors and aliases

An anchor marks a value. Replacing the value keeps the anchor on the new value, so every alias resolves to it. Editing under an anchor edits in place. Writing over an alias is refused, because the library cannot tell whether you mean the shared value or this one reference; removing an anchor an alias still needs is refused, because the alias would dangle. Emission never expands aliases, and explicit expansion is bounded by the projection limits.

Never

Bytes that do not parse. Every transaction re-parses its own output before it commits.

Bytes that parse but no longer mean what the edit said. The re-parsed output is compared against an independent statement of the edit: the values requested, the nodes retained, the bytes those nodes depend on, and the owner of every comment. A mismatch is ErrMismatch or ErrPreservation, nothing is committed, and the file is as it was. The check is independent of the code that produced the output, which is the point: a bug in the writer cannot vouch for itself.

A half-applied batch. The callback given to Edit is the transaction. One error anywhere in it discards all of it.

Stability across repeated edits

A tool that saves on every keystroke must not accumulate drift. Since an unedited byte is never rewritten, the only bytes that can change between two saves are the ones the second edit touched, and writing the value a scalar already holds is a no-op that changes nothing at all.