##// END OF EJS Templates
streamclone: define first iteration of version 2 of stream format...
streamclone: define first iteration of version 2 of stream format (This patch is based on a first draft from Gregory Szorc, with deeper rework) Version 1 of the stream clone format was invented many years ago and suffers from a few deficiencies: 1) Filenames are stored in store-encoded (on filesystem) form rather than in their internal form. This makes future compatibility with new store filename encodings more difficult. 2) File entry "headers" consist of a newline of the file name followed by the string file size. Converting strings to integers is avoidable overhead. We can't store filenames with newlines (manifests have this limitation as well, so it isn't a major concern). But the big concern here is the necessity for readline(). Scanning for newlines means reading ahead and that means extra buffer allocations and slicing (in Python) and this makes performance suffer. 3) Filenames aren't compressed optimally. Filenames should be compressed well since there is a lot of repeated data. However, since they are scattered all over the stream (with revlog data in between), they typically fall outside the window size of the compressor and don't compress. 4) It can only exchange stored based content, being able to exchange caches too would be nice. 5) It is limited to a stream-based protocol and isn't suitable for an on-disk format for general repository reading because the offset of individual file entries requires scanning the entire file to find file records. As part of enabling streaming clones to work in bundle2, #2 proved to have a significant negative impact on performance. Since bundle2 provides the opportunity to start fresh, Gregory Szorc figured he would take the opportunity to invent a new streaming clone data format. The new format devised in this series addresses #1, #2, and #4. It punts on #3 because it was complex without yielding a significant gain and on #5 because devising a new store format that "packs" multiple revlogs into a single "packed revlog" is massive scope bloat. However, this v2 format might be suitable for streaming into a "packed revlog" with minimal processing. If it works, great. If not, we can always invent stream format when it is needed. This patch only introduces the bases of the format. We'll get it usable through bundle2 first, then we'll extend the format in future patches to bring it to its full potential (especially #4).

File last commit:

