##// END OF EJS Templates
bundle2: print debug information during unbundling...
Pierre-Yves David -
r20843:0641b41b default
parent child Browse files
Show More
@@ -1,192 +1,197 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. parameter with value
46 46 are stored in the form `<name>=<value>`. Both name and value are urlquoted.
47 47
48 48 Empty name are obviously forbidden.
49 49
50 50 Name MUST start with a letter. This first character has to be capitalizable.
51 51 The capitalisation of the first letter will be used to know if an option is
52 52 advisory or mandatory. This is not implemented yet.
53 53
54 54 Stream parameters use a simple textual format for two main reasons:
55 55
56 56 - Stream level parameters should remains simple and we want to discourage any
57 57 crazy usage.
58 58 - Textual data allow easy human inspection of a the bundle2 header in case of
59 59 troubles.
60 60
61 61 Any Applicative level options MUST go into a bundle2 part instead.
62 62
63 63 Payload part
64 64 ------------------------
65 65
66 66 Binary format is as follow
67 67
68 68 :header size: (16 bits inter)
69 69
70 70 The total number of Bytes used by the part headers. When the header is empty
71 71 (size = 0) this is interpreted as the end of stream marker.
72 72
73 73 Currently forced to 0 in the current state of the implementation
74 74 """
75 75
76 76 import util
77 77 import struct
78 78 import urllib
79 79 import string
80 80
81 81 import changegroup
82 82 from i18n import _
83 83
84 84 _pack = struct.pack
85 85 _unpack = struct.unpack
86 86
87 87 _magicstring = 'HG20'
88 88
89 89 _fstreamparamsize = '>H'
90 90
91 91 class bundle20(object):
92 92 """represent an outgoing bundle2 container
93 93
94 94 Use the `addparam` method to add stream level parameter. Then call
95 95 `getchunks` to retrieve all the binary chunks of datathat compose the
96 96 bundle2 container.
97 97
98 98 This object does not support payload part yet."""
99 99
100 100 def __init__(self, ui):
101 101 self.ui = ui
102 102 self._params = []
103 103 self._parts = []
104 104
105 105 def addparam(self, name, value=None):
106 106 """add a stream level parameter"""
107 107 if not name:
108 108 raise ValueError('empty parameter name')
109 109 if name[0] not in string.letters:
110 110 raise ValueError('non letter first character: %r' % name)
111 111 self._params.append((name, value))
112 112
113 113 def getchunks(self):
114 114 self.ui.debug('start emission of %s stream\n' % _magicstring)
115 115 yield _magicstring
116 116 param = self._paramchunk()
117 117 self.ui.debug('bundle parameter: %s\n' % param)
118 118 yield _pack(_fstreamparamsize, len(param))
119 119 if param:
120 120 yield param
121 121
122 122 # no support for parts
123 123 # to be obviously fixed soon.
124 124 assert not self._parts
125 125 self.ui.debug('end of bundle\n')
126 126 yield '\0\0'
127 127
128 128 def _paramchunk(self):
129 129 """return a encoded version of all stream parameters"""
130 130 blocks = []
131 131 for par, value in self._params:
132 132 par = urllib.quote(par)
133 133 if value is not None:
134 134 value = urllib.quote(value)
135 135 par = '%s=%s' % (par, value)
136 136 blocks.append(par)
137 137 return ' '.join(blocks)
138 138
139 139 class unbundle20(object):
140 140 """interpret a bundle2 stream
141 141
142 142 (this will eventually yield parts)"""
143 143
144 def __init__(self, fp):
144 def __init__(self, ui, fp):
145 self.ui = ui
145 146 self._fp = fp
146 147 header = self._readexact(4)
147 148 magic, version = header[0:2], header[2:4]
148 149 if magic != 'HG':
149 150 raise util.Abort(_('not a Mercurial bundle'))
150 151 if version != '20':
151 152 raise util.Abort(_('unknown bundle version %s') % version)
153 self.ui.debug('start processing of %s stream\n' % header)
152 154
153 155 def _unpack(self, format):
154 156 """unpack this struct format from the stream"""
155 157 data = self._readexact(struct.calcsize(format))
156 158 return _unpack(format, data)
157 159
158 160 def _readexact(self, size):
159 161 """read exactly <size> bytes from the stream"""
160 162 return changegroup.readexactly(self._fp, size)
161 163
162 164 @util.propertycache
163 165 def params(self):
164 166 """dictionnary of stream level parameters"""
167 self.ui.debug('reading bundle2 stream parameters\n')
165 168 params = {}
166 169 paramssize = self._unpack(_fstreamparamsize)[0]
167 170 if paramssize:
168 171 for p in self._readexact(paramssize).split(' '):
169 172 p = p.split('=', 1)
170 173 p = [urllib.unquote(i) for i in p]
171 174 if len(p) < 2:
172 175 p.append(None)
173 176 params[p[0]] = p[1]
174 177 return params
175 178
176 179 def __iter__(self):
177 180 """yield all parts contained in the stream"""
178 181 # make sure param have been loaded
179 182 self.params
183 self.ui.debug('start extraction of bundle2 parts\n')
180 184 part = self._readpart()
181 185 while part is not None:
182 186 yield part
183 187 part = self._readpart()
188 self.ui.debug('end of bundle2 stream\n')
184 189
185 190 def _readpart(self):
186 191 """return None when an end of stream markers is reach"""
187 192 headersize = self._readexact(2)
188 193 assert headersize == '\0\0'
189 194 return None
190 195
191 196
192 197
@@ -1,188 +1,204 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 util
14 14 > from mercurial import bundle2
15 15 > cmdtable = {}
16 16 > command = cmdutil.command(cmdtable)
17 17 >
18 18 > @command('bundle2',
19 19 > [('', 'param', [], 'stream level parameter'),],
20 20 > '[OUTPUTFILE]')
21 21 > def cmdbundle2(ui, repo, path=None, **opts):
22 22 > """write a bundle2 container on standard ouput"""
23 23 > bundler = bundle2.bundle20(ui)
24 24 > for p in opts['param']:
25 25 > p = p.split('=', 1)
26 26 > try:
27 27 > bundler.addparam(*p)
28 28 > except ValueError, exc:
29 29 > raise util.Abort('%s' % exc)
30 30 >
31 31 > if path is None:
32 32 > file = sys.stdout
33 33 > else:
34 34 > file = open(path, 'w')
35 35 >
36 36 > for chunk in bundler.getchunks():
37 37 > file.write(chunk)
38 38 >
39 39 > @command('unbundle2', [], '')
40 40 > def cmdunbundle2(ui, repo):
41 41 > """read a bundle2 container from standard input"""
42 > unbundler = bundle2.unbundle20(sys.stdin)
42 > unbundler = bundle2.unbundle20(ui, sys.stdin)
43 43 > ui.write('options count: %i\n' % len(unbundler.params))
44 44 > for key in sorted(unbundler.params):
45 45 > ui.write('- %s\n' % key)
46 46 > value = unbundler.params[key]
47 47 > if value is not None:
48 48 > ui.write(' %s\n' % value)
49 49 > parts = list(unbundler)
50 50 > ui.write('parts count: %i\n' % len(parts))
51 51 > EOF
52 52 $ cat >> $HGRCPATH << EOF
53 53 > [extensions]
54 54 > bundle2=$TESTTMP/bundle2.py
55 55 > EOF
56 56
57 57 The extension requires a repo (currently unused)
58 58
59 59 $ hg init main
60 60 $ cd main
61 61 $ touch a
62 62 $ hg add a
63 63 $ hg commit -m 'a'
64 64
65 65
66 66 Empty bundle
67 67 =================
68 68
69 69 - no option
70 70 - no parts
71 71
72 72 Test bundling
73 73
74 74 $ hg bundle2
75 75 HG20\x00\x00\x00\x00 (no-eol) (esc)
76 76
77 77 Test unbundling
78 78
79 79 $ hg bundle2 | hg unbundle2
80 80 options count: 0
81 81 parts count: 0
82 82
83 83 Test old style bundle are detected and refused
84 84
85 85 $ hg bundle --all ../bundle.hg
86 86 1 changesets found
87 87 $ hg unbundle2 < ../bundle.hg
88 88 abort: unknown bundle version 10
89 89 [255]
90 90
91 91 Test parameters
92 92 =================
93 93
94 94 - some options
95 95 - no parts
96 96
97 97 advisory parameters, no value
98 98 -------------------------------
99 99
100 100 Simplest possible parameters form
101 101
102 102 Test generation simple option
103 103
104 104 $ hg bundle2 --param 'caution'
105 105 HG20\x00\x07caution\x00\x00 (no-eol) (esc)
106 106
107 107 Test unbundling
108 108
109 109 $ hg bundle2 --param 'caution' | hg unbundle2
110 110 options count: 1
111 111 - caution
112 112 parts count: 0
113 113
114 114 Test generation multiple option
115 115
116 116 $ hg bundle2 --param 'caution' --param 'meal'
117 117 HG20\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
118 118
119 119 Test unbundling
120 120
121 121 $ hg bundle2 --param 'caution' --param 'meal' | hg unbundle2
122 122 options count: 2
123 123 - caution
124 124 - meal
125 125 parts count: 0
126 126
127 127 advisory parameters, with value
128 128 -------------------------------
129 129
130 130 Test generation
131 131
132 132 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
133 133 HG20\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
134 134
135 135 Test unbundling
136 136
137 137 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg unbundle2
138 138 options count: 3
139 139 - caution
140 140 - elephants
141 141 - meal
142 142 vegan
143 143 parts count: 0
144 144
145 145 parameter with special char in value
146 146 ---------------------------------------------------
147 147
148 148 Test generation
149 149
150 150 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
151 151 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
152 152
153 153 Test unbundling
154 154
155 155 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg unbundle2
156 156 options count: 2
157 157 - e|! 7/
158 158 babar%#==tutu
159 159 - simple
160 160 parts count: 0
161 161
162 162 Test debug output
163 163 ---------------------------------------------------
164 164
165 bundling debug
166
165 167 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
166 168 start emission of HG20 stream
167 169 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
168 170 end of bundle
169 171
170 172 file content is ok
171 173
172 174 $ cat ../out.hg2
173 175 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
174 176
177 unbundling debug
178
179 $ hg unbundle2 --debug < ../out.hg2
180 start processing of HG20 stream
181 reading bundle2 stream parameters
182 options count: 2
183 - e|! 7/
184 babar%#==tutu
185 - simple
186 start extraction of bundle2 parts
187 end of bundle2 stream
188 parts count: 0
189
190
175 191 Test buggy input
176 192 ---------------------------------------------------
177 193
178 194 empty parameter name
179 195
180 196 $ hg bundle2 --param '' --quiet
181 197 abort: empty parameter name
182 198 [255]
183 199
184 200 bad parameter name
185 201
186 202 $ hg bundle2 --param 42babar
187 203 abort: non letter first character: '42babar'
188 204 [255]
General Comments 0
You need to be logged in to leave comments. Login now