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