r32139:de86a687 stable
r35774:cfdccd56 default
Show More
changegroups.txt
188 lines | 8.2 KiB | text/plain | TextLexer
Changegroups are representations of repository revlog data, specifically
the changelog data, root/flat manifest data, treemanifest data, and
filelogs.
There are 3 versions of changegroups: ``1``, ``2``, and ``3``. From a
high-level, versions ``1`` and ``2`` are almost exactly the same, with the
only difference being an additional item in the *delta header*. Version
``3`` adds support for revlog flags in the *delta header* and optionally
exchanging treemanifests (enabled by setting an option on the
``changegroup`` part in the bundle2).
Changegroups when not exchanging treemanifests consist of 3 logical
segments::
+---------------------------------+
| | | |
| changeset | manifest | filelogs |
| | | |
| | | |
+---------------------------------+
When exchanging treemanifests, there are 4 logical segments::
+-------------------------------------------------+
| | | | |
| changeset | root | treemanifests | filelogs |
| | manifest | | |
| | | | |
+-------------------------------------------------+
The principle building block of each segment is a *chunk*. A *chunk*
is a framed piece of data::
+---------------------------------------+
| | |
| length | data |
| (4 bytes) | (<length - 4> bytes) |
| | |
+---------------------------------------+
All integers are big-endian signed integers. Each chunk starts with a 32-bit
integer indicating the length of the entire chunk (including the length field
itself).
There is a special case chunk that has a value of 0 for the length
(``0x00000000``). We call this an *empty chunk*.
Delta Groups
============
A *delta group* expresses the content of a revlog as a series of deltas,
or patches against previous revisions.
Delta groups consist of 0 or more *chunks* followed by the *empty chunk*
to signal the end of the delta group::
+------------------------------------------------------------------------+
| | | | | |
| chunk0 length | chunk0 data | chunk1 length | chunk1 data | 0x0 |
| (4 bytes) | (various) | (4 bytes) | (various) | (4 bytes) |
| | | | | |
+------------------------------------------------------------------------+
Each *chunk*'s data consists of the following::
+---------------------------------------+
| | |
| delta header | delta data |
| (various by version) | (various) |
| | |
+---------------------------------------+
The *delta data* is a series of *delta*s that describe a diff from an existing
entry (either that the recipient already has, or previously specified in the
bundle/changegroup).
The *delta header* is different between versions ``1``, ``2``, and
``3`` of the changegroup format.
Version 1 (headerlen=80)::
+------------------------------------------------------+
| | | | |
| node | p1 node | p2 node | link node |
| (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) |
| | | | |
+------------------------------------------------------+
Version 2 (headerlen=100)::
+------------------------------------------------------------------+
| | | | | |
| node | p1 node | p2 node | base node | link node |
| (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) |
| | | | | |
+------------------------------------------------------------------+
Version 3 (headerlen=102)::
+------------------------------------------------------------------------------+
| | | | | | |
| node | p1 node | p2 node | base node | link node | flags |
| (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (20 bytes) | (2 bytes) |
| | | | | | |
+------------------------------------------------------------------------------+
The *delta data* consists of ``chunklen - 4 - headerlen`` bytes, which contain a
series of *delta*s, densely packed (no separators). These deltas describe a diff
from an existing entry (either that the recipient already has, or previously
specified in the bundle/changegroup). The format is described more fully in
``hg help internals.bdiff``, but briefly::
+---------------------------------------------------------------+
| | | | |
| start offset | end offset | new length | content |
| (4 bytes) | (4 bytes) | (4 bytes) | (<new length> bytes) |
| | | | |
+---------------------------------------------------------------+
Please note that the length field in the delta data does *not* include itself.
In version 1, the delta is always applied against the previous node from
the changegroup or the first parent if this is the first entry in the
changegroup.
In version 2 and up, the delta base node is encoded in the entry in the
changegroup. This allows the delta to be expressed against any parent,
which can result in smaller deltas and more efficient encoding of data.
Changeset Segment
=================
The *changeset segment* consists of a single *delta group* holding
changelog data. The *empty chunk* at the end of the *delta group* denotes
the boundary to the *manifest segment*.
Manifest Segment
================
The *manifest segment* consists of a single *delta group* holding manifest
data. If treemanifests are in use, it contains only the manifest for the
root directory of the repository. Otherwise, it contains the entire
manifest data. The *empty chunk* at the end of the *delta group* denotes
the boundary to the next segment (either the *treemanifests segment* or the
*filelogs segment*, depending on version and the request options).
Treemanifests Segment
---------------------
The *treemanifests segment* only exists in changegroup version ``3``, and
only if the 'treemanifest' param is part of the bundle2 changegroup part
(it is not possible to use changegroup version 3 outside of bundle2).
Aside from the filenames in the *treemanifests segment* containing a
trailing ``/`` character, it behaves identically to the *filelogs segment*
(see below). The final sub-segment is followed by an *empty chunk* (logically,
a sub-segment with filename size 0). This denotes the boundary to the
*filelogs segment*.
Filelogs Segment
================
The *filelogs segment* consists of multiple sub-segments, each
corresponding to an individual file whose data is being described::
+--------------------------------------------------+
| | | | | |
| filelog0 | filelog1 | filelog2 | ... | 0x0 |
| | | | | (4 bytes) |
| | | | | |
+--------------------------------------------------+
The final filelog sub-segment is followed by an *empty chunk* (logically,
a sub-segment with filename size 0). This denotes the end of the segment
and of the overall changegroup.
Each filelog sub-segment consists of the following::
+------------------------------------------------------+
| | | |
| filename length | filename | delta group |
| (4 bytes) | (<length - 4> bytes) | (various) |
| | | |
+------------------------------------------------------+
That is, a *chunk* consisting of the filename (not terminated or padded)
followed by N chunks constituting the *delta group* for this file. The
*empty chunk* at the end of each *delta group* denotes the boundary to the
next filelog sub-segment.