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