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 namedgat.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 matchversion 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: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.
\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 asfoo, foo.bar, foo/bar.
Sharded representation
A sharded lock is agat.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 depthN, where 1 <= N <= 32:
- Hash the exact decoded path UTF-8 bytes with BLAKE3.
- Encode the first
Ndigest bytes as lowercase hexadecimal byte pairs. - Use the first
N - 1pairs as directories and the last as a.tsvfilename.
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.
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 orNone for absence:
Invalid examples
In this table,<digest64> denotes a valid digest and <TAB> a literal separator.

