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