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