##// END OF EJS Templates
bundle2: make sure the unbundler refuse non bundle2 stream...
Pierre-Yves David -
r20803:88db3e61 default
parent child Browse files
Show More
@@ -1,133 +1,136 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 is limited to empty bundle.
24 The current implementation is limited to empty bundle.
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 Currently force to 0.
40 Currently force to 0.
41
41
42 :params value: arbitrary number of Bytes
42 :params value: arbitrary number of Bytes
43
43
44 A blob of `params size` containing the serialized version of all stream level
44 A blob of `params size` containing the serialized version of all stream level
45 parameters.
45 parameters.
46
46
47 Currently always empty.
47 Currently always empty.
48
48
49
49
50 Payload part
50 Payload part
51 ------------------------
51 ------------------------
52
52
53 Binary format is as follow
53 Binary format is as follow
54
54
55 :header size: (16 bits inter)
55 :header size: (16 bits inter)
56
56
57 The total number of Bytes used by the part headers. When the header is empty
57 The total number of Bytes used by the part headers. When the header is empty
58 (size = 0) this is interpreted as the end of stream marker.
58 (size = 0) this is interpreted as the end of stream marker.
59
59
60 Currently forced to 0 in the current state of the implementation
60 Currently forced to 0 in the current state of the implementation
61 """
61 """
62
62
63 import util
63 import util
64 import changegroup
64 import changegroup
65
65 from i18n import _
66
66
67 _magicstring = 'HG20'
67 _magicstring = 'HG20'
68
68
69 class bundle20(object):
69 class bundle20(object):
70 """represent an outgoing bundle2 container
70 """represent an outgoing bundle2 container
71
71
72 People will eventually be able to add param and parts to this object and
72 People will eventually be able to add param and parts to this object and
73 generated a stream from it."""
73 generated a stream from it."""
74
74
75 def __init__(self):
75 def __init__(self):
76 self._params = []
76 self._params = []
77 self._parts = []
77 self._parts = []
78
78
79 def getchunks(self):
79 def getchunks(self):
80 yield _magicstring
80 yield _magicstring
81 # no support for any param yet
81 # no support for any param yet
82 # to be obviously fixed soon.
82 # to be obviously fixed soon.
83 assert not self._params
83 assert not self._params
84 yield '\0\0'
84 yield '\0\0'
85 # no support for parts
85 # no support for parts
86 # to be obviously fixed soon.
86 # to be obviously fixed soon.
87 assert not self._parts
87 assert not self._parts
88 yield '\0\0'
88 yield '\0\0'
89
89
90 class unbundle20(object):
90 class unbundle20(object):
91 """interpret a bundle2 stream
91 """interpret a bundle2 stream
92
92
93 (this will eventually yield parts)"""
93 (this will eventually yield parts)"""
94
94
95 def __init__(self, fp):
95 def __init__(self, fp):
96 # assume the magic string is ok and drop it
97 # to be obviously fixed soon.
98 self._fp = fp
96 self._fp = fp
99 self._readexact(4)
97 header = self._readexact(4)
98 magic, version = header[0:2], header[2:4]
99 if magic != 'HG':
100 raise util.Abort(_('not a Mercurial bundle'))
101 if version != '20':
102 raise util.Abort(_('unknown bundle version %s') % version)
100
103
101 def _unpack(self, format):
104 def _unpack(self, format):
102 """unpack this struct format from the stream"""
105 """unpack this struct format from the stream"""
103 data = self._readexact(struct.calcsize(format))
106 data = self._readexact(struct.calcsize(format))
104 return _unpack(format, data)
107 return _unpack(format, data)
105
108
106 def _readexact(self, size):
109 def _readexact(self, size):
107 """read exactly <size> bytes from the stream"""
110 """read exactly <size> bytes from the stream"""
108 return changegroup.readexactly(self._fp, size)
111 return changegroup.readexactly(self._fp, size)
109
112
110 @util.propertycache
113 @util.propertycache
111 def params(self):
114 def params(self):
112 """dictionnary of stream level parameters"""
115 """dictionnary of stream level parameters"""
113 paramsize = self._readexact(2)
116 paramsize = self._readexact(2)
114 assert paramsize == '\0\0'
117 assert paramsize == '\0\0'
115 return {}
118 return {}
116
119
117 def __iter__(self):
120 def __iter__(self):
118 """yield all parts contained in the stream"""
121 """yield all parts contained in the stream"""
119 # make sure param have been loaded
122 # make sure param have been loaded
120 self.params
123 self.params
121 part = self._readpart()
124 part = self._readpart()
122 while part is not None:
125 while part is not None:
123 yield part
126 yield part
124 part = self._readpart()
127 part = self._readpart()
125
128
126 def _readpart(self):
129 def _readpart(self):
127 """return None when an end of stream markers is reach"""
130 """return None when an end of stream markers is reach"""
128 headersize = self._readexact(2)
131 headersize = self._readexact(2)
129 assert headersize == '\0\0'
132 assert headersize == '\0\0'
130 return None
133 return None
131
134
132
135
133
136
@@ -1,51 +1,62 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 > def cmdbundle2(ui, repo):
18 > def cmdbundle2(ui, repo):
19 > """write a bundle2 container on standard ouput"""
19 > """write a bundle2 container on standard ouput"""
20 > bundle = bundle2.bundle20()
20 > bundle = bundle2.bundle20()
21 > for chunk in bundle.getchunks():
21 > for chunk in bundle.getchunks():
22 > ui.write(chunk)
22 > ui.write(chunk)
23 >
23 >
24 > @command('unbundle2', [], '')
24 > @command('unbundle2', [], '')
25 > def cmdunbundle2(ui, repo):
25 > def cmdunbundle2(ui, repo):
26 > """read a bundle2 container from standard input"""
26 > """read a bundle2 container from standard input"""
27 > unbundler = bundle2.unbundle20(sys.stdin)
27 > unbundler = bundle2.unbundle20(sys.stdin)
28 > ui.write('options count: %i\n' % len(unbundler.params))
28 > ui.write('options count: %i\n' % len(unbundler.params))
29 > parts = list(unbundler)
29 > parts = list(unbundler)
30 > ui.write('parts count: %i\n' % len(parts))
30 > ui.write('parts count: %i\n' % len(parts))
31 > EOF
31 > EOF
32 $ cat >> $HGRCPATH << EOF
32 $ cat >> $HGRCPATH << EOF
33 > [extensions]
33 > [extensions]
34 > bundle2=$TESTTMP/bundle2.py
34 > bundle2=$TESTTMP/bundle2.py
35 > EOF
35 > EOF
36
36
37 The extension requires a repo (currently unused)
37 The extension requires a repo (currently unused)
38
38
39 $ hg init main
39 $ hg init main
40 $ cd main
40 $ cd main
41 $ touch a
42 $ hg add a
43 $ hg commit -m 'a'
41
44
42 Test simple generation of empty bundle
45 Test simple generation of empty bundle
43
46
44 $ hg bundle2
47 $ hg bundle2
45 HG20\x00\x00\x00\x00 (no-eol) (esc)
48 HG20\x00\x00\x00\x00 (no-eol) (esc)
46
49
47 Test parsing of an empty bundle
50 Test parsing of an empty bundle
48
51
49 $ hg bundle2 | hg unbundle2
52 $ hg bundle2 | hg unbundle2
50 options count: 0
53 options count: 0
51 parts count: 0
54 parts count: 0
55
56 Test old style bundle are detected and refused
57
58 $ hg bundle --all ../bundle.hg
59 1 changesets found
60 $ hg unbundle2 < ../bundle.hg
61 abort: unknown bundle version 10
62 [255]
General Comments 0
You need to be logged in to leave comments. Login now