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