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