|
|
|
# reftable
|
|
|
|
|
|
|
|
[TOC]
|
|
|
|
|
|
|
|
## Overview
|
|
|
|
|
|
|
|
### Problem statement
|
|
|
|
|
|
|
|
Some repositories contain a lot of references (e.g. android at 866k,
|
|
|
|
rails at 31k). The existing packed-refs format takes up a lot of
|
|
|
|
space (e.g. 62M), and does not scale with additional references.
|
|
|
|
Lookup of a single reference requires linearly scanning the file.
|
|
|
|
|
|
|
|
Atomic pushes modifying multiple references require copying the
|
|
|
|
entire packed-refs file, which can be a considerable amount of data
|
|
|
|
moved (e.g. 62M in, 62M out) for even small transactions (2 refs
|
|
|
|
modified).
|
|
|
|
|
|
|
|
Repositories with many loose references occupy a large number of disk
|
|
|
|
blocks from the local file system, as each reference is its own file
|
|
|
|
storing 41 bytes (and another file for the corresponding reflog).
|
|
|
|
This negatively affects the number of inodes available when a large
|
|
|
|
number of repositories are stored on the same filesystem. Readers can
|
|
|
|
be penalized due to the larger number of syscalls required to traverse
|
|
|
|
and read the `$GIT_DIR/refs` directory.
|
|
|
|
|
|
|
|
### Objectives
|
|
|
|
|
|
|
|
- Near constant time lookup for any single reference, even when the
|
|
|
|
repository is cold and not in process or kernel cache.
|
|
|
|
- Near constant time verification if a SHA-1 is referred to by at
|
|
|
|
least one reference (for allow-tip-sha1-in-want).
|
|
|
|
- Efficient lookup of an entire namespace, such as `refs/tags/`.
|
|
|
|
- Support atomic push with `O(size_of_update)` operations.
|
|
|
|
- Combine reflog storage with ref storage for small transactions.
|
|
|
|
- Separate reflog storage for base refs and historical logs.
|
|
|
|
|
|
|
|
### Description
|
|
|
|
|
|
|
|
A reftable file is a portable binary file format customized for
|
|
|
|
reference storage. References are sorted, enabling linear scans,
|
|
|
|
binary search lookup, and range scans.
|
|
|
|
|
|
|
|
Storage in the file is organized into variable sized blocks. Prefix
|
|
|
|
compression is used within a single block to reduce disk space. Block
|
|
|
|
size and alignment is tunable by the writer.
|
|
|
|
|
|
|
|
### Performance
|
|
|
|
|
|
|
|
Space used, packed-refs vs. reftable:
|
|
|
|
|
|
|
|
repository | packed-refs | reftable | % original | avg ref | avg obj
|
|
|
|
-----------|------------:|---------:|-----------:|---------:|--------:
|
|
|
|
android | 62.2 M | 36.1 M | 58.0% | 33 bytes | 5 bytes
|
|
|
|
rails | 1.8 M | 1.1 M | 57.7% | 29 bytes | 4 bytes
|
|
|
|
git | 78.7 K | 48.1 K | 61.0% | 50 bytes | 4 bytes
|
|
|
|
git (heads)| 332 b | 269 b | 81.0% | 33 bytes | 0 bytes
|
|
|
|
|
|
|
|
Scan (read 866k refs), by reference name lookup (single ref from 866k
|
|
|
|
refs), and by SHA-1 lookup (refs with that SHA-1, from 866k refs):
|
|
|
|
|
|
|
|
format | cache | scan | by name | by SHA-1
|
|
|
|
------------|------:|--------:|---------------:|---------------:
|
|
|
|
packed-refs | cold | 402 ms | 409,660.1 usec | 412,535.8 usec
|
|
|
|
packed-refs | hot | | 6,844.6 usec | 20,110.1 usec
|
|
|
|
reftable | cold | 112 ms | 33.9 usec | 323.2 usec
|
|
|
|
reftable | hot | | 20.2 usec | 320.8 usec
|
|
|
|
|
|
|
|
Space used for 149,932 log entries for 43,061 refs,
|
|
|
|
reflog vs. reftable:
|
|
|
|
|
|
|
|
format | size | avg entry
|
|
|
|
--------------|------:|-----------:
|
|
|
|
$GIT_DIR/logs | 173 M | 1209 bytes
|
|
|
|
reftable | 5 M | 37 bytes
|
|
|
|
|
|
|
|
## Details
|
|
|
|
|
|
|
|
### Peeling
|
|
|
|
|
|
|
|
References stored in a reftable are peeled, a record for an annotated
|
|
|
|
(or signed) tag records both the tag object, and the object it refers
|
|
|
|
to.
|
|
|
|
|
|
|
|
### Reference name encoding
|
|
|
|
|
|
|
|
Reference names are an uninterpreted sequence of bytes that must pass
|
|
|
|
[git-check-ref-format][ref-fmt] as a valid reference name.
|
|
|
|
|
|
|
|
[ref-fmt]: https://git-scm.com/docs/git-check-ref-format
|
|
|
|
|
|
|
|
### Key unicity
|
|
|
|
|
|
|
|
Each entry must have a unique key; repeated keys are disallowed.
|
|
|
|
|
|
|
|
### Network byte order
|
|
|
|
|
|
|
|
All multi-byte, fixed width fields are in network byte order.
|
|
|
|
|
|
|
|
### Ordering
|
|
|
|
|
|
|
|
Blocks are lexicographically ordered by their first reference.
|
|
|
|
|
|
|
|
### Directory/file conflicts
|
|
|
|
|
|
|
|
The reftable format accepts both `refs/heads/foo` and
|
|
|
|
`refs/heads/foo/bar` as distinct references.
|
|
|
|
|
|
|
|
This property is useful for retaining log records in reftable, but may
|
|
|
|
confuse versions of Git using `$GIT_DIR/refs` directory tree to
|
|
|
|
maintain references. Users of reftable may choose to continue to
|
|
|
|
reject `foo` and `foo/bar` type conflicts to prevent problems for
|
|
|
|
peers.
|
|
|
|
|
|
|
|
## File format
|
|
|
|
|
|
|
|
### Structure
|
|
|
|
|
|
|
|
A reftable file has the following high-level structure:
|
|
|
|
|
|
|
|
first_block {
|
|
|
|
header
|
|
|
|
first_ref_block
|
|
|
|
}
|
|
|
|
ref_block*
|
|
|
|
ref_index*
|
|
|
|
obj_block*
|
|
|
|
obj_index*
|
|
|
|
log_block*
|
|
|
|
log_index*
|
|
|
|
footer
|
|
|
|
|
|
|
|
A log-only file omits the `ref_block`, `ref_index`, `obj_block` and
|
|
|
|
`obj_index` sections, containing only the file header and log block:
|
|
|
|
|
|
|
|
first_block {
|
|
|
|
header
|
|
|
|
}
|
|
|
|
log_block*
|
|
|
|
log_index*
|
|
|
|
footer
|
|
|
|
|
|
|
|
in a log-only file the first log block immediately follows the file
|
|
|
|
header, without padding to block alignment.
|
|
|
|
|
|
|
|
### Block size
|
|
|
|
|
|
|
|
The file's block size is arbitrarily determined by the writer, and
|
|
|
|
does not have to be a power of 2. The block size must be larger than
|
|
|
|
the longest reference name or log entry used in the repository, as
|
|
|
|
references cannot span blocks.
|
|
|
|
|
|
|
|
Powers of two that are friendly to the virtual memory system or
|
|
|
|
filesystem (such as 4k or 8k) are recommended. Larger sizes (64k) can
|
|
|
|
yield better compression, with a possible increased cost incurred by
|
|
|
|
readers during access.
|
|
|
|
|
|
|
|
The largest block size is `16777215` bytes (15.99 MiB).
|
|
|
|
|
|
|
|
### Block alignment
|
|
|
|
|
|
|
|
Writers may choose to align blocks at multiples of the block size by
|
|
|
|
including `padding` filled with NUL bytes at the end of a block to
|
|
|
|
round out to the chosen alignment. When alignment is used, writers
|
|
|
|
must specify the alignment with the file header's `block_size` field.
|
|
|
|
|
|
|
|
Block alignment is not required by the file format. Unaligned files
|
|
|
|
must set `block_size = 0` in the file header, and omit `padding`.
|
|
|
|
Unaligned files with more than one ref block must include the
|
|
|
|
[ref index](#Ref-index) to support fast lookup. Readers must be
|
|
|
|
able to read both aligned and non-aligned files.
|
|
|
|
|
|
|
|
Very small files (e.g. 1 only ref block) may omit `padding` and the
|
|
|
|
ref index to reduce total file size.
|
|
|
|
|
|
|
|
### Header
|
|
|
|
|
|
|
|
A 24-byte header appears at the beginning of the file:
|
|
|
|
|
|
|
|
'REFT'
|
|
|
|
uint8( version_number = 1 )
|
|
|
|
uint24( block_size )
|
|
|
|
uint64( min_update_index )
|
|
|
|
uint64( max_update_index )
|
|
|
|
|
|
|
|
Aligned files must specify `block_size` to configure readers with the
|
|
|
|
expected block alignment. Unaligned files must set `block_size = 0`.
|
|
|
|
|
|
|
|
The `min_update_index` and `max_update_index` describe bounds for the
|
|
|
|
`update_index` field of all log records in this file. When reftables
|
|
|
|
are used in a stack for [transactions](#Update-transactions), these
|
|
|
|
fields can order the files such that the prior file's
|
|
|
|
`max_update_index + 1` is the next file's `min_update_index`.
|
|
|
|
|
|
|
|
### First ref block
|
|
|
|
|
|
|
|
The first ref block shares the same block as the file header, and is
|
|
|
|
24 bytes smaller than all other blocks in the file. The first block
|
|
|
|
immediately begins after the file header, at position 24.
|
|
|
|
|
|
|
|
If the first block is a log block (a log-only file), its block header
|
|
|
|
begins immediately at position 24.
|
|
|
|
|
|
|
|
### Ref block format
|
|
|
|
|
|
|
|
A ref block is written as:
|
|
|
|
|
|
|
|
'r'
|
|
|
|
uint24( block_len )
|
|
|
|
ref_record+
|
|
|
|
uint24( restart_offset )+
|
|
|
|
uint16( restart_count )
|
|
|
|
|
|
|
|
padding?
|
|
|
|
|
|
|
|
Blocks begin with `block_type = 'r'` and a 3-byte `block_len` which
|
|
|
|
encodes the number of bytes in the block up to, but not including the
|
|
|
|
optional `padding`. This is always less than or equal to the file's
|
|
|
|
block size. In the first ref block, `block_len` includes 24 bytes
|
|
|
|
for the file header.
|
|
|
|
|
|
|
|
The 2-byte `restart_count` stores the number of entries in the
|
|
|
|
`restart_offset` list, which must not be empty. Readers can use
|
|
|
|
`restart_count` to binary search between restarts before starting a
|
|
|
|
linear scan.
|
|
|
|
|
|
|
|
Exactly `restart_count` 3-byte `restart_offset` values precedes the
|
|
|
|
`restart_count`. Offsets are relative to the start of the block and
|
|
|
|
refer to the first byte of any `ref_record` whose name has not been
|
|
|
|
prefix compressed. Entries in the `restart_offset` list must be
|
|
|
|
sorted, ascending. Readers can start linear scans from any of these
|
|
|
|
records.
|
|
|
|
|
|
|
|
A variable number of `ref_record` fill the middle of the block,
|
|
|
|
describing reference names and values. The format is described below.
|
|
|
|
|
|
|
|
As the first ref block shares the first file block with the file
|
|
|
|
header, all `restart_offset` in the first block are relative to the
|
|
|
|
start of the file (position 0), and include the file header. This
|
|
|
|
forces the first `restart_offset` to be `28`.
|
|
|
|
|
|
|
|
#### ref record
|
|
|
|
|
|
|
|
A `ref_record` describes a single reference, storing both the name and
|
|
|
|
its value(s). Records are formatted as:
|
|
|
|
|
|
|
|
varint( prefix_length )
|
|
|
|
varint( (suffix_length << 3) | value_type )
|
|
|
|
suffix
|
|
|
|
varint( update_index_delta )
|
|
|
|
value?
|
|
|
|
|
|
|
|
The `prefix_length` field specifies how many leading bytes of the
|
|
|
|
prior reference record's name should be copied to obtain this
|
|
|
|
reference's name. This must be 0 for the first reference in any
|
|
|
|
block, and also must be 0 for any `ref_record` whose offset is listed
|
|
|
|
in the `restart_offset` table at the end of the block.
|
|
|
|
|
|
|
|
Recovering a reference name from any `ref_record` is a simple concat:
|
|
|
|
|
|
|
|
this_name = prior_name[0..prefix_length] + suffix
|
|
|
|
|
|
|
|
The `suffix_length` value provides the number of bytes available in
|
|
|
|
`suffix` to copy from `suffix` to complete the reference name.
|
|
|
|
|
|
|
|
The `update_index` that last modified the reference can be obtained by
|
|
|
|
adding `update_index_delta` to the `min_update_index` from the file
|
|
|
|
header: `min_update_index + update_index_delta`.
|
|
|
|
|
|
|
|
The `value` follows. Its format is determined by `value_type`, one of
|
|
|
|
the following:
|
|
|
|
|
|
|
|
- `0x0`: deletion; no value data (see transactions, below)
|
|
|
|
- `0x1`: one 20-byte object id; value of the ref
|
|
|
|
- `0x2`: two 20-byte object ids; value of the ref, peeled target
|
|
|
|
- `0x3`: symbolic reference: `varint( target_len ) target`
|
|
|
|
|
|
|
|
Symbolic references use `0x3`, followed by the complete name of the
|
|
|
|
reference target. No compression is applied to the target name.
|
|
|
|
|
|
|
|
Types `0x4..0x7` are reserved for future use.
|
|
|
|
|
|
|
|
### Ref index
|
|
|
|
|
|
|
|
The ref index stores the name of the last reference from every ref
|
|
|
|
block in the file, enabling reduced disk seeks for lookups. Any
|
|
|
|
reference can be found by searching the index, identifying the
|
|
|
|
containing block, and searching within that block.
|
|
|
|
|
|
|
|
The index may be organized into a multi-level index, where the 1st
|
|
|
|
level index block points to additional ref index blocks (2nd level),
|
|
|
|
which may in turn point to either additional index blocks (e.g. 3rd
|
|
|
|
level) or ref blocks (leaf level). Disk reads required to access a
|
|
|
|
ref go up with higher index levels. Multi-level indexes may be
|
|
|
|
required to ensure no single index block exceeds the file format's max
|
|
|
|
block size of `16777215` bytes (15.99 MiB). To acheive constant O(1)
|
|
|
|
disk seeks for lookups the index must be a single level, which is
|
|
|
|
permitted to exceed the file's configured block size, but not the
|
|
|
|
format's max block size of 15.99 MiB.
|
|
|
|
|
|
|
|
If present, the ref index block(s) appears after the last ref block.
|
|
|
|
|
|
|
|
If there are at least 4 ref blocks, a ref index block should be
|
|
|
|
written to improve lookup times. Cold reads using the index require
|
|
|
|
2 disk reads (read index, read block), and binary searching < 4 blocks
|
|
|
|
also requires <= 2 reads. Omitting the index block from smaller files
|
|
|
|
saves space.
|
|
|
|
|
|
|
|
If the file is unaligned and contains more than one ref block, the ref
|
|
|
|
index must be written.
|
|
|
|
|
|
|
|
Index block format:
|
|
|
|
|
|
|
|
'i'
|
|
|
|
uint24( block_len )
|
|
|
|
index_record+
|
|
|
|
uint24( restart_offset )+
|
|
|
|
uint16( restart_count )
|
|
|
|
|
|
|
|
padding?
|
|
|
|
|
|
|
|
The index blocks begin with `block_type = 'i'` and a 3-byte
|
|
|
|
`block_len` which encodes the number of bytes in the block,
|
|
|
|
up to but not including the optional `padding`.
|
|
|
|
|
|
|
|
The `restart_offset` and `restart_count` fields are identical in
|
|
|
|
format, meaning and usage as in ref blocks.
|
|
|
|
|
|
|
|
To reduce the number of reads required for random access in very large
|
|
|
|
files the index block may be larger than other blocks. However,
|
|
|
|
readers must hold the entire index in memory to benefit from this, so
|
|
|
|
it's a time-space tradeoff in both file size and reader memory.
|
|
|
|
|
|
|
|
Increasing the file's block size decreases the index size.
|
|
|
|
Alternatively a multi-level index may be used, keeping index blocks
|
|
|
|
within the file's block size, but increasing the number of blocks
|
|
|
|
that need to be accessed.
|
|
|
|
|
|
|
|
#### index record
|
|
|
|
|
|
|
|
An index record describes the last entry in another block.
|
|
|
|
Index records are written as:
|
|
|
|
|
|
|
|
varint( prefix_length )
|
|
|
|
varint( (suffix_length << 3) | 0 )
|
|
|
|
suffix
|
|
|
|
varint( block_position )
|
|
|
|
|
|
|
|
Index records use prefix compression exactly like `ref_record`.
|
|
|
|
|
|
|
|
Index records store `block_position` after the suffix, specifying the
|
|
|
|
absolute position in bytes (from the start of the file) of the block
|
|
|
|
that ends with this reference. Readers can seek to `block_position` to
|
|
|
|
begin reading the block header.
|
|
|
|
|
|
|
|
Readers must examine the block header at `block_position` to determine
|
|
|
|
if the next block is another level index block, or the leaf-level ref
|
|
|
|
block.
|
|
|
|
|
|
|
|
#### Reading the index
|
|
|
|
|
|
|
|
Readers loading the ref index must first read the footer (below) to
|
|
|
|
obtain `ref_index_position`. If not present, the position will be 0.
|
|
|
|
The `ref_index_position` is for the 1st level root of the ref index.
|
|
|
|
|
|
|
|
### Obj block format
|
|
|
|
|
|
|
|
Object blocks are optional. Writers may choose to omit object blocks,
|
|
|
|
especially if readers will not use the SHA-1 to ref mapping.
|
|
|
|
|
|
|
|
Object blocks use unique, abbreviated 2-20 byte SHA-1 keys, mapping
|
|
|
|
to ref blocks containing references pointing to that object directly,
|
|
|
|
or as the peeled value of an annotated tag. Like ref blocks, object
|
|
|
|
blocks use the file's standard block size. The abbrevation length is
|
|
|
|
available in the footer as `obj_id_len`.
|
|
|
|
|
|
|
|
To save space in small files, object blocks may be omitted if the ref
|
|
|
|
index is not present, as brute force search will only need to read a
|
|
|
|
few ref blocks. When missing, readers should brute force a linear
|
|
|
|
search of all references to lookup by SHA-1.
|
|
|
|
|
|
|
|
An object block is written as:
|
|
|
|
|
|
|
|
'o'
|
|
|
|
uint24( block_len )
|
|
|
|
obj_record+
|
|
|
|
uint24( restart_offset )+
|
|
|
|
uint16( restart_count )
|
|
|
|
|
|
|
|
padding?
|
|
|
|
|
|
|
|
Fields are identical to ref block. Binary search using the restart
|
|
|
|
table works the same as in reference blocks.
|
|
|
|
|
|
|
|
Because object identifiers are abbreviated by writers to the shortest
|
|
|
|
unique abbreviation within the reftable, obj key lengths are variable
|
|
|
|
between 2 and 20 bytes. Readers must compare only for common prefix
|
|
|
|
match within an obj block or obj index.
|
|
|
|
|
|
|
|
#### obj record
|
|
|
|
|
|
|
|
An `obj_record` describes a single object abbreviation, and the blocks
|
|
|
|
containing references using that unique abbreviation:
|
|
|
|
|
|
|
|
varint( prefix_length )
|
|
|
|
varint( (suffix_length << 3) | cnt_3 )
|
|
|
|
suffix
|
|
|
|
varint( cnt_large )?
|
|
|
|
varint( position_delta )*
|
|
|
|
|
|
|
|
Like in reference blocks, abbreviations are prefix compressed within
|
|
|
|
an obj block. On large reftables with many unique objects, higher
|
|
|
|
block sizes (64k), and higher restart interval (128), a
|
|
|
|
`prefix_length` of 2 or 3 and `suffix_length` of 3 may be common in
|
|
|
|
obj records (unique abbreviation of 5-6 raw bytes, 10-12 hex digits).
|
|
|
|
|
|
|
|
Each record contains `position_count` number of positions for matching
|
|
|
|
ref blocks. For 1-7 positions the count is stored in `cnt_3`. When
|
|
|
|
`cnt_3 = 0` the actual count follows in a varint, `cnt_large`.
|
|
|
|
|
|
|
|
The use of `cnt_3` bets most objects are pointed to by only a single
|
|
|
|
reference, some may be pointed to by a couple of references, and very
|
|
|
|
few (if any) are pointed to by more than 7 references.
|
|
|
|
|
|
|
|
A special case exists when `cnt_3 = 0` and `cnt_large = 0`: there
|
|
|
|
are no `position_delta`, but at least one reference starts with this
|
|
|
|
abbreviation. A reader that needs exact reference names must scan all
|
|
|
|
references to find which specific references have the desired object.
|
|
|
|
Writers should use this format when the `position_delta` list would have
|
|
|
|
overflowed the file's block size due to a high number of references
|
|
|
|
pointing to the same object.
|
|
|
|
|
|
|
|
The first `position_delta` is the position from the start of the file.
|
|
|
|
Additional `position_delta` entries are sorted ascending and relative
|
|
|
|
to the prior entry, e.g. a reader would perform:
|
|
|
|
|
|
|
|
pos = position_delta[0]
|
|
|
|
prior = pos
|
|
|
|
for (j = 1; j < position_count; j++) {
|
|
|
|
pos = prior + position_delta[j]
|
|
|
|
prior = pos
|
|
|
|
}
|
|
|
|
|
|
|
|
With a position in hand, a reader must linearly scan the ref block,
|
|
|
|
starting from the first `ref_record`, testing each reference's SHA-1s
|
|
|
|
(for `value_type = 0x1` or `0x2`) for full equality. Faster searching
|
|
|
|
by SHA-1 within a single ref block is not supported by the reftable
|
|
|
|
format. Smaller block sizes reduce the number of candidates this step
|
|
|
|
must consider.
|
|
|
|
|
|
|
|
### Obj index
|
|
|
|
|
|
|
|
The obj index stores the abbreviation from the last entry for every
|
|
|
|
obj block in the file, enabling reduced disk seeks for all lookups.
|
|
|
|
It is formatted exactly the same as the ref index, but refers to obj
|
|
|
|
blocks.
|
|
|
|
|
|
|
|
The obj index should be present if obj blocks are present, as
|
|
|
|
obj blocks should only be written in larger files.
|
|
|
|
|
|
|
|
Readers loading the obj index must first read the footer (below) to
|
|
|
|
obtain `obj_index_position`. If not present, the position will be 0.
|
|
|
|
|
|
|
|
### Log block format
|
|
|
|
|
|
|
|
Unlike ref and obj blocks, log blocks are always unaligned.
|
|
|
|
|
|
|
|
Log blocks are variable in size, and do not match the `block_size`
|
|
|
|
specified in the file header or footer. Writers should choose an
|
|
|
|
appropriate buffer size to prepare a log block for deflation, such as
|
|
|
|
`2 * block_size`.
|
|
|
|
|
|
|
|
A log block is written as:
|
|
|
|
|
|
|
|
'g'
|
|
|
|
uint24( block_len )
|
|
|
|
zlib_deflate {
|
|
|
|
log_record+
|
|
|
|
uint24( restart_offset )+
|
|
|
|
uint16( restart_count )
|
|
|
|
}
|
|
|
|
|
|
|
|
Log blocks look similar to ref blocks, except `block_type = 'g'`.
|
|
|
|
|
|
|
|
The 4-byte block header is followed by the deflated block contents
|
|
|
|
using zlib deflate. The `block_len` in the header is the inflated
|
|
|
|
size (including 4-byte block header), and should be used by readers to
|
|
|
|
preallocate the inflation output buffer. A log block's `block_len`
|
|
|
|
may exceed the file's block size.
|
|
|
|
|
|
|
|
Offsets within the log block (e.g. `restart_offset`) still include
|
|
|
|
the 4-byte header. Readers may prefer prefixing the inflation output
|
|
|
|
buffer with the 4-byte header.
|
|
|
|
|
|
|
|
Within the deflate container, a variable number of `log_record`
|
|
|
|
describe reference changes. The log record format is described
|
|
|
|
below. See ref block format (above) for a description of
|
|
|
|
`restart_offset` and `restart_count`.
|
|
|
|
|
|
|
|
Because log blocks have no alignment or padding between blocks,
|
|
|
|
readers must keep track of the bytes consumed by the inflater to
|
|
|
|
know where the next log block begins.
|
|
|
|
|
|
|
|
#### log record
|
|
|
|
|
|
|
|
Log record keys are structured as:
|
|
|
|
|
|
|
|
ref_name '\0' reverse_int64( update_index )
|
|
|
|
|
|
|
|
where `update_index` is the unique transaction identifier. The
|
|
|
|
`update_index` field must be unique within the scope of a `ref_name`.
|
|
|
|
See the update transactions section below for further details.
|
|
|
|
|
|
|
|
The `reverse_int64` function inverses the value so lexographical
|
|
|
|
ordering the network byte order encoding sorts the more recent records
|
|
|
|
with higher `update_index` values first:
|
|
|
|
|
|
|
|
reverse_int64(int64 t) {
|
|
|
|
return 0xffffffffffffffff - t;
|
|
|
|
}
|
|
|
|
|
|
|
|
Log records have a similar starting structure to ref and index
|
|
|
|
records, utilizing the same prefix compression scheme applied to the
|
|
|
|
log record key described above.
|
|
|
|
|
|
|
|
```
|
|
|
|
varint( prefix_length )
|
|
|
|
varint( (suffix_length << 3) | log_type )
|
|
|
|
suffix
|
|
|
|
log_data {
|
|
|
|
old_id
|
|
|
|
new_id
|
|
|
|
varint( name_length ) name
|
|
|
|
varint( email_length ) email
|
|
|
|
varint( time_seconds )
|
|
|
|
sint16( tz_offset )
|
|
|
|
varint( message_length ) message
|
|
|
|
}?
|
|
|
|
```
|
|
|
|
|
|
|
|
Log record entries use `log_type` to indicate what follows:
|
|
|
|
|
|
|
|
- `0x0`: deletion; no log data.
|
|
|
|
- `0x1`: standard git reflog data using `log_data` above.
|
|
|
|
|
|
|
|
The `log_type = 0x0` is mostly useful for `git stash drop`, removing
|
|
|
|
an entry from the reflog of `refs/stash` in a transaction file
|
|
|
|
(below), without needing to rewrite larger files. Readers reading a
|
|
|
|
stack of reflogs must treat this as a deletion.
|
|
|
|
|
|
|
|
For `log_type = 0x1`, the `log_data` section follows
|
|
|
|
[git update-ref][update-ref] logging, and includes:
|
|
|
|
|
|
|
|
- two 20-byte SHA-1s (old id, new id)
|
|
|
|
- varint string of committer's name
|
|
|
|
- varint string of committer's email
|
|
|
|
- varint time in seconds since epoch (Jan 1, 1970)
|
|
|
|
- 2-byte timezone offset in minutes (signed)
|
|
|
|
- varint string of message
|
|
|
|
|
|
|
|
`tz_offset` is the absolute number of minutes from GMT the committer
|
|
|
|
was at the time of the update. For example `GMT-0800` is encoded in
|
|
|
|
reftable as `sint16(-480)` and `GMT+0230` is `sint16(150)`.
|
|
|
|
|
|
|
|
The committer email does not contain `<` or `>`, it's the value
|
|
|
|
normally found between the `<>` in a git commit object header.
|
|
|
|
|
|
|
|
The `message_length` may be 0, in which case there was no message
|
|
|
|
supplied for the update.
|
|
|
|
|
|
|
|
[update-ref]: https://git-scm.com/docs/git-update-ref#_logging_updates
|
|
|
|
|
|
|
|
Contrary to traditional reflog (which is a file), renames are encoded as a
|
|
|
|
combination of ref deletion and ref creation.
|
|
|
|
|
|
|
|
|
|
|
|
#### Reading the log
|
|
|
|
|
|
|
|
Readers accessing the log must first read the footer (below) to
|
|
|
|
determine the `log_position`. The first block of the log begins at
|
|
|
|
`log_position` bytes since the start of the file. The `log_position`
|
|
|
|
is not block aligned.
|
|
|
|
|
|
|
|
#### Importing logs
|
|
|
|
|
|
|
|
When importing from `$GIT_DIR/logs` writers should globally order all
|
|
|
|
log records roughly by timestamp while preserving file order, and
|
|
|
|
assign unique, increasing `update_index` values for each log line.
|
|
|
|
Newer log records get higher `update_index` values.
|
|
|
|
|
|
|
|
Although an import may write only a single reftable file, the reftable
|
|
|
|
file must span many unique `update_index`, as each log line requires
|
|
|
|
its own `update_index` to preserve semantics.
|
|
|
|
|
|
|
|
### Log index
|
|
|
|
|
|
|
|
The log index stores the log key (`refname \0 reverse_int64(update_index)`)
|
|
|
|
for the last log record of every log block in the file, supporting
|
|
|
|
bounded-time lookup.
|
|
|
|
|
|
|
|
A log index block must be written if 2 or more log blocks are written
|
|
|
|
to the file. If present, the log index appears after the last log
|
|
|
|
block. There is no padding used to align the log index to block
|
|
|
|
alignment.
|
|
|
|
|
|
|
|
Log index format is identical to ref index, except the keys are 9
|
|
|
|
bytes longer to include `'\0'` and the 8-byte
|
|
|
|
`reverse_int64(update_index)`. Records use `block_position` to
|
|
|
|
refer to the start of a log block.
|
|
|
|
|
|
|
|
#### Reading the index
|
|
|
|
|
|
|
|
Readers loading the log index must first read the footer (below) to
|
|
|
|
obtain `log_index_position`. If not present, the position will be 0.
|
|
|
|
|
|
|
|
### Footer
|
|
|
|
|
|
|
|
After the last block of the file, a file footer is written. It begins
|
|
|
|
like the file header, but is extended with additional data.
|
|
|
|
|
|
|
|
A 68-byte footer appears at the end:
|
|
|
|
|
|
|
|
```
|
|
|
|
'REFT'
|
|
|
|
uint8( version_number = 1 )
|
|
|
|
uint24( block_size )
|
|
|
|
uint64( min_update_index )
|
|
|
|
uint64( max_update_index )
|
|
|
|
|
|
|
|
uint64( ref_index_position )
|
|
|
|
uint64( (obj_position << 5) | obj_id_len )
|
|
|
|
uint64( obj_index_position )
|
|
|
|
|
|
|
|
uint64( log_position )
|
|
|
|
uint64( log_index_position )
|
|
|
|
|
|
|
|
uint32( CRC-32 of above )
|
|
|
|
```
|
|
|
|
|
|
|
|
If a section is missing (e.g. ref index) the corresponding position
|
|
|
|
field (e.g. `ref_index_position`) will be 0.
|
|
|
|
|
|
|
|
- `obj_position`: byte position for the first obj block.
|
|
|
|
- `obj_id_len`: number of bytes used to abbreviate object identifiers
|
|
|
|
in obj blocks.
|
|
|
|
- `log_position`: byte position for the first log block.
|
|
|
|
- `ref_index_position`: byte position for the start of the ref index.
|
|
|
|
- `obj_index_position`: byte position for the start of the obj index.
|
|
|
|
- `log_index_position`: byte position for the start of the log index.
|
|
|
|
|
|
|
|
#### Reading the footer
|
|
|
|
|
|
|
|
Readers must seek to `file_length - 68` to access the footer. A
|
|
|
|
trusted external source (such as `stat(2)`) is necessary to obtain
|
|
|
|
`file_length`. When reading the footer, readers must verify:
|
|
|
|
|
|
|
|
- 4-byte magic is correct
|
|
|
|
- 1-byte version number is recognized
|
|
|
|
- 4-byte CRC-32 matches the other 64 bytes (including magic, and version)
|
|
|
|
|
|
|
|
Once verified, the other fields of the footer can be accessed.
|
|
|
|
|
|
|
|
### Varint encoding
|
|
|
|
|
|
|
|
Varint encoding is identical to the ofs-delta encoding method used
|
|
|
|
within pack files.
|
|
|
|
|
|
|
|
Decoder works such as:
|
|
|
|
|
|
|
|
val = buf[ptr] & 0x7f
|
|
|
|
while (buf[ptr] & 0x80) {
|
|
|
|
ptr++
|
|
|
|
val = ((val + 1) << 7) | (buf[ptr] & 0x7f)
|
|
|
|
}
|
|
|
|
|
|
|
|
### Binary search
|
|
|
|
|
|
|
|
Binary search within a block is supported by the `restart_offset`
|
|
|
|
fields at the end of the block. Readers can binary search through the
|
|
|
|
restart table to locate between which two restart points the sought
|
|
|
|
reference or key should appear.
|
|
|
|
|
|
|
|
Each record identified by a `restart_offset` stores the complete key
|
|
|
|
in the `suffix` field of the record, making the compare operation
|
|
|
|
during binary search straightforward.
|
|
|
|
|
|
|
|
Once a restart point lexicographically before the sought reference has
|
|
|
|
been identified, readers can linearly scan through the following
|
|
|
|
record entries to locate the sought record, terminating if the current
|
|
|
|
record sorts after (and therefore the sought key is not present).
|
|
|
|
|
|
|
|
#### Restart point selection
|
|
|
|
|
|
|
|
Writers determine the restart points at file creation. The process is
|
|
|
|
arbitrary, but every 16 or 64 records is recommended. Every 16 may
|
|
|
|
be more suitable for smaller block sizes (4k or 8k), every 64 for
|
|
|
|
larger block sizes (64k).
|
|
|
|
|
|
|
|
More frequent restart points reduces prefix compression and increases
|
|
|
|
space consumed by the restart table, both of which increase file size.
|
|
|
|
|
|
|
|
Less frequent restart points makes prefix compression more effective,
|
|
|
|
decreasing overall file size, with increased penalities for readers
|
|
|
|
walking through more records after the binary search step.
|
|
|
|
|
|
|
|
A maximum of `65535` restart points per block is supported.
|
|
|
|
|
|
|
|
## Considerations
|
|
|
|
|
|
|
|
### Lightweight refs dominate
|
|
|
|
|
|
|
|
The reftable format assumes the vast majority of references are single
|
|
|
|
SHA-1 valued with common prefixes, such as Gerrit Code Review's
|
|
|
|
`refs/changes/` namespace, GitHub's `refs/pulls/` namespace, or many
|
|
|
|
lightweight tags in the `refs/tags/` namespace.
|
|
|
|
|
|
|
|
Annotated tags storing the peeled object cost an additional 20 bytes
|
|
|
|
per reference.
|
|
|
|
|
|
|
|
### Low overhead
|
|
|
|
|
|
|
|
A reftable with very few references (e.g. git.git with 5 heads)
|
|
|
|
is 269 bytes for reftable, vs. 332 bytes for packed-refs. This
|
|
|
|
supports reftable scaling down for transaction logs (below).
|
|
|
|
|
|
|
|
### Block size
|
|
|
|
|
|
|
|
For a Gerrit Code Review type repository with many change refs, larger
|
|
|
|
block sizes (64 KiB) and less frequent restart points (every 64) yield
|
|
|
|
better compression due to more references within the block compressing
|
|
|
|
against the prior reference.
|
|
|
|
|
|
|
|
Larger block sizes reduce the index size, as the reftable will
|
|
|
|
require fewer blocks to store the same number of references.
|
|
|
|
|
|
|
|
### Minimal disk seeks
|
|
|
|
|
|
|
|
Assuming the index block has been loaded into memory, binary searching
|
|
|
|
for any single reference requires exactly 1 disk seek to load the
|
|
|
|
containing block.
|
|
|
|
|
|
|
|
### Scans and lookups dominate
|
|
|
|
|
|
|
|
Scanning all references and lookup by name (or namespace such as
|
|
|
|
`refs/heads/`) are the most common activities performed on repositories.
|
|
|
|
SHA-1s are stored directly with references to optimize this use case.
|
|
|
|
|
|
|
|
### Logs are infrequently read
|
|
|
|
|
|
|
|
Logs are infrequently accessed, but can be large. Deflating log
|
|
|
|
blocks saves disk space, with some increased penalty at read time.
|
|
|
|
|
|
|
|
Logs are stored in an isolated section from refs, reducing the burden
|
|
|
|
on reference readers that want to ignore logs. Further, historical
|
|
|
|
logs can be isolated into log-only files.
|
|
|
|
|
|
|
|
### Logs are read backwards
|
|
|
|
|
|
|
|
Logs are frequently accessed backwards (most recent N records for
|
|
|
|
master to answer `master@{4}`), so log records are grouped by
|
|
|
|
reference, and sorted descending by update index.
|
|
|
|
|
|
|
|
## Repository format
|
|
|
|
|
|
|
|
### Version 1
|
|
|
|
|
|
|
|
A repository must set its `$GIT_DIR/config` to configure reftable:
|
|
|
|
|
|
|
|
[core]
|
|
|
|
repositoryformatversion = 1
|
|
|
|
[extensions]
|
|
|
|
refStorage = reftable
|
|
|
|
|
|
|
|
### Layout
|
|
|
|
|
|
|
|
A collection of reftable files are stored in the `$GIT_DIR/reftable/`
|
|
|
|
directory:
|
|
|
|
|
|
|
|
00000001-00000001.log
|
|
|
|
00000002-00000002.ref
|
|
|
|
00000003-00000003.ref
|
|
|
|
|
|
|
|
where reftable files are named by a unique name such as produced by
|
|
|
|
the function `${min_update_index}-${max_update_index}.ref`.
|
|
|
|
|
|
|
|
Log-only files use the `.log` extension, while ref-only and mixed ref
|
|
|
|
and log files use `.ref`. extension.
|
|
|
|
|
|
|
|
|
|
|
|
The stack ordering file is `$GIT_DIR/reftable/tables.list` and lists the current
|
|
|
|
files, one per line, in order, from oldest (base) to newest (most recent):
|
|
|
|
|
|
|
|
$ cat .git/reftable/tables.list
|
|
|
|
00000001-00000001.log
|
|
|
|
00000002-00000002.ref
|
|
|
|
00000003-00000003.ref
|
|
|
|
|
|
|
|
Readers must read `$GIT_DIR/reftable/tables.list` to determine which files are
|
|
|
|
relevant right now, and search through the stack in reverse order (last reftable
|
|
|
|
is examined first).
|
|
|
|
|
|
|
|
Reftable files not listed in `tables.list` may be new (and about to be added
|
|
|
|
to the stack by the active writer), or ancient and ready to be pruned.
|
|
|
|
|
|
|
|
### Backward compatibility
|
|
|
|
|
|
|
|
Older clients should continue to recognize the directory as a git repository so
|
|
|
|
they don't look for an enclosing repository in parent directories. To this end,
|
|
|
|
a reftable-enabled repository must contain the following dummy files
|
|
|
|
|
|
|
|
* `.git/HEAD`, a regular file containing `ref: refs/heads/.invalid`.
|
|
|
|
* `.git/refs/`, a directory
|
|
|
|
* `.git/refs/heads`, a regular file
|
|
|
|
|
|
|
|
### Readers
|
|
|
|
|
|
|
|
Readers can obtain a consistent snapshot of the reference space by
|
|
|
|
following:
|
|
|
|
|
|
|
|
1. Open and read the `tables.list` file.
|
|
|
|
2. Open each of the reftable files that it mentions.
|
|
|
|
3. If any of the files is missing, goto 1.
|
|
|
|
4. Read from the now-open files as long as necessary.
|
|
|
|
|
|
|
|
### Update transactions
|
|
|
|
|
|
|
|
Although reftables are immutable, mutations are supported by writing a
|
|
|
|
new reftable and atomically appending it to the stack:
|
|
|
|
|
|
|
|
1. Acquire `tables.list.lock`.
|
|
|
|
2. Read `tables.list` to determine current reftables.
|
|
|
|
3. Select `update_index` to be most recent file's `max_update_index + 1`.
|
|
|
|
4. Prepare temp reftable `tmp_XXXXXX`, including log entries.
|
|
|
|
5. Rename `tmp_XXXXXX` to `${update_index}-${update_index}.ref`.
|
|
|
|
6. Copy `tables.list` to `tables.list.lock`, appending file from (5).
|
|
|
|
7. Rename `tables.list.lock` to `tables.list`.
|
|
|
|
|
|
|
|
During step 4 the new file's `min_update_index` and `max_update_index`
|
|
|
|
are both set to the `update_index` selected by step 3. All log
|
|
|
|
records for the transaction use the same `update_index` in their keys.
|
|
|
|
This enables later correlation of which references were updated by the
|
|
|
|
same transaction.
|
|
|
|
|
|
|
|
Because a single `tables.list.lock` file is used to manage locking, the
|
|
|
|
repository is single-threaded for writers. Writers may have to
|
|
|
|
busy-spin (with backoff) around creating `tables.list.lock`, for up to an
|
|
|
|
acceptable wait period, aborting if the repository is too busy to
|
|
|
|
mutate. Application servers wrapped around repositories (e.g. Gerrit
|
|
|
|
Code Review) can layer their own lock/wait queue to improve fairness
|
|
|
|
to writers.
|
|
|
|
|
|
|
|
### Reference deletions
|
|
|
|
|
|
|
|
Deletion of any reference can be explicitly stored by setting the
|
|
|
|
`type` to `0x0` and omitting the `value` field of the `ref_record`.
|
|
|
|
This serves as a tombstone, overriding any assertions about the
|
|
|
|
existence of the reference from earlier files in the stack.
|
|
|
|
|
|
|
|
### Compaction
|
|
|
|
|
|
|
|
A partial stack of reftables can be compacted by merging references
|
|
|
|
using a straightforward merge join across reftables, selecting the
|
|
|
|
most recent value for output, and omitting deleted references that do
|
|
|
|
not appear in remaining, lower reftables.
|
|
|
|
|
|
|
|
A compacted reftable should set its `min_update_index` to the smallest of
|
|
|
|
the input files' `min_update_index`, and its `max_update_index`
|
|
|
|
likewise to the largest input `max_update_index`.
|
|
|
|
|
|
|
|
For sake of illustration, assume the stack currently consists of
|
|
|
|
reftable files (from oldest to newest): A, B, C, and D. The compactor
|
|
|
|
is going to compact B and C, leaving A and D alone.
|
|
|
|
|
|
|
|
1. Obtain lock `tables.list.lock` and read the `tables.list` file.
|
|
|
|
2. Obtain locks `B.lock` and `C.lock`.
|
|
|
|
Ownership of these locks prevents other processes from trying
|
|
|
|
to compact these files.
|
|
|
|
3. Release `tables.list.lock`.
|
|
|
|
4. Compact `B` and `C` into a temp file `${min_update_index}-${max_update_index}_XXXXXX`.
|
|
|
|
5. Reacquire lock `tables.list.lock`.
|
|
|
|
6. Verify that `B` and `C` are still in the stack, in that order. This
|
|
|
|
should always be the case, assuming that other processes are adhering
|
|
|
|
to the locking protocol.
|
|
|
|
7. Rename `${min_update_index}-${max_update_index}_XXXXXX` to
|
|
|
|
`${min_update_index}-${max_update_index}.ref`.
|
|
|
|
8. Write the new stack to `tables.list.lock`, replacing `B` and `C` with the
|
|
|
|
file from (4).
|
|
|
|
9. Rename `tables.list.lock` to `tables.list`.
|
|
|
|
10. Delete `B` and `C`, perhaps after a short sleep to avoid forcing
|
|
|
|
readers to backtrack.
|
|
|
|
|
|
|
|
This strategy permits compactions to proceed independently of updates.
|
|
|
|
|
|
|
|
Each reftable (compacted or not) is uniquely identified by its name, so open
|
|
|
|
reftables can be cached by their name.
|
|
|
|
|
|
|
|
## Alternatives considered
|
|
|
|
|
|
|
|
### bzip packed-refs
|
|
|
|
|
|
|
|
`bzip2` can significantly shrink a large packed-refs file (e.g. 62
|
|
|
|
MiB compresses to 23 MiB, 37%). However the bzip format does not support
|
|
|
|
random access to a single reference. Readers must inflate and discard
|
|
|
|
while performing a linear scan.
|
|
|
|
|
|
|
|
Breaking packed-refs into chunks (individually compressing each chunk)
|
|
|
|
would reduce the amount of data a reader must inflate, but still
|
|
|
|
leaves the problem of indexing chunks to support readers efficiently
|
|
|
|
locating the correct chunk.
|
|
|
|
|
|
|
|
Given the compression achieved by reftable's encoding, it does not
|
|
|
|
seem necessary to add the complexity of bzip/gzip/zlib.
|
|
|
|
|
|
|
|
### Michael Haggerty's alternate format
|
|
|
|
|
|
|
|
Michael Haggerty proposed [an alternate][mh-alt] format to reftable on
|
|
|
|
the Git mailing list. This format uses smaller chunks, without the
|
|
|
|
restart table, and avoids block alignment with padding. Reflog entries
|
|
|
|
immediately follow each ref, and are thus interleaved between refs.
|
|
|
|
|
|
|
|
Performance testing indicates reftable is faster for lookups (51%
|
|
|
|
faster, 11.2 usec vs. 5.4 usec), although reftable produces a
|
|
|
|
slightly larger file (+ ~3.2%, 28.3M vs 29.2M):
|
|
|
|
|
|
|
|
format | size | seek cold | seek hot |
|
|
|
|
---------:|-------:|----------:|----------:|
|
|
|
|
mh-alt | 28.3 M | 23.4 usec | 11.2 usec |
|
|
|
|
reftable | 29.2 M | 19.9 usec | 5.4 usec |
|
|
|
|
|
|
|
|
[mh-alt]: https://public-inbox.org/git/CAMy9T_HCnyc1g8XWOOWhe7nN0aEFyyBskV2aOMb_fe+wGvEJ7A@mail.gmail.com/
|
|
|
|
|
|
|
|
### JGit Ketch RefTree
|
|
|
|
|
|
|
|
[JGit Ketch][ketch] proposed [RefTree][reftree], an encoding of
|
|
|
|
references inside Git tree objects stored as part of the repository's
|
|
|
|
object database.
|
|
|
|
|
|
|
|
The RefTree format adds additional load on the object database storage
|
|
|
|
layer (more loose objects, more objects in packs), and relies heavily
|
|
|
|
on the packer's delta compression to save space. Namespaces which are
|
|
|
|
flat (e.g. thousands of tags in refs/tags) initially create very
|
|
|
|
large loose objects, and so RefTree does not address the problem of
|
|
|
|
copying many references to modify a handful.
|
|
|
|
|
|
|
|
Flat namespaces are not efficiently searchable in RefTree, as tree
|
|
|
|
objects in canonical formatting cannot be binary searched. This fails
|
|
|
|
the need to handle a large number of references in a single namespace,
|
|
|
|
such as GitHub's `refs/pulls`, or a project with many tags.
|
|
|
|
|
|
|
|
[ketch]: https://dev.eclipse.org/mhonarc/lists/jgit-dev/msg03073.html
|
|
|
|
[reftree]: https://public-inbox.org/git/CAJo=hJvnAPNAdDcAAwAvU9C4RVeQdoS3Ev9WTguHx4fD0V_nOg@mail.gmail.com/
|
|
|
|
|
|
|
|
### LMDB
|
|
|
|
|
|
|
|
David Turner proposed [using LMDB][dt-lmdb], as LMDB is lightweight
|
|
|
|
(64k of runtime code) and GPL-compatible license.
|
|
|
|
|
|
|
|
A downside of LMDB is its reliance on a single C implementation. This
|
|
|
|
makes embedding inside JGit (a popular reimplemenation of Git)
|
|
|
|
difficult, and hoisting onto virtual storage (for JGit DFS) virtually
|
|
|
|
impossible.
|
|
|
|
|
|
|
|
A common format that can be supported by all major Git implementations
|
|
|
|
(git-core, JGit, libgit2) is strongly preferred.
|
|
|
|
|
|
|
|
[dt-lmdb]: https://public-inbox.org/git/1455772670-21142-26-git-send-email-dturner@twopensource.com/
|
|
|
|
|
|
|
|
## Future
|
|
|
|
|
|
|
|
### Longer hashes
|
|
|
|
|
|
|
|
Version will bump (e.g. 2) to indicate `value` uses a different
|
|
|
|
object id length other than 20. The length could be stored in an
|
|
|
|
expanded file header, or hardcoded as part of the version.
|