##// END OF EJS Templates
bundle2: urlquote stream parameter name and value...
Pierre-Yves David -
r20811:9785c3f8 default
parent child Browse files
Show More
@@ -1,177 +1,177
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>`.
46 are stored in the form `<name>=<value>`. Both name and value are urlquoted.
47
48 Special character in param name are not supported yet.
49
47
50 Stream parameters use a simple textual format for two main reasons:
48 Stream parameters use a simple textual format for two main reasons:
51
49
52 - 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
53 crazy usage.
51 crazy usage.
54 - 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
55 troubles.
53 troubles.
56
54
57 Any Applicative level options MUST go into a bundle2 part instead.
55 Any Applicative level options MUST go into a bundle2 part instead.
58
56
59
57
60 Payload part
58 Payload part
61 ------------------------
59 ------------------------
62
60
63 Binary format is as follow
61 Binary format is as follow
64
62
65 :header size: (16 bits inter)
63 :header size: (16 bits inter)
66
64
67 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
68 (size = 0) this is interpreted as the end of stream marker.
66 (size = 0) this is interpreted as the end of stream marker.
69
67
70 Currently forced to 0 in the current state of the implementation
68 Currently forced to 0 in the current state of the implementation
71 """
69 """
72
70
73 import util
71 import util
74 import struct
72 import struct
73 import urllib
75
74
76 import changegroup
75 import changegroup
77 from i18n import _
76 from i18n import _
78
77
79 _pack = struct.pack
78 _pack = struct.pack
80 _unpack = struct.unpack
79 _unpack = struct.unpack
81
80
82 _magicstring = 'HG20'
81 _magicstring = 'HG20'
83
82
84 _fstreamparamsize = '>H'
83 _fstreamparamsize = '>H'
85
84
86 class bundle20(object):
85 class bundle20(object):
87 """represent an outgoing bundle2 container
86 """represent an outgoing bundle2 container
88
87
89 Use the `addparam` method to add stream level parameter. Then call
88 Use the `addparam` method to add stream level parameter. Then call
90 `getchunks` to retrieve all the binary chunks of datathat compose the
89 `getchunks` to retrieve all the binary chunks of datathat compose the
91 bundle2 container.
90 bundle2 container.
92
91
93 This object does not support payload part yet."""
92 This object does not support payload part yet."""
94
93
95 def __init__(self):
94 def __init__(self):
96 self._params = []
95 self._params = []
97 self._parts = []
96 self._parts = []
98
97
99 def addparam(self, name, value=None):
98 def addparam(self, name, value=None):
100 """add a stream level parameter"""
99 """add a stream level parameter"""
101 self._params.append((name, value))
100 self._params.append((name, value))
102
101
103 def getchunks(self):
102 def getchunks(self):
104 yield _magicstring
103 yield _magicstring
105 param = self._paramchunk()
104 param = self._paramchunk()
106 yield _pack(_fstreamparamsize, len(param))
105 yield _pack(_fstreamparamsize, len(param))
107 if param:
106 if param:
108 yield param
107 yield param
109
108
110 # no support for parts
109 # no support for parts
111 # to be obviously fixed soon.
110 # to be obviously fixed soon.
112 assert not self._parts
111 assert not self._parts
113 yield '\0\0'
112 yield '\0\0'
114
113
115 def _paramchunk(self):
114 def _paramchunk(self):
116 """return a encoded version of all stream parameters"""
115 """return a encoded version of all stream parameters"""
117 blocks = []
116 blocks = []
118 for par, value in self._params:
117 for par, value in self._params:
119 # XXX no escaping yet
118 par = urllib.quote(par)
120 if value is not None:
119 if value is not None:
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 if len(p) < 2:
156 if len(p) < 2:
157 p.append(None)
157 p.append(None)
158 params[p[0]] = p[1]
158 params[p[0]] = p[1]
159 return params
159 return params
160
160
161 def __iter__(self):
161 def __iter__(self):
162 """yield all parts contained in the stream"""
162 """yield all parts contained in the stream"""
163 # make sure param have been loaded
163 # make sure param have been loaded
164 self.params
164 self.params
165 part = self._readpart()
165 part = self._readpart()
166 while part is not None:
166 while part is not None:
167 yield part
167 yield part
168 part = self._readpart()
168 part = self._readpart()
169
169
170 def _readpart(self):
170 def _readpart(self):
171 """return None when an end of stream markers is reach"""
171 """return None when an end of stream markers is reach"""
172 headersize = self._readexact(2)
172 headersize = self._readexact(2)
173 assert headersize == '\0\0'
173 assert headersize == '\0\0'
174 return None
174 return None
175
175
176
176
177
177
@@ -1,134 +1,142
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
136 parameter with special char in value
137 ---------------------------------------------------
138
139 Test generation
140
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)
General Comments 0
You need to be logged in to leave comments. Login now