##// END OF EJS Templates
bundle2: support bundling simple parameter...
Pierre-Yves David -
r20804:db9d3991 default
parent child Browse files
Show More
@@ -1,136 +1,165
1 1 # bundle2.py - generic container format to transmit arbitrary data.
2 2 #
3 3 # Copyright 2013 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 """Handling of the new bundle2 format
8 8
9 9 The goal of bundle2 is to act as an atomically packet to transmit a set of
10 10 payloads in an application agnostic way. It consist in a sequence of "parts"
11 11 that will be handed to and processed by the application layer.
12 12
13 13
14 14 General format architecture
15 15 ===========================
16 16
17 17 The format is architectured as follow
18 18
19 19 - magic string
20 20 - stream level parameters
21 21 - payload parts (any number)
22 22 - end of stream marker.
23 23
24 The current implementation is limited to empty bundle.
24 The current implementation accept some stream level option but no part.
25 25
26 26 Details on the Binary format
27 27 ============================
28 28
29 29 All numbers are unsigned and big endian.
30 30
31 31 stream level parameters
32 32 ------------------------
33 33
34 34 Binary format is as follow
35 35
36 36 :params size: (16 bits integer)
37 37
38 38 The total number of Bytes used by the parameters
39 39
40 Currently force to 0.
41
42 40 :params value: arbitrary number of Bytes
43 41
44 42 A blob of `params size` containing the serialized version of all stream level
45 43 parameters.
46 44
47 Currently always empty.
45 The blob contains a space separated list of parameters.
46
47 Parameter value are not supported yet.
48
49 Special character in param name are not supported yet.
50
51
48 52
49 53
50 54 Payload part
51 55 ------------------------
52 56
53 57 Binary format is as follow
54 58
55 59 :header size: (16 bits inter)
56 60
57 61 The total number of Bytes used by the part headers. When the header is empty
58 62 (size = 0) this is interpreted as the end of stream marker.
59 63
60 64 Currently forced to 0 in the current state of the implementation
61 65 """
62 66
63 67 import util
68 import struct
69
64 70 import changegroup
65 71 from i18n import _
66 72
73 _pack = struct.pack
74 _unpack = struct.unpack
75
67 76 _magicstring = 'HG20'
68 77
78 _fstreamparamsize = '>H'
79
69 80 class bundle20(object):
70 81 """represent an outgoing bundle2 container
71 82
72 People will eventually be able to add param and parts to this object and
73 generated a stream from it."""
83 Use the `addparam` method to add stream level parameter. Then call
84 `getchunks` to retrieve all the binary chunks of datathat compose the
85 bundle2 container.
86
87 This object does not support payload part yet."""
74 88
75 89 def __init__(self):
76 90 self._params = []
77 91 self._parts = []
78 92
93 def addparam(self, name, value=None):
94 """add a stream level parameter"""
95 self._params.append((name, value))
96
79 97 def getchunks(self):
80 98 yield _magicstring
81 # no support for any param yet
82 # to be obviously fixed soon.
83 assert not self._params
84 yield '\0\0'
99 param = self._paramchunk()
100 yield _pack(_fstreamparamsize, len(param))
101 if param:
102 yield param
103
85 104 # no support for parts
86 105 # to be obviously fixed soon.
87 106 assert not self._parts
88 107 yield '\0\0'
89 108
109 def _paramchunk(self):
110 """return a encoded version of all stream parameters"""
111 blocks = []
112 for key, value in self._params:
113 # XXX no support for value yet
114 assert value is None
115 # XXX no escaping yet
116 blocks.append(key)
117 return ' '.join(blocks)
118
90 119 class unbundle20(object):
91 120 """interpret a bundle2 stream
92 121
93 122 (this will eventually yield parts)"""
94 123
95 124 def __init__(self, fp):
96 125 self._fp = fp
97 126 header = self._readexact(4)
98 127 magic, version = header[0:2], header[2:4]
99 128 if magic != 'HG':
100 129 raise util.Abort(_('not a Mercurial bundle'))
101 130 if version != '20':
102 131 raise util.Abort(_('unknown bundle version %s') % version)
103 132
104 133 def _unpack(self, format):
105 134 """unpack this struct format from the stream"""
106 135 data = self._readexact(struct.calcsize(format))
107 136 return _unpack(format, data)
108 137
109 138 def _readexact(self, size):
110 139 """read exactly <size> bytes from the stream"""
111 140 return changegroup.readexactly(self._fp, size)
112 141
113 142 @util.propertycache
114 143 def params(self):
115 144 """dictionnary of stream level parameters"""
116 145 paramsize = self._readexact(2)
117 146 assert paramsize == '\0\0'
118 147 return {}
119 148
120 149 def __iter__(self):
121 150 """yield all parts contained in the stream"""
122 151 # make sure param have been loaded
123 152 self.params
124 153 part = self._readpart()
125 154 while part is not None:
126 155 yield part
127 156 part = self._readpart()
128 157
129 158 def _readpart(self):
130 159 """return None when an end of stream markers is reach"""
131 160 headersize = self._readexact(2)
132 161 assert headersize == '\0\0'
133 162 return None
134 163
135 164
136 165
@@ -1,62 +1,94
1 1
2 2 Create an extension to test bundle2 API
3 3
4 4 $ cat > bundle2.py << EOF
5 5 > """A small extension to test bundle2 implementation
6 6 >
7 7 > Current bundle2 implementation is far too limited to be used in any core
8 8 > code. We still need to be able to test it while it grow up.
9 9 > """
10 10 >
11 11 > import sys
12 12 > from mercurial import cmdutil
13 13 > from mercurial import bundle2
14 14 > cmdtable = {}
15 15 > command = cmdutil.command(cmdtable)
16 16 >
17 > @command('bundle2', [], '')
18 > def cmdbundle2(ui, repo):
17 > @command('bundle2',
18 > [('', 'param', [], 'stream level parameter'),],
19 > '')
20 > def cmdbundle2(ui, repo, **opts):
19 21 > """write a bundle2 container on standard ouput"""
20 > bundle = bundle2.bundle20()
21 > for chunk in bundle.getchunks():
22 > bundler = bundle2.bundle20()
23 > for p in opts['param']:
24 > bundler.addparam(p)
25 > for chunk in bundler.getchunks():
22 26 > ui.write(chunk)
23 27 >
24 28 > @command('unbundle2', [], '')
25 29 > def cmdunbundle2(ui, repo):
26 30 > """read a bundle2 container from standard input"""
27 31 > unbundler = bundle2.unbundle20(sys.stdin)
28 32 > ui.write('options count: %i\n' % len(unbundler.params))
29 33 > parts = list(unbundler)
30 34 > ui.write('parts count: %i\n' % len(parts))
31 35 > EOF
32 36 $ cat >> $HGRCPATH << EOF
33 37 > [extensions]
34 38 > bundle2=$TESTTMP/bundle2.py
35 39 > EOF
36 40
37 41 The extension requires a repo (currently unused)
38 42
39 43 $ hg init main
40 44 $ cd main
41 45 $ touch a
42 46 $ hg add a
43 47 $ hg commit -m 'a'
44 48
45 Test simple generation of empty bundle
49
50 Empty bundle
51 =================
52
53 - no option
54 - no parts
55
56 Test bundling
46 57
47 58 $ hg bundle2
48 59 HG20\x00\x00\x00\x00 (no-eol) (esc)
49 60
50 Test parsing of an empty bundle
61 Test unbundling
51 62
52 63 $ hg bundle2 | hg unbundle2
53 64 options count: 0
54 65 parts count: 0
55 66
56 67 Test old style bundle are detected and refused
57 68
58 69 $ hg bundle --all ../bundle.hg
59 70 1 changesets found
60 71 $ hg unbundle2 < ../bundle.hg
61 72 abort: unknown bundle version 10
62 73 [255]
74
75 Test parameters
76 =================
77
78 - some options
79 - no parts
80
81 advisory parameters, no value
82 -------------------------------
83
84 Simplest possible parameters form
85
86 Test generation
87
88 $ hg bundle2 --param 'caution'
89 HG20\x00\x07caution\x00\x00 (no-eol) (esc)
90
91 Test generation multiple option
92
93 $ hg bundle2 --param 'caution' --param 'meal'
94 HG20\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
General Comments 0
You need to be logged in to leave comments. Login now