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