##// END OF EJS Templates
bundle2: print debug information during bundling...
Pierre-Yves David -
r20842:938718d7 default
parent child Browse files
Show More
@@ -1,188 +1,192 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 def __init__(self):
100 def __init__(self, ui):
101 self.ui = ui
101 102 self._params = []
102 103 self._parts = []
103 104
104 105 def addparam(self, name, value=None):
105 106 """add a stream level parameter"""
106 107 if not name:
107 108 raise ValueError('empty parameter name')
108 109 if name[0] not in string.letters:
109 110 raise ValueError('non letter first character: %r' % name)
110 111 self._params.append((name, value))
111 112
112 113 def getchunks(self):
114 self.ui.debug('start emission of %s stream\n' % _magicstring)
113 115 yield _magicstring
114 116 param = self._paramchunk()
117 self.ui.debug('bundle parameter: %s\n' % param)
115 118 yield _pack(_fstreamparamsize, len(param))
116 119 if param:
117 120 yield param
118 121
119 122 # no support for parts
120 123 # to be obviously fixed soon.
121 124 assert not self._parts
125 self.ui.debug('end of bundle\n')
122 126 yield '\0\0'
123 127
124 128 def _paramchunk(self):
125 129 """return a encoded version of all stream parameters"""
126 130 blocks = []
127 131 for par, value in self._params:
128 132 par = urllib.quote(par)
129 133 if value is not None:
130 134 value = urllib.quote(value)
131 135 par = '%s=%s' % (par, value)
132 136 blocks.append(par)
133 137 return ' '.join(blocks)
134 138
135 139 class unbundle20(object):
136 140 """interpret a bundle2 stream
137 141
138 142 (this will eventually yield parts)"""
139 143
140 144 def __init__(self, fp):
141 145 self._fp = fp
142 146 header = self._readexact(4)
143 147 magic, version = header[0:2], header[2:4]
144 148 if magic != 'HG':
145 149 raise util.Abort(_('not a Mercurial bundle'))
146 150 if version != '20':
147 151 raise util.Abort(_('unknown bundle version %s') % version)
148 152
149 153 def _unpack(self, format):
150 154 """unpack this struct format from the stream"""
151 155 data = self._readexact(struct.calcsize(format))
152 156 return _unpack(format, data)
153 157
154 158 def _readexact(self, size):
155 159 """read exactly <size> bytes from the stream"""
156 160 return changegroup.readexactly(self._fp, size)
157 161
158 162 @util.propertycache
159 163 def params(self):
160 164 """dictionnary of stream level parameters"""
161 165 params = {}
162 166 paramssize = self._unpack(_fstreamparamsize)[0]
163 167 if paramssize:
164 168 for p in self._readexact(paramssize).split(' '):
165 169 p = p.split('=', 1)
166 170 p = [urllib.unquote(i) for i in p]
167 171 if len(p) < 2:
168 172 p.append(None)
169 173 params[p[0]] = p[1]
170 174 return params
171 175
172 176 def __iter__(self):
173 177 """yield all parts contained in the stream"""
174 178 # make sure param have been loaded
175 179 self.params
176 180 part = self._readpart()
177 181 while part is not None:
178 182 yield part
179 183 part = self._readpart()
180 184
181 185 def _readpart(self):
182 186 """return None when an end of stream markers is reach"""
183 187 headersize = self._readexact(2)
184 188 assert headersize == '\0\0'
185 189 return None
186 190
187 191
188 192
@@ -1,186 +1,188 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 > bundler = bundle2.bundle20()
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 42 > unbundler = bundle2.unbundle20(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 (no debug output yet)
165 164
166 165 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
166 start emission of HG20 stream
167 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
168 end of bundle
167 169
168 170 file content is ok
169 171
170 172 $ cat ../out.hg2
171 173 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
172 174
173 175 Test buggy input
174 176 ---------------------------------------------------
175 177
176 178 empty parameter name
177 179
178 180 $ hg bundle2 --param '' --quiet
179 181 abort: empty parameter name
180 182 [255]
181 183
182 184 bad parameter name
183 185
184 186 $ hg bundle2 --param 42babar
185 187 abort: non letter first character: '42babar'
186 188 [255]
General Comments 0
You need to be logged in to leave comments. Login now