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