Skip to main content
Lock-v1 maps repository-relative paths to 32-byte BLAKE3 content digests. The format identifier is https://getgat.dev/spec/lock-v1.

Conformance language

MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are normative. Readers MUST enforce the grammar and invariants below. Writers MUST emit canonical LF-terminated output; readers MUST also accept CRLF, including mixed line endings.

Semantic model

Each entry maps one file path to one content ID (OID).
  • Paths MUST be unique across the logical lock.
  • Multiple paths MAY reference the same OID.
  • Directories are implicit. A tracked path MUST NOT be a /-delimited ancestor of another tracked path, including across shards.
  • File size, timestamps, permissions, and storage locations are not recorded.
  • Flat and sharded layouts represent the same logical map.

Canonical flat format

The flat representation is a UTF-8 file named gat.lock at the repository root. The example uses literal TAB separators and illustrative digests:

Grammar

LF is byte 0x0a; HTAB is byte 0x09. escaped-path follows Paths. For accepted input, each LF terminator MAY independently be preceded by one CR (byte 0x0d). Canonical output uses LF alone.

Version line

The first line MUST match version https://getgat.dev/spec/lock-v1 byte-for-byte, excluding its terminator. Readers MUST reject a missing or different version, a UTF-8 byte-order mark, and an unterminated version line. The version line alone, with its terminator, represents an empty map. An absent gat.lock is not a lock-v1 document.

Entry rows

Each row contains the digest first, exactly one TAB at byte offset 64, and the unquoted escaped path before the record terminator:
Readers MUST reject invalid digests, paths, separators, ordering, duplicates, and file/directory-prefix conflicts. Additional fields, comments, and blank rows are invalid. Quotes are literal path characters, not field delimiters.

Line endings and canonical output

The version line and every entry MUST have a terminator, including the final row. Readers MUST accept LF, CRLF, and mixed endings. Only one CR immediately before LF is framing; other raw controls in the path are invalid. A decoded path ending in CR MUST encode that character as \x0d, independently of the record terminator. Writers MUST emit UTF-8, LF terminators, canonical escapes, and strictly path-ordered rows. They MUST NOT emit a byte-order mark, blank rows, or optional whitespace. Spaces within a path are data and MUST be preserved.

Paths

Paths are unquoted UTF-8 with control-only escaping:
  • U+0000–U+001F MUST use \xhh, with exactly two lowercase hexadecimal digits.
  • All other permitted Unicode scalar values MUST appear literally, including quotes, spaces, /, U+007F, U+2028, and U+2029.
  • Backslash is only an escape introducer; decoded backslashes are invalid.
Readers MUST reject invalid UTF-8, raw controls, incomplete or unknown escapes, uppercase escape digits, and escapes outside U+0000–U+001F. For example, \t, \n, \u0009, \x2f, and \" are invalid. The decoded path MUST be non-empty and repository-relative, with / separators. Empty segments, . segments, .. segments, leading or trailing /, and backslashes are invalid. Thus ./a, a//b, a/../b, /a, and a/ are invalid. Path identity uses exact decoded UTF-8 bytes, with no Unicode normalization or case folding. Colons are ordinary characters, including in C:foo. Format validity does not imply filesystem support: controls and names prohibited by a host filesystem can still be valid lock paths. Filesystem consumers MUST preserve repository-root confinement when resolving paths.

Content IDs

The first field MUST contain exactly 64 lowercase hexadecimal characters (0–9, a–f) encoding the 32-byte BLAKE3 digest of the file’s raw bytes. The version defines the algorithm; rows contain no blake3: prefix. Uppercase hex, base64, whitespace, and alternative digest spellings are invalid. The digest excludes the path and filesystem metadata. Identical file bytes have the same OID regardless of path.

Canonical ordering

Rows MUST be strictly increasing by decoded path UTF-8 bytes, within the flat file or within each shard. Digests and escape spellings do not participate in ordering. Readers MUST reject unordered input and duplicate paths. Row position and shard traversal order have no semantic meaning. Directory-prefix validation MUST also detect non-adjacent conflicts such as foo, foo.bar, foo/bar.

Sharded representation

A sharded lock is a gat.lock/ directory. Each shard is a complete lock-v1 document with the same header, digest-first rows, path encoding, and line-ending rules. Flat and sharded locks use the same format identifier. See Shard a very large Gat lock for configuration.

Shard assignment

For depth N, where 1 <= N <= 32:
  1. Hash the exact decoded path UTF-8 bytes with BLAKE3.
  2. Encode the first N digest bytes as lowercase hexadecimal byte pairs.
  3. Use the first N - 1 pairs as directories and the last as a .tsv filename.
For example, a path hash beginning with bytes 12 34 maps to gat.lock/12/34.tsv at depth 2. Assignment hashes the path, not the content OID or escaped spelling. Implementations MAY expose a smaller maximum depth.

Shard invariants

Readers MUST enforce:
  • valid lock-v1 content in every shard,
  • uniform shard depth and lowercase two-digit hexadecimal name components,
  • correct path-hash placement,
  • strictly increasing decoded paths within each shard, and
  • global path uniqueness and absence of file/directory-prefix conflicts.
Writers SHOULD omit empty or stale shards.

Shape detection

Consumers MUST determine flat-file versus sharded-tree layout from the revision itself, not external configuration. Different revisions may use different layouts.

Semantic equivalence

Two representations are equivalent when they define the same path-to-OID map. Flat versus sharded layout, valid shard depth, and accepted line-ending differences do not affect equivalence. Semantic comparisons SHOULD compare maps rather than raw bytes; gat diff provides this comparison.

Three-way merge

Merge independently for each path in the union of ancestor, ours, and theirs. Each value is an OID or None for absence:
The combined map MUST satisfy all lock invariants before canonical serialization, including file/directory-prefix checks across independently merged paths. Shard placement is derived from the output paths and depth. See Branching and merging for conflict resolution.

Invalid examples

In this table, <digest64> denotes a valid digest and <TAB> a literal separator.

Versioning

Readers MUST reject unknown version identifiers. Future incompatible changes to fields, escaping, or content-ID syntax MUST use a new identifier. A change of valid shard depth preserves the grammar and semantics and does not require a new version.