##// END OF EJS Templates
wireproto: move clonebundles command from extension (issue4931)...
Gregory Szorc -
r26857:e5a1df51 stable
parent child Browse files
Show More
@@ -1,266 +1,254 b''
1 # This software may be used and distributed according to the terms of the
1 # This software may be used and distributed according to the terms of the
2 # GNU General Public License version 2 or any later version.
2 # GNU General Public License version 2 or any later version.
3
3
4 """advertise pre-generated bundles to seed clones (experimental)
4 """advertise pre-generated bundles to seed clones (experimental)
5
5
6 "clonebundles" is a server-side extension used to advertise the existence
6 "clonebundles" is a server-side extension used to advertise the existence
7 of pre-generated, externally hosted bundle files to clients that are
7 of pre-generated, externally hosted bundle files to clients that are
8 cloning so that cloning can be faster, more reliable, and require less
8 cloning so that cloning can be faster, more reliable, and require less
9 resources on the server.
9 resources on the server.
10
10
11 Cloning can be a CPU and I/O intensive operation on servers. Traditionally,
11 Cloning can be a CPU and I/O intensive operation on servers. Traditionally,
12 the server, in response to a client's request to clone, dynamically generates
12 the server, in response to a client's request to clone, dynamically generates
13 a bundle containing the entire repository content and sends it to the client.
13 a bundle containing the entire repository content and sends it to the client.
14 There is no caching on the server and the server will have to redundantly
14 There is no caching on the server and the server will have to redundantly
15 generate the same outgoing bundle in response to each clone request. For
15 generate the same outgoing bundle in response to each clone request. For
16 servers with large repositories or with high clone volume, the load from
16 servers with large repositories or with high clone volume, the load from
17 clones can make scaling the server challenging and costly.
17 clones can make scaling the server challenging and costly.
18
18
19 This extension provides server operators the ability to offload potentially
19 This extension provides server operators the ability to offload potentially
20 expensive clone load to an external service. Here's how it works.
20 expensive clone load to an external service. Here's how it works.
21
21
22 1. A server operator establishes a mechanism for making bundle files available
22 1. A server operator establishes a mechanism for making bundle files available
23 on a hosting service where Mercurial clients can fetch them.
23 on a hosting service where Mercurial clients can fetch them.
24 2. A manifest file listing available bundle URLs and some optional metadata
24 2. A manifest file listing available bundle URLs and some optional metadata
25 is added to the Mercurial repository on the server.
25 is added to the Mercurial repository on the server.
26 3. A client initiates a clone against a clone bundles aware server.
26 3. A client initiates a clone against a clone bundles aware server.
27 4. The client sees the server is advertising clone bundles and fetches the
27 4. The client sees the server is advertising clone bundles and fetches the
28 manifest listing available bundles.
28 manifest listing available bundles.
29 5. The client filters and sorts the available bundles based on what it
29 5. The client filters and sorts the available bundles based on what it
30 supports and prefers.
30 supports and prefers.
31 6. The client downloads and applies an available bundle from the
31 6. The client downloads and applies an available bundle from the
32 server-specified URL.
32 server-specified URL.
33 7. The client reconnects to the original server and performs the equivalent
33 7. The client reconnects to the original server and performs the equivalent
34 of :hg:`pull` to retrieve all repository data not in the bundle. (The
34 of :hg:`pull` to retrieve all repository data not in the bundle. (The
35 repository could have been updated between when the bundle was created
35 repository could have been updated between when the bundle was created
36 and when the client started the clone.)
36 and when the client started the clone.)
37
37
38 Instead of the server generating full repository bundles for every clone
38 Instead of the server generating full repository bundles for every clone
39 request, it generates full bundles once and they are subsequently reused to
39 request, it generates full bundles once and they are subsequently reused to
40 bootstrap new clones. The server may still transfer data at clone time.
40 bootstrap new clones. The server may still transfer data at clone time.
41 However, this is only data that has been added/changed since the bundle was
41 However, this is only data that has been added/changed since the bundle was
42 created. For large, established repositories, this can reduce server load for
42 created. For large, established repositories, this can reduce server load for
43 clones to less than 1% of original.
43 clones to less than 1% of original.
44
44
45 To work, this extension requires the following of server operators:
45 To work, this extension requires the following of server operators:
46
46
47 * Generating bundle files of repository content (typically periodically,
47 * Generating bundle files of repository content (typically periodically,
48 such as once per day).
48 such as once per day).
49 * A file server that clients have network access to and that Python knows
49 * A file server that clients have network access to and that Python knows
50 how to talk to through its normal URL handling facility (typically a
50 how to talk to through its normal URL handling facility (typically a
51 HTTP server).
51 HTTP server).
52 * A process for keeping the bundles manifest in sync with available bundle
52 * A process for keeping the bundles manifest in sync with available bundle
53 files.
53 files.
54
54
55 Strictly speaking, using a static file hosting server isn't required: a server
55 Strictly speaking, using a static file hosting server isn't required: a server
56 operator could use a dynamic service for retrieving bundle data. However,
56 operator could use a dynamic service for retrieving bundle data. However,
57 static file hosting services are simple and scalable and should be sufficient
57 static file hosting services are simple and scalable and should be sufficient
58 for most needs.
58 for most needs.
59
59
60 Bundle files can be generated with the :hg:`bundle` comand. Typically
60 Bundle files can be generated with the :hg:`bundle` comand. Typically
61 :hg:`bundle --all` is used to produce a bundle of the entire repository.
61 :hg:`bundle --all` is used to produce a bundle of the entire repository.
62
62
63 :hg:`debugcreatestreamclonebundle` can be used to produce a special
63 :hg:`debugcreatestreamclonebundle` can be used to produce a special
64 *streaming clone bundle*. These are bundle files that are extremely efficient
64 *streaming clone bundle*. These are bundle files that are extremely efficient
65 to produce and consume (read: fast). However, they are larger than
65 to produce and consume (read: fast). However, they are larger than
66 traditional bundle formats and require that clients support the exact set
66 traditional bundle formats and require that clients support the exact set
67 of repository data store formats in use by the repository that created them.
67 of repository data store formats in use by the repository that created them.
68 Typically, a newer server can serve data that is compatible with older clients.
68 Typically, a newer server can serve data that is compatible with older clients.
69 However, *streaming clone bundles* don't have this guarantee. **Server
69 However, *streaming clone bundles* don't have this guarantee. **Server
70 operators need to be aware that newer versions of Mercurial may produce
70 operators need to be aware that newer versions of Mercurial may produce
71 streaming clone bundles incompatible with older Mercurial versions.**
71 streaming clone bundles incompatible with older Mercurial versions.**
72
72
73 The list of requirements printed by :hg:`debugcreatestreamclonebundle` should
73 The list of requirements printed by :hg:`debugcreatestreamclonebundle` should
74 be specified in the ``requirements`` parameter of the *bundle specification
74 be specified in the ``requirements`` parameter of the *bundle specification
75 string* for the ``BUNDLESPEC`` manifest property described below. e.g.
75 string* for the ``BUNDLESPEC`` manifest property described below. e.g.
76 ``BUNDLESPEC=none-packed1;requirements%3Drevlogv1``.
76 ``BUNDLESPEC=none-packed1;requirements%3Drevlogv1``.
77
77
78 A server operator is responsible for creating a ``.hg/clonebundles.manifest``
78 A server operator is responsible for creating a ``.hg/clonebundles.manifest``
79 file containing the list of available bundle files suitable for seeding
79 file containing the list of available bundle files suitable for seeding
80 clones. If this file does not exist, the repository will not advertise the
80 clones. If this file does not exist, the repository will not advertise the
81 existence of clone bundles when clients connect.
81 existence of clone bundles when clients connect.
82
82
83 The manifest file contains a newline (\n) delimited list of entries.
83 The manifest file contains a newline (\n) delimited list of entries.
84
84
85 Each line in this file defines an available bundle. Lines have the format:
85 Each line in this file defines an available bundle. Lines have the format:
86
86
87 <URL> [<key>=<value>[ <key>=<value>]]
87 <URL> [<key>=<value>[ <key>=<value>]]
88
88
89 That is, a URL followed by an optional, space-delimited list of key=value
89 That is, a URL followed by an optional, space-delimited list of key=value
90 pairs describing additional properties of this bundle. Both keys and values
90 pairs describing additional properties of this bundle. Both keys and values
91 are URI encoded.
91 are URI encoded.
92
92
93 Keys in UPPERCASE are reserved for use by Mercurial and are defined below.
93 Keys in UPPERCASE are reserved for use by Mercurial and are defined below.
94 All non-uppercase keys can be used by site installations. An example use
94 All non-uppercase keys can be used by site installations. An example use
95 for custom properties is to use the *datacenter* attribute to define which
95 for custom properties is to use the *datacenter* attribute to define which
96 data center a file is hosted in. Clients could then prefer a server in the
96 data center a file is hosted in. Clients could then prefer a server in the
97 data center closest to them.
97 data center closest to them.
98
98
99 The following reserved keys are currently defined:
99 The following reserved keys are currently defined:
100
100
101 BUNDLESPEC
101 BUNDLESPEC
102 A "bundle specification" string that describes the type of the bundle.
102 A "bundle specification" string that describes the type of the bundle.
103
103
104 These are string values that are accepted by the "--type" argument of
104 These are string values that are accepted by the "--type" argument of
105 :hg:`bundle`.
105 :hg:`bundle`.
106
106
107 The values are parsed in strict mode, which means they must be of the
107 The values are parsed in strict mode, which means they must be of the
108 "<compression>-<type>" form. See
108 "<compression>-<type>" form. See
109 mercurial.exchange.parsebundlespec() for more details.
109 mercurial.exchange.parsebundlespec() for more details.
110
110
111 Clients will automatically filter out specifications that are unknown or
111 Clients will automatically filter out specifications that are unknown or
112 unsupported so they won't attempt to download something that likely won't
112 unsupported so they won't attempt to download something that likely won't
113 apply.
113 apply.
114
114
115 The actual value doesn't impact client behavior beyond filtering:
115 The actual value doesn't impact client behavior beyond filtering:
116 clients will still sniff the bundle type from the header of downloaded
116 clients will still sniff the bundle type from the header of downloaded
117 files.
117 files.
118
118
119 **Use of this key is highly recommended**, as it allows clients to
119 **Use of this key is highly recommended**, as it allows clients to
120 easily skip unsupported bundles.
120 easily skip unsupported bundles.
121
121
122 REQUIRESNI
122 REQUIRESNI
123 Whether Server Name Indication (SNI) is required to connect to the URL.
123 Whether Server Name Indication (SNI) is required to connect to the URL.
124 SNI allows servers to use multiple certificates on the same IP. It is
124 SNI allows servers to use multiple certificates on the same IP. It is
125 somewhat common in CDNs and other hosting providers. Older Python
125 somewhat common in CDNs and other hosting providers. Older Python
126 versions do not support SNI. Defining this attribute enables clients
126 versions do not support SNI. Defining this attribute enables clients
127 with older Python versions to filter this entry without experiencing
127 with older Python versions to filter this entry without experiencing
128 an opaque SSL failure at connection time.
128 an opaque SSL failure at connection time.
129
129
130 If this is defined, it is important to advertise a non-SNI fallback
130 If this is defined, it is important to advertise a non-SNI fallback
131 URL or clients running old Python releases may not be able to clone
131 URL or clients running old Python releases may not be able to clone
132 with the clonebundles facility.
132 with the clonebundles facility.
133
133
134 Value should be "true".
134 Value should be "true".
135
135
136 Manifests can contain multiple entries. Assuming metadata is defined, clients
136 Manifests can contain multiple entries. Assuming metadata is defined, clients
137 will filter entries from the manifest that they don't support. The remaining
137 will filter entries from the manifest that they don't support. The remaining
138 entries are optionally sorted by client preferences
138 entries are optionally sorted by client preferences
139 (``experimental.clonebundleprefers`` config option). The client then attempts
139 (``experimental.clonebundleprefers`` config option). The client then attempts
140 to fetch the bundle at the first URL in the remaining list.
140 to fetch the bundle at the first URL in the remaining list.
141
141
142 **Errors when downloading a bundle will fail the entire clone operation:
142 **Errors when downloading a bundle will fail the entire clone operation:
143 clients do not automatically fall back to a traditional clone.** The reason
143 clients do not automatically fall back to a traditional clone.** The reason
144 for this is that if a server is using clone bundles, it is probably doing so
144 for this is that if a server is using clone bundles, it is probably doing so
145 because the feature is necessary to help it scale. In other words, there
145 because the feature is necessary to help it scale. In other words, there
146 is an assumption that clone load will be offloaded to another service and
146 is an assumption that clone load will be offloaded to another service and
147 that the Mercurial server isn't responsible for serving this clone load.
147 that the Mercurial server isn't responsible for serving this clone load.
148 If that other service experiences issues and clients start mass falling back to
148 If that other service experiences issues and clients start mass falling back to
149 the original Mercurial server, the added clone load could overwhelm the server
149 the original Mercurial server, the added clone load could overwhelm the server
150 due to unexpected load and effectively take it offline. Not having clients
150 due to unexpected load and effectively take it offline. Not having clients
151 automatically fall back to cloning from the original server mitigates this
151 automatically fall back to cloning from the original server mitigates this
152 scenario.
152 scenario.
153
153
154 Because there is no automatic Mercurial server fallback on failure of the
154 Because there is no automatic Mercurial server fallback on failure of the
155 bundle hosting service, it is important for server operators to view the bundle
155 bundle hosting service, it is important for server operators to view the bundle
156 hosting service as an extension of the Mercurial server in terms of
156 hosting service as an extension of the Mercurial server in terms of
157 availability and service level agreements: if the bundle hosting service goes
157 availability and service level agreements: if the bundle hosting service goes
158 down, so does the ability for clients to clone. Note: clients will see a
158 down, so does the ability for clients to clone. Note: clients will see a
159 message informing them how to bypass the clone bundles facility when a failure
159 message informing them how to bypass the clone bundles facility when a failure
160 occurs. So server operators should prepare for some people to follow these
160 occurs. So server operators should prepare for some people to follow these
161 instructions when a failure occurs, thus driving more load to the original
161 instructions when a failure occurs, thus driving more load to the original
162 Mercurial server when the bundle hosting service fails.
162 Mercurial server when the bundle hosting service fails.
163
163
164 The following config options influence the behavior of the clone bundles
164 The following config options influence the behavior of the clone bundles
165 feature:
165 feature:
166
166
167 ui.clonebundleadvertise
167 ui.clonebundleadvertise
168 Whether the server advertises the existence of the clone bundles feature
168 Whether the server advertises the existence of the clone bundles feature
169 to compatible clients that aren't using it.
169 to compatible clients that aren't using it.
170
170
171 When this is enabled (the default), a server will send a message to
171 When this is enabled (the default), a server will send a message to
172 compatible clients performing a traditional clone informing them of the
172 compatible clients performing a traditional clone informing them of the
173 available clone bundles feature. Compatible clients are those that support
173 available clone bundles feature. Compatible clients are those that support
174 bundle2 and are advertising support for the clone bundles feature.
174 bundle2 and are advertising support for the clone bundles feature.
175
175
176 ui.clonebundlefallback
176 ui.clonebundlefallback
177 Whether to automatically fall back to a traditional clone in case of
177 Whether to automatically fall back to a traditional clone in case of
178 clone bundles failure. Defaults to false for reasons described above.
178 clone bundles failure. Defaults to false for reasons described above.
179
179
180 experimental.clonebundles
180 experimental.clonebundles
181 Whether the clone bundles feature is enabled on clients. Defaults to true.
181 Whether the clone bundles feature is enabled on clients. Defaults to true.
182
182
183 experimental.clonebundleprefers
183 experimental.clonebundleprefers
184 List of "key=value" properties the client prefers in bundles. Downloaded
184 List of "key=value" properties the client prefers in bundles. Downloaded
185 bundle manifests will be sorted by the preferences in this list. e.g.
185 bundle manifests will be sorted by the preferences in this list. e.g.
186 the value "BUNDLESPEC=gzip-v1, BUNDLESPEC=bzip2=v1" will prefer a gzipped
186 the value "BUNDLESPEC=gzip-v1, BUNDLESPEC=bzip2=v1" will prefer a gzipped
187 version 1 bundle type then bzip2 version 1 bundle type.
187 version 1 bundle type then bzip2 version 1 bundle type.
188
188
189 If not defined, the order in the manifest will be used and the first
189 If not defined, the order in the manifest will be used and the first
190 available bundle will be downloaded.
190 available bundle will be downloaded.
191 """
191 """
192
192
193 from mercurial.i18n import _
193 from mercurial.i18n import _
194 from mercurial.node import nullid
194 from mercurial.node import nullid
195 from mercurial import (
195 from mercurial import (
196 exchange,
196 exchange,
197 extensions,
197 extensions,
198 wireproto,
198 wireproto,
199 )
199 )
200
200
201 testedwith = 'internal'
201 testedwith = 'internal'
202
202
203 def capabilities(orig, repo, proto):
203 def capabilities(orig, repo, proto):
204 caps = orig(repo, proto)
204 caps = orig(repo, proto)
205
205
206 # Only advertise if a manifest exists. This does add some I/O to requests.
206 # Only advertise if a manifest exists. This does add some I/O to requests.
207 # But this should be cheaper than a wasted network round trip due to
207 # But this should be cheaper than a wasted network round trip due to
208 # missing file.
208 # missing file.
209 if repo.opener.exists('clonebundles.manifest'):
209 if repo.opener.exists('clonebundles.manifest'):
210 caps.append('clonebundles')
210 caps.append('clonebundles')
211
211
212 return caps
212 return caps
213
213
214 @wireproto.wireprotocommand('clonebundles', '')
215 def bundles(repo, proto):
216 """Server command for returning info for available bundles to seed clones.
217
218 Clients will parse this response and determine what bundle to fetch.
219
220 Other extensions may wrap this command to filter or dynamically emit
221 data depending on the request. e.g. you could advertise URLs for
222 the closest data center given the client's IP address.
223 """
224 return repo.opener.tryread('clonebundles.manifest')
225
226 @exchange.getbundle2partsgenerator('clonebundlesadvertise', 0)
214 @exchange.getbundle2partsgenerator('clonebundlesadvertise', 0)
227 def advertiseclonebundlespart(bundler, repo, source, bundlecaps=None,
215 def advertiseclonebundlespart(bundler, repo, source, bundlecaps=None,
228 b2caps=None, heads=None, common=None,
216 b2caps=None, heads=None, common=None,
229 cbattempted=None, **kwargs):
217 cbattempted=None, **kwargs):
230 """Inserts an output part to advertise clone bundles availability."""
218 """Inserts an output part to advertise clone bundles availability."""
231 # Allow server operators to disable this behavior.
219 # Allow server operators to disable this behavior.
232 # # experimental config: ui.clonebundleadvertise
220 # # experimental config: ui.clonebundleadvertise
233 if not repo.ui.configbool('ui', 'clonebundleadvertise', True):
221 if not repo.ui.configbool('ui', 'clonebundleadvertise', True):
234 return
222 return
235
223
236 # Only advertise if a manifest is present.
224 # Only advertise if a manifest is present.
237 if not repo.opener.exists('clonebundles.manifest'):
225 if not repo.opener.exists('clonebundles.manifest'):
238 return
226 return
239
227
240 # And when changegroup data is requested.
228 # And when changegroup data is requested.
241 if not kwargs.get('cg', True):
229 if not kwargs.get('cg', True):
242 return
230 return
243
231
244 # And when the client supports clone bundles.
232 # And when the client supports clone bundles.
245 if cbattempted is None:
233 if cbattempted is None:
246 return
234 return
247
235
248 # And when the client didn't attempt a clone bundle as part of this pull.
236 # And when the client didn't attempt a clone bundle as part of this pull.
249 if cbattempted:
237 if cbattempted:
250 return
238 return
251
239
252 # And when a full clone is requested.
240 # And when a full clone is requested.
253 # Note: client should not send "cbattempted" for regular pulls. This check
241 # Note: client should not send "cbattempted" for regular pulls. This check
254 # is defense in depth.
242 # is defense in depth.
255 if common and common != [nullid]:
243 if common and common != [nullid]:
256 return
244 return
257
245
258 msg = _('this server supports the experimental "clone bundles" feature '
246 msg = _('this server supports the experimental "clone bundles" feature '
259 'that should enable faster and more reliable cloning\n'
247 'that should enable faster and more reliable cloning\n'
260 'help test it by setting the "experimental.clonebundles" config '
248 'help test it by setting the "experimental.clonebundles" config '
261 'flag to "true"')
249 'flag to "true"')
262
250
263 bundler.newpart('output', data=msg)
251 bundler.newpart('output', data=msg)
264
252
265 def extsetup(ui):
253 def extsetup(ui):
266 extensions.wrapfunction(wireproto, '_capabilities', capabilities)
254 extensions.wrapfunction(wireproto, '_capabilities', capabilities)
@@ -1,816 +1,827 b''
1 # wireproto.py - generic wire protocol support functions
1 # wireproto.py - generic wire protocol support functions
2 #
2 #
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
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
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11 import sys
11 import sys
12 import tempfile
12 import tempfile
13 import urllib
13 import urllib
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 bin,
17 bin,
18 hex,
18 hex,
19 )
19 )
20
20
21 from . import (
21 from . import (
22 bundle2,
22 bundle2,
23 changegroup as changegroupmod,
23 changegroup as changegroupmod,
24 encoding,
24 encoding,
25 error,
25 error,
26 exchange,
26 exchange,
27 peer,
27 peer,
28 pushkey as pushkeymod,
28 pushkey as pushkeymod,
29 streamclone,
29 streamclone,
30 util,
30 util,
31 )
31 )
32
32
33 class abstractserverproto(object):
33 class abstractserverproto(object):
34 """abstract class that summarizes the protocol API
34 """abstract class that summarizes the protocol API
35
35
36 Used as reference and documentation.
36 Used as reference and documentation.
37 """
37 """
38
38
39 def getargs(self, args):
39 def getargs(self, args):
40 """return the value for arguments in <args>
40 """return the value for arguments in <args>
41
41
42 returns a list of values (same order as <args>)"""
42 returns a list of values (same order as <args>)"""
43 raise NotImplementedError()
43 raise NotImplementedError()
44
44
45 def getfile(self, fp):
45 def getfile(self, fp):
46 """write the whole content of a file into a file like object
46 """write the whole content of a file into a file like object
47
47
48 The file is in the form::
48 The file is in the form::
49
49
50 (<chunk-size>\n<chunk>)+0\n
50 (<chunk-size>\n<chunk>)+0\n
51
51
52 chunk size is the ascii version of the int.
52 chunk size is the ascii version of the int.
53 """
53 """
54 raise NotImplementedError()
54 raise NotImplementedError()
55
55
56 def redirect(self):
56 def redirect(self):
57 """may setup interception for stdout and stderr
57 """may setup interception for stdout and stderr
58
58
59 See also the `restore` method."""
59 See also the `restore` method."""
60 raise NotImplementedError()
60 raise NotImplementedError()
61
61
62 # If the `redirect` function does install interception, the `restore`
62 # If the `redirect` function does install interception, the `restore`
63 # function MUST be defined. If interception is not used, this function
63 # function MUST be defined. If interception is not used, this function
64 # MUST NOT be defined.
64 # MUST NOT be defined.
65 #
65 #
66 # left commented here on purpose
66 # left commented here on purpose
67 #
67 #
68 #def restore(self):
68 #def restore(self):
69 # """reinstall previous stdout and stderr and return intercepted stdout
69 # """reinstall previous stdout and stderr and return intercepted stdout
70 # """
70 # """
71 # raise NotImplementedError()
71 # raise NotImplementedError()
72
72
73 def groupchunks(self, cg):
73 def groupchunks(self, cg):
74 """return 4096 chunks from a changegroup object
74 """return 4096 chunks from a changegroup object
75
75
76 Some protocols may have compressed the contents."""
76 Some protocols may have compressed the contents."""
77 raise NotImplementedError()
77 raise NotImplementedError()
78
78
79 class remotebatch(peer.batcher):
79 class remotebatch(peer.batcher):
80 '''batches the queued calls; uses as few roundtrips as possible'''
80 '''batches the queued calls; uses as few roundtrips as possible'''
81 def __init__(self, remote):
81 def __init__(self, remote):
82 '''remote must support _submitbatch(encbatch) and
82 '''remote must support _submitbatch(encbatch) and
83 _submitone(op, encargs)'''
83 _submitone(op, encargs)'''
84 peer.batcher.__init__(self)
84 peer.batcher.__init__(self)
85 self.remote = remote
85 self.remote = remote
86 def submit(self):
86 def submit(self):
87 req, rsp = [], []
87 req, rsp = [], []
88 for name, args, opts, resref in self.calls:
88 for name, args, opts, resref in self.calls:
89 mtd = getattr(self.remote, name)
89 mtd = getattr(self.remote, name)
90 batchablefn = getattr(mtd, 'batchable', None)
90 batchablefn = getattr(mtd, 'batchable', None)
91 if batchablefn is not None:
91 if batchablefn is not None:
92 batchable = batchablefn(mtd.im_self, *args, **opts)
92 batchable = batchablefn(mtd.im_self, *args, **opts)
93 encargsorres, encresref = batchable.next()
93 encargsorres, encresref = batchable.next()
94 if encresref:
94 if encresref:
95 req.append((name, encargsorres,))
95 req.append((name, encargsorres,))
96 rsp.append((batchable, encresref, resref,))
96 rsp.append((batchable, encresref, resref,))
97 else:
97 else:
98 resref.set(encargsorres)
98 resref.set(encargsorres)
99 else:
99 else:
100 if req:
100 if req:
101 self._submitreq(req, rsp)
101 self._submitreq(req, rsp)
102 req, rsp = [], []
102 req, rsp = [], []
103 resref.set(mtd(*args, **opts))
103 resref.set(mtd(*args, **opts))
104 if req:
104 if req:
105 self._submitreq(req, rsp)
105 self._submitreq(req, rsp)
106 def _submitreq(self, req, rsp):
106 def _submitreq(self, req, rsp):
107 encresults = self.remote._submitbatch(req)
107 encresults = self.remote._submitbatch(req)
108 for encres, r in zip(encresults, rsp):
108 for encres, r in zip(encresults, rsp):
109 batchable, encresref, resref = r
109 batchable, encresref, resref = r
110 encresref.set(encres)
110 encresref.set(encres)
111 resref.set(batchable.next())
111 resref.set(batchable.next())
112
112
113 # Forward a couple of names from peer to make wireproto interactions
113 # Forward a couple of names from peer to make wireproto interactions
114 # slightly more sensible.
114 # slightly more sensible.
115 batchable = peer.batchable
115 batchable = peer.batchable
116 future = peer.future
116 future = peer.future
117
117
118 # list of nodes encoding / decoding
118 # list of nodes encoding / decoding
119
119
120 def decodelist(l, sep=' '):
120 def decodelist(l, sep=' '):
121 if l:
121 if l:
122 return map(bin, l.split(sep))
122 return map(bin, l.split(sep))
123 return []
123 return []
124
124
125 def encodelist(l, sep=' '):
125 def encodelist(l, sep=' '):
126 try:
126 try:
127 return sep.join(map(hex, l))
127 return sep.join(map(hex, l))
128 except TypeError:
128 except TypeError:
129 raise
129 raise
130
130
131 # batched call argument encoding
131 # batched call argument encoding
132
132
133 def escapearg(plain):
133 def escapearg(plain):
134 return (plain
134 return (plain
135 .replace(':', ':c')
135 .replace(':', ':c')
136 .replace(',', ':o')
136 .replace(',', ':o')
137 .replace(';', ':s')
137 .replace(';', ':s')
138 .replace('=', ':e'))
138 .replace('=', ':e'))
139
139
140 def unescapearg(escaped):
140 def unescapearg(escaped):
141 return (escaped
141 return (escaped
142 .replace(':e', '=')
142 .replace(':e', '=')
143 .replace(':s', ';')
143 .replace(':s', ';')
144 .replace(':o', ',')
144 .replace(':o', ',')
145 .replace(':c', ':'))
145 .replace(':c', ':'))
146
146
147 # mapping of options accepted by getbundle and their types
147 # mapping of options accepted by getbundle and their types
148 #
148 #
149 # Meant to be extended by extensions. It is extensions responsibility to ensure
149 # Meant to be extended by extensions. It is extensions responsibility to ensure
150 # such options are properly processed in exchange.getbundle.
150 # such options are properly processed in exchange.getbundle.
151 #
151 #
152 # supported types are:
152 # supported types are:
153 #
153 #
154 # :nodes: list of binary nodes
154 # :nodes: list of binary nodes
155 # :csv: list of comma-separated values
155 # :csv: list of comma-separated values
156 # :scsv: list of comma-separated values return as set
156 # :scsv: list of comma-separated values return as set
157 # :plain: string with no transformation needed.
157 # :plain: string with no transformation needed.
158 gboptsmap = {'heads': 'nodes',
158 gboptsmap = {'heads': 'nodes',
159 'common': 'nodes',
159 'common': 'nodes',
160 'obsmarkers': 'boolean',
160 'obsmarkers': 'boolean',
161 'bundlecaps': 'scsv',
161 'bundlecaps': 'scsv',
162 'listkeys': 'csv',
162 'listkeys': 'csv',
163 'cg': 'boolean',
163 'cg': 'boolean',
164 'cbattempted': 'boolean'}
164 'cbattempted': 'boolean'}
165
165
166 # client side
166 # client side
167
167
168 class wirepeer(peer.peerrepository):
168 class wirepeer(peer.peerrepository):
169
169
170 def batch(self):
170 def batch(self):
171 if self.capable('batch'):
171 if self.capable('batch'):
172 return remotebatch(self)
172 return remotebatch(self)
173 else:
173 else:
174 return peer.localbatch(self)
174 return peer.localbatch(self)
175 def _submitbatch(self, req):
175 def _submitbatch(self, req):
176 cmds = []
176 cmds = []
177 for op, argsdict in req:
177 for op, argsdict in req:
178 args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
178 args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
179 for k, v in argsdict.iteritems())
179 for k, v in argsdict.iteritems())
180 cmds.append('%s %s' % (op, args))
180 cmds.append('%s %s' % (op, args))
181 rsp = self._call("batch", cmds=';'.join(cmds))
181 rsp = self._call("batch", cmds=';'.join(cmds))
182 return [unescapearg(r) for r in rsp.split(';')]
182 return [unescapearg(r) for r in rsp.split(';')]
183 def _submitone(self, op, args):
183 def _submitone(self, op, args):
184 return self._call(op, **args)
184 return self._call(op, **args)
185
185
186 @batchable
186 @batchable
187 def lookup(self, key):
187 def lookup(self, key):
188 self.requirecap('lookup', _('look up remote revision'))
188 self.requirecap('lookup', _('look up remote revision'))
189 f = future()
189 f = future()
190 yield {'key': encoding.fromlocal(key)}, f
190 yield {'key': encoding.fromlocal(key)}, f
191 d = f.value
191 d = f.value
192 success, data = d[:-1].split(" ", 1)
192 success, data = d[:-1].split(" ", 1)
193 if int(success):
193 if int(success):
194 yield bin(data)
194 yield bin(data)
195 self._abort(error.RepoError(data))
195 self._abort(error.RepoError(data))
196
196
197 @batchable
197 @batchable
198 def heads(self):
198 def heads(self):
199 f = future()
199 f = future()
200 yield {}, f
200 yield {}, f
201 d = f.value
201 d = f.value
202 try:
202 try:
203 yield decodelist(d[:-1])
203 yield decodelist(d[:-1])
204 except ValueError:
204 except ValueError:
205 self._abort(error.ResponseError(_("unexpected response:"), d))
205 self._abort(error.ResponseError(_("unexpected response:"), d))
206
206
207 @batchable
207 @batchable
208 def known(self, nodes):
208 def known(self, nodes):
209 f = future()
209 f = future()
210 yield {'nodes': encodelist(nodes)}, f
210 yield {'nodes': encodelist(nodes)}, f
211 d = f.value
211 d = f.value
212 try:
212 try:
213 yield [bool(int(b)) for b in d]
213 yield [bool(int(b)) for b in d]
214 except ValueError:
214 except ValueError:
215 self._abort(error.ResponseError(_("unexpected response:"), d))
215 self._abort(error.ResponseError(_("unexpected response:"), d))
216
216
217 @batchable
217 @batchable
218 def branchmap(self):
218 def branchmap(self):
219 f = future()
219 f = future()
220 yield {}, f
220 yield {}, f
221 d = f.value
221 d = f.value
222 try:
222 try:
223 branchmap = {}
223 branchmap = {}
224 for branchpart in d.splitlines():
224 for branchpart in d.splitlines():
225 branchname, branchheads = branchpart.split(' ', 1)
225 branchname, branchheads = branchpart.split(' ', 1)
226 branchname = encoding.tolocal(urllib.unquote(branchname))
226 branchname = encoding.tolocal(urllib.unquote(branchname))
227 branchheads = decodelist(branchheads)
227 branchheads = decodelist(branchheads)
228 branchmap[branchname] = branchheads
228 branchmap[branchname] = branchheads
229 yield branchmap
229 yield branchmap
230 except TypeError:
230 except TypeError:
231 self._abort(error.ResponseError(_("unexpected response:"), d))
231 self._abort(error.ResponseError(_("unexpected response:"), d))
232
232
233 def branches(self, nodes):
233 def branches(self, nodes):
234 n = encodelist(nodes)
234 n = encodelist(nodes)
235 d = self._call("branches", nodes=n)
235 d = self._call("branches", nodes=n)
236 try:
236 try:
237 br = [tuple(decodelist(b)) for b in d.splitlines()]
237 br = [tuple(decodelist(b)) for b in d.splitlines()]
238 return br
238 return br
239 except ValueError:
239 except ValueError:
240 self._abort(error.ResponseError(_("unexpected response:"), d))
240 self._abort(error.ResponseError(_("unexpected response:"), d))
241
241
242 def between(self, pairs):
242 def between(self, pairs):
243 batch = 8 # avoid giant requests
243 batch = 8 # avoid giant requests
244 r = []
244 r = []
245 for i in xrange(0, len(pairs), batch):
245 for i in xrange(0, len(pairs), batch):
246 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
246 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
247 d = self._call("between", pairs=n)
247 d = self._call("between", pairs=n)
248 try:
248 try:
249 r.extend(l and decodelist(l) or [] for l in d.splitlines())
249 r.extend(l and decodelist(l) or [] for l in d.splitlines())
250 except ValueError:
250 except ValueError:
251 self._abort(error.ResponseError(_("unexpected response:"), d))
251 self._abort(error.ResponseError(_("unexpected response:"), d))
252 return r
252 return r
253
253
254 @batchable
254 @batchable
255 def pushkey(self, namespace, key, old, new):
255 def pushkey(self, namespace, key, old, new):
256 if not self.capable('pushkey'):
256 if not self.capable('pushkey'):
257 yield False, None
257 yield False, None
258 f = future()
258 f = future()
259 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
259 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
260 yield {'namespace': encoding.fromlocal(namespace),
260 yield {'namespace': encoding.fromlocal(namespace),
261 'key': encoding.fromlocal(key),
261 'key': encoding.fromlocal(key),
262 'old': encoding.fromlocal(old),
262 'old': encoding.fromlocal(old),
263 'new': encoding.fromlocal(new)}, f
263 'new': encoding.fromlocal(new)}, f
264 d = f.value
264 d = f.value
265 d, output = d.split('\n', 1)
265 d, output = d.split('\n', 1)
266 try:
266 try:
267 d = bool(int(d))
267 d = bool(int(d))
268 except ValueError:
268 except ValueError:
269 raise error.ResponseError(
269 raise error.ResponseError(
270 _('push failed (unexpected response):'), d)
270 _('push failed (unexpected response):'), d)
271 for l in output.splitlines(True):
271 for l in output.splitlines(True):
272 self.ui.status(_('remote: '), l)
272 self.ui.status(_('remote: '), l)
273 yield d
273 yield d
274
274
275 @batchable
275 @batchable
276 def listkeys(self, namespace):
276 def listkeys(self, namespace):
277 if not self.capable('pushkey'):
277 if not self.capable('pushkey'):
278 yield {}, None
278 yield {}, None
279 f = future()
279 f = future()
280 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
280 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
281 yield {'namespace': encoding.fromlocal(namespace)}, f
281 yield {'namespace': encoding.fromlocal(namespace)}, f
282 d = f.value
282 d = f.value
283 self.ui.debug('received listkey for "%s": %i bytes\n'
283 self.ui.debug('received listkey for "%s": %i bytes\n'
284 % (namespace, len(d)))
284 % (namespace, len(d)))
285 yield pushkeymod.decodekeys(d)
285 yield pushkeymod.decodekeys(d)
286
286
287 def stream_out(self):
287 def stream_out(self):
288 return self._callstream('stream_out')
288 return self._callstream('stream_out')
289
289
290 def changegroup(self, nodes, kind):
290 def changegroup(self, nodes, kind):
291 n = encodelist(nodes)
291 n = encodelist(nodes)
292 f = self._callcompressable("changegroup", roots=n)
292 f = self._callcompressable("changegroup", roots=n)
293 return changegroupmod.cg1unpacker(f, 'UN')
293 return changegroupmod.cg1unpacker(f, 'UN')
294
294
295 def changegroupsubset(self, bases, heads, kind):
295 def changegroupsubset(self, bases, heads, kind):
296 self.requirecap('changegroupsubset', _('look up remote changes'))
296 self.requirecap('changegroupsubset', _('look up remote changes'))
297 bases = encodelist(bases)
297 bases = encodelist(bases)
298 heads = encodelist(heads)
298 heads = encodelist(heads)
299 f = self._callcompressable("changegroupsubset",
299 f = self._callcompressable("changegroupsubset",
300 bases=bases, heads=heads)
300 bases=bases, heads=heads)
301 return changegroupmod.cg1unpacker(f, 'UN')
301 return changegroupmod.cg1unpacker(f, 'UN')
302
302
303 def getbundle(self, source, **kwargs):
303 def getbundle(self, source, **kwargs):
304 self.requirecap('getbundle', _('look up remote changes'))
304 self.requirecap('getbundle', _('look up remote changes'))
305 opts = {}
305 opts = {}
306 bundlecaps = kwargs.get('bundlecaps')
306 bundlecaps = kwargs.get('bundlecaps')
307 if bundlecaps is not None:
307 if bundlecaps is not None:
308 kwargs['bundlecaps'] = sorted(bundlecaps)
308 kwargs['bundlecaps'] = sorted(bundlecaps)
309 else:
309 else:
310 bundlecaps = () # kwargs could have it to None
310 bundlecaps = () # kwargs could have it to None
311 for key, value in kwargs.iteritems():
311 for key, value in kwargs.iteritems():
312 if value is None:
312 if value is None:
313 continue
313 continue
314 keytype = gboptsmap.get(key)
314 keytype = gboptsmap.get(key)
315 if keytype is None:
315 if keytype is None:
316 assert False, 'unexpected'
316 assert False, 'unexpected'
317 elif keytype == 'nodes':
317 elif keytype == 'nodes':
318 value = encodelist(value)
318 value = encodelist(value)
319 elif keytype in ('csv', 'scsv'):
319 elif keytype in ('csv', 'scsv'):
320 value = ','.join(value)
320 value = ','.join(value)
321 elif keytype == 'boolean':
321 elif keytype == 'boolean':
322 value = '%i' % bool(value)
322 value = '%i' % bool(value)
323 elif keytype != 'plain':
323 elif keytype != 'plain':
324 raise KeyError('unknown getbundle option type %s'
324 raise KeyError('unknown getbundle option type %s'
325 % keytype)
325 % keytype)
326 opts[key] = value
326 opts[key] = value
327 f = self._callcompressable("getbundle", **opts)
327 f = self._callcompressable("getbundle", **opts)
328 if any((cap.startswith('HG2') for cap in bundlecaps)):
328 if any((cap.startswith('HG2') for cap in bundlecaps)):
329 return bundle2.getunbundler(self.ui, f)
329 return bundle2.getunbundler(self.ui, f)
330 else:
330 else:
331 return changegroupmod.cg1unpacker(f, 'UN')
331 return changegroupmod.cg1unpacker(f, 'UN')
332
332
333 def unbundle(self, cg, heads, source):
333 def unbundle(self, cg, heads, source):
334 '''Send cg (a readable file-like object representing the
334 '''Send cg (a readable file-like object representing the
335 changegroup to push, typically a chunkbuffer object) to the
335 changegroup to push, typically a chunkbuffer object) to the
336 remote server as a bundle.
336 remote server as a bundle.
337
337
338 When pushing a bundle10 stream, return an integer indicating the
338 When pushing a bundle10 stream, return an integer indicating the
339 result of the push (see localrepository.addchangegroup()).
339 result of the push (see localrepository.addchangegroup()).
340
340
341 When pushing a bundle20 stream, return a bundle20 stream.'''
341 When pushing a bundle20 stream, return a bundle20 stream.'''
342
342
343 if heads != ['force'] and self.capable('unbundlehash'):
343 if heads != ['force'] and self.capable('unbundlehash'):
344 heads = encodelist(['hashed',
344 heads = encodelist(['hashed',
345 util.sha1(''.join(sorted(heads))).digest()])
345 util.sha1(''.join(sorted(heads))).digest()])
346 else:
346 else:
347 heads = encodelist(heads)
347 heads = encodelist(heads)
348
348
349 if util.safehasattr(cg, 'deltaheader'):
349 if util.safehasattr(cg, 'deltaheader'):
350 # this a bundle10, do the old style call sequence
350 # this a bundle10, do the old style call sequence
351 ret, output = self._callpush("unbundle", cg, heads=heads)
351 ret, output = self._callpush("unbundle", cg, heads=heads)
352 if ret == "":
352 if ret == "":
353 raise error.ResponseError(
353 raise error.ResponseError(
354 _('push failed:'), output)
354 _('push failed:'), output)
355 try:
355 try:
356 ret = int(ret)
356 ret = int(ret)
357 except ValueError:
357 except ValueError:
358 raise error.ResponseError(
358 raise error.ResponseError(
359 _('push failed (unexpected response):'), ret)
359 _('push failed (unexpected response):'), ret)
360
360
361 for l in output.splitlines(True):
361 for l in output.splitlines(True):
362 self.ui.status(_('remote: '), l)
362 self.ui.status(_('remote: '), l)
363 else:
363 else:
364 # bundle2 push. Send a stream, fetch a stream.
364 # bundle2 push. Send a stream, fetch a stream.
365 stream = self._calltwowaystream('unbundle', cg, heads=heads)
365 stream = self._calltwowaystream('unbundle', cg, heads=heads)
366 ret = bundle2.getunbundler(self.ui, stream)
366 ret = bundle2.getunbundler(self.ui, stream)
367 return ret
367 return ret
368
368
369 def debugwireargs(self, one, two, three=None, four=None, five=None):
369 def debugwireargs(self, one, two, three=None, four=None, five=None):
370 # don't pass optional arguments left at their default value
370 # don't pass optional arguments left at their default value
371 opts = {}
371 opts = {}
372 if three is not None:
372 if three is not None:
373 opts['three'] = three
373 opts['three'] = three
374 if four is not None:
374 if four is not None:
375 opts['four'] = four
375 opts['four'] = four
376 return self._call('debugwireargs', one=one, two=two, **opts)
376 return self._call('debugwireargs', one=one, two=two, **opts)
377
377
378 def _call(self, cmd, **args):
378 def _call(self, cmd, **args):
379 """execute <cmd> on the server
379 """execute <cmd> on the server
380
380
381 The command is expected to return a simple string.
381 The command is expected to return a simple string.
382
382
383 returns the server reply as a string."""
383 returns the server reply as a string."""
384 raise NotImplementedError()
384 raise NotImplementedError()
385
385
386 def _callstream(self, cmd, **args):
386 def _callstream(self, cmd, **args):
387 """execute <cmd> on the server
387 """execute <cmd> on the server
388
388
389 The command is expected to return a stream.
389 The command is expected to return a stream.
390
390
391 returns the server reply as a file like object."""
391 returns the server reply as a file like object."""
392 raise NotImplementedError()
392 raise NotImplementedError()
393
393
394 def _callcompressable(self, cmd, **args):
394 def _callcompressable(self, cmd, **args):
395 """execute <cmd> on the server
395 """execute <cmd> on the server
396
396
397 The command is expected to return a stream.
397 The command is expected to return a stream.
398
398
399 The stream may have been compressed in some implementations. This
399 The stream may have been compressed in some implementations. This
400 function takes care of the decompression. This is the only difference
400 function takes care of the decompression. This is the only difference
401 with _callstream.
401 with _callstream.
402
402
403 returns the server reply as a file like object.
403 returns the server reply as a file like object.
404 """
404 """
405 raise NotImplementedError()
405 raise NotImplementedError()
406
406
407 def _callpush(self, cmd, fp, **args):
407 def _callpush(self, cmd, fp, **args):
408 """execute a <cmd> on server
408 """execute a <cmd> on server
409
409
410 The command is expected to be related to a push. Push has a special
410 The command is expected to be related to a push. Push has a special
411 return method.
411 return method.
412
412
413 returns the server reply as a (ret, output) tuple. ret is either
413 returns the server reply as a (ret, output) tuple. ret is either
414 empty (error) or a stringified int.
414 empty (error) or a stringified int.
415 """
415 """
416 raise NotImplementedError()
416 raise NotImplementedError()
417
417
418 def _calltwowaystream(self, cmd, fp, **args):
418 def _calltwowaystream(self, cmd, fp, **args):
419 """execute <cmd> on server
419 """execute <cmd> on server
420
420
421 The command will send a stream to the server and get a stream in reply.
421 The command will send a stream to the server and get a stream in reply.
422 """
422 """
423 raise NotImplementedError()
423 raise NotImplementedError()
424
424
425 def _abort(self, exception):
425 def _abort(self, exception):
426 """clearly abort the wire protocol connection and raise the exception
426 """clearly abort the wire protocol connection and raise the exception
427 """
427 """
428 raise NotImplementedError()
428 raise NotImplementedError()
429
429
430 # server side
430 # server side
431
431
432 # wire protocol command can either return a string or one of these classes.
432 # wire protocol command can either return a string or one of these classes.
433 class streamres(object):
433 class streamres(object):
434 """wireproto reply: binary stream
434 """wireproto reply: binary stream
435
435
436 The call was successful and the result is a stream.
436 The call was successful and the result is a stream.
437 Iterate on the `self.gen` attribute to retrieve chunks.
437 Iterate on the `self.gen` attribute to retrieve chunks.
438 """
438 """
439 def __init__(self, gen):
439 def __init__(self, gen):
440 self.gen = gen
440 self.gen = gen
441
441
442 class pushres(object):
442 class pushres(object):
443 """wireproto reply: success with simple integer return
443 """wireproto reply: success with simple integer return
444
444
445 The call was successful and returned an integer contained in `self.res`.
445 The call was successful and returned an integer contained in `self.res`.
446 """
446 """
447 def __init__(self, res):
447 def __init__(self, res):
448 self.res = res
448 self.res = res
449
449
450 class pusherr(object):
450 class pusherr(object):
451 """wireproto reply: failure
451 """wireproto reply: failure
452
452
453 The call failed. The `self.res` attribute contains the error message.
453 The call failed. The `self.res` attribute contains the error message.
454 """
454 """
455 def __init__(self, res):
455 def __init__(self, res):
456 self.res = res
456 self.res = res
457
457
458 class ooberror(object):
458 class ooberror(object):
459 """wireproto reply: failure of a batch of operation
459 """wireproto reply: failure of a batch of operation
460
460
461 Something failed during a batch call. The error message is stored in
461 Something failed during a batch call. The error message is stored in
462 `self.message`.
462 `self.message`.
463 """
463 """
464 def __init__(self, message):
464 def __init__(self, message):
465 self.message = message
465 self.message = message
466
466
467 def dispatch(repo, proto, command):
467 def dispatch(repo, proto, command):
468 repo = repo.filtered("served")
468 repo = repo.filtered("served")
469 func, spec = commands[command]
469 func, spec = commands[command]
470 args = proto.getargs(spec)
470 args = proto.getargs(spec)
471 return func(repo, proto, *args)
471 return func(repo, proto, *args)
472
472
473 def options(cmd, keys, others):
473 def options(cmd, keys, others):
474 opts = {}
474 opts = {}
475 for k in keys:
475 for k in keys:
476 if k in others:
476 if k in others:
477 opts[k] = others[k]
477 opts[k] = others[k]
478 del others[k]
478 del others[k]
479 if others:
479 if others:
480 sys.stderr.write("warning: %s ignored unexpected arguments %s\n"
480 sys.stderr.write("warning: %s ignored unexpected arguments %s\n"
481 % (cmd, ",".join(others)))
481 % (cmd, ",".join(others)))
482 return opts
482 return opts
483
483
484 # list of commands
484 # list of commands
485 commands = {}
485 commands = {}
486
486
487 def wireprotocommand(name, args=''):
487 def wireprotocommand(name, args=''):
488 """decorator for wire protocol command"""
488 """decorator for wire protocol command"""
489 def register(func):
489 def register(func):
490 commands[name] = (func, args)
490 commands[name] = (func, args)
491 return func
491 return func
492 return register
492 return register
493
493
494 @wireprotocommand('batch', 'cmds *')
494 @wireprotocommand('batch', 'cmds *')
495 def batch(repo, proto, cmds, others):
495 def batch(repo, proto, cmds, others):
496 repo = repo.filtered("served")
496 repo = repo.filtered("served")
497 res = []
497 res = []
498 for pair in cmds.split(';'):
498 for pair in cmds.split(';'):
499 op, args = pair.split(' ', 1)
499 op, args = pair.split(' ', 1)
500 vals = {}
500 vals = {}
501 for a in args.split(','):
501 for a in args.split(','):
502 if a:
502 if a:
503 n, v = a.split('=')
503 n, v = a.split('=')
504 vals[n] = unescapearg(v)
504 vals[n] = unescapearg(v)
505 func, spec = commands[op]
505 func, spec = commands[op]
506 if spec:
506 if spec:
507 keys = spec.split()
507 keys = spec.split()
508 data = {}
508 data = {}
509 for k in keys:
509 for k in keys:
510 if k == '*':
510 if k == '*':
511 star = {}
511 star = {}
512 for key in vals.keys():
512 for key in vals.keys():
513 if key not in keys:
513 if key not in keys:
514 star[key] = vals[key]
514 star[key] = vals[key]
515 data['*'] = star
515 data['*'] = star
516 else:
516 else:
517 data[k] = vals[k]
517 data[k] = vals[k]
518 result = func(repo, proto, *[data[k] for k in keys])
518 result = func(repo, proto, *[data[k] for k in keys])
519 else:
519 else:
520 result = func(repo, proto)
520 result = func(repo, proto)
521 if isinstance(result, ooberror):
521 if isinstance(result, ooberror):
522 return result
522 return result
523 res.append(escapearg(result))
523 res.append(escapearg(result))
524 return ';'.join(res)
524 return ';'.join(res)
525
525
526 @wireprotocommand('between', 'pairs')
526 @wireprotocommand('between', 'pairs')
527 def between(repo, proto, pairs):
527 def between(repo, proto, pairs):
528 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
528 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
529 r = []
529 r = []
530 for b in repo.between(pairs):
530 for b in repo.between(pairs):
531 r.append(encodelist(b) + "\n")
531 r.append(encodelist(b) + "\n")
532 return "".join(r)
532 return "".join(r)
533
533
534 @wireprotocommand('branchmap')
534 @wireprotocommand('branchmap')
535 def branchmap(repo, proto):
535 def branchmap(repo, proto):
536 branchmap = repo.branchmap()
536 branchmap = repo.branchmap()
537 heads = []
537 heads = []
538 for branch, nodes in branchmap.iteritems():
538 for branch, nodes in branchmap.iteritems():
539 branchname = urllib.quote(encoding.fromlocal(branch))
539 branchname = urllib.quote(encoding.fromlocal(branch))
540 branchnodes = encodelist(nodes)
540 branchnodes = encodelist(nodes)
541 heads.append('%s %s' % (branchname, branchnodes))
541 heads.append('%s %s' % (branchname, branchnodes))
542 return '\n'.join(heads)
542 return '\n'.join(heads)
543
543
544 @wireprotocommand('branches', 'nodes')
544 @wireprotocommand('branches', 'nodes')
545 def branches(repo, proto, nodes):
545 def branches(repo, proto, nodes):
546 nodes = decodelist(nodes)
546 nodes = decodelist(nodes)
547 r = []
547 r = []
548 for b in repo.branches(nodes):
548 for b in repo.branches(nodes):
549 r.append(encodelist(b) + "\n")
549 r.append(encodelist(b) + "\n")
550 return "".join(r)
550 return "".join(r)
551
551
552 @wireprotocommand('clonebundles', '')
553 def clonebundles(repo, proto):
554 """Server command for returning info for available bundles to seed clones.
555
556 Clients will parse this response and determine what bundle to fetch.
557
558 Extensions may wrap this command to filter or dynamically emit data
559 depending on the request. e.g. you could advertise URLs for the closest
560 data center given the client's IP address.
561 """
562 return repo.opener.tryread('clonebundles.manifest')
552
563
553 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
564 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
554 'known', 'getbundle', 'unbundlehash', 'batch']
565 'known', 'getbundle', 'unbundlehash', 'batch']
555
566
556 def _capabilities(repo, proto):
567 def _capabilities(repo, proto):
557 """return a list of capabilities for a repo
568 """return a list of capabilities for a repo
558
569
559 This function exists to allow extensions to easily wrap capabilities
570 This function exists to allow extensions to easily wrap capabilities
560 computation
571 computation
561
572
562 - returns a lists: easy to alter
573 - returns a lists: easy to alter
563 - change done here will be propagated to both `capabilities` and `hello`
574 - change done here will be propagated to both `capabilities` and `hello`
564 command without any other action needed.
575 command without any other action needed.
565 """
576 """
566 # copy to prevent modification of the global list
577 # copy to prevent modification of the global list
567 caps = list(wireprotocaps)
578 caps = list(wireprotocaps)
568 if streamclone.allowservergeneration(repo.ui):
579 if streamclone.allowservergeneration(repo.ui):
569 if repo.ui.configbool('server', 'preferuncompressed', False):
580 if repo.ui.configbool('server', 'preferuncompressed', False):
570 caps.append('stream-preferred')
581 caps.append('stream-preferred')
571 requiredformats = repo.requirements & repo.supportedformats
582 requiredformats = repo.requirements & repo.supportedformats
572 # if our local revlogs are just revlogv1, add 'stream' cap
583 # if our local revlogs are just revlogv1, add 'stream' cap
573 if not requiredformats - set(('revlogv1',)):
584 if not requiredformats - set(('revlogv1',)):
574 caps.append('stream')
585 caps.append('stream')
575 # otherwise, add 'streamreqs' detailing our local revlog format
586 # otherwise, add 'streamreqs' detailing our local revlog format
576 else:
587 else:
577 caps.append('streamreqs=%s' % ','.join(requiredformats))
588 caps.append('streamreqs=%s' % ','.join(requiredformats))
578 if repo.ui.configbool('experimental', 'bundle2-advertise', True):
589 if repo.ui.configbool('experimental', 'bundle2-advertise', True):
579 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
590 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
580 caps.append('bundle2=' + urllib.quote(capsblob))
591 caps.append('bundle2=' + urllib.quote(capsblob))
581 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
592 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
582 caps.append(
593 caps.append(
583 'httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen', 1024))
594 'httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen', 1024))
584 return caps
595 return caps
585
596
586 # If you are writing an extension and consider wrapping this function. Wrap
597 # If you are writing an extension and consider wrapping this function. Wrap
587 # `_capabilities` instead.
598 # `_capabilities` instead.
588 @wireprotocommand('capabilities')
599 @wireprotocommand('capabilities')
589 def capabilities(repo, proto):
600 def capabilities(repo, proto):
590 return ' '.join(_capabilities(repo, proto))
601 return ' '.join(_capabilities(repo, proto))
591
602
592 @wireprotocommand('changegroup', 'roots')
603 @wireprotocommand('changegroup', 'roots')
593 def changegroup(repo, proto, roots):
604 def changegroup(repo, proto, roots):
594 nodes = decodelist(roots)
605 nodes = decodelist(roots)
595 cg = changegroupmod.changegroup(repo, nodes, 'serve')
606 cg = changegroupmod.changegroup(repo, nodes, 'serve')
596 return streamres(proto.groupchunks(cg))
607 return streamres(proto.groupchunks(cg))
597
608
598 @wireprotocommand('changegroupsubset', 'bases heads')
609 @wireprotocommand('changegroupsubset', 'bases heads')
599 def changegroupsubset(repo, proto, bases, heads):
610 def changegroupsubset(repo, proto, bases, heads):
600 bases = decodelist(bases)
611 bases = decodelist(bases)
601 heads = decodelist(heads)
612 heads = decodelist(heads)
602 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
613 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
603 return streamres(proto.groupchunks(cg))
614 return streamres(proto.groupchunks(cg))
604
615
605 @wireprotocommand('debugwireargs', 'one two *')
616 @wireprotocommand('debugwireargs', 'one two *')
606 def debugwireargs(repo, proto, one, two, others):
617 def debugwireargs(repo, proto, one, two, others):
607 # only accept optional args from the known set
618 # only accept optional args from the known set
608 opts = options('debugwireargs', ['three', 'four'], others)
619 opts = options('debugwireargs', ['three', 'four'], others)
609 return repo.debugwireargs(one, two, **opts)
620 return repo.debugwireargs(one, two, **opts)
610
621
611 # List of options accepted by getbundle.
622 # List of options accepted by getbundle.
612 #
623 #
613 # Meant to be extended by extensions. It is the extension's responsibility to
624 # Meant to be extended by extensions. It is the extension's responsibility to
614 # ensure such options are properly processed in exchange.getbundle.
625 # ensure such options are properly processed in exchange.getbundle.
615 gboptslist = ['heads', 'common', 'bundlecaps']
626 gboptslist = ['heads', 'common', 'bundlecaps']
616
627
617 @wireprotocommand('getbundle', '*')
628 @wireprotocommand('getbundle', '*')
618 def getbundle(repo, proto, others):
629 def getbundle(repo, proto, others):
619 opts = options('getbundle', gboptsmap.keys(), others)
630 opts = options('getbundle', gboptsmap.keys(), others)
620 for k, v in opts.iteritems():
631 for k, v in opts.iteritems():
621 keytype = gboptsmap[k]
632 keytype = gboptsmap[k]
622 if keytype == 'nodes':
633 if keytype == 'nodes':
623 opts[k] = decodelist(v)
634 opts[k] = decodelist(v)
624 elif keytype == 'csv':
635 elif keytype == 'csv':
625 opts[k] = list(v.split(','))
636 opts[k] = list(v.split(','))
626 elif keytype == 'scsv':
637 elif keytype == 'scsv':
627 opts[k] = set(v.split(','))
638 opts[k] = set(v.split(','))
628 elif keytype == 'boolean':
639 elif keytype == 'boolean':
629 # Client should serialize False as '0', which is a non-empty string
640 # Client should serialize False as '0', which is a non-empty string
630 # so it evaluates as a True bool.
641 # so it evaluates as a True bool.
631 if v == '0':
642 if v == '0':
632 opts[k] = False
643 opts[k] = False
633 else:
644 else:
634 opts[k] = bool(v)
645 opts[k] = bool(v)
635 elif keytype != 'plain':
646 elif keytype != 'plain':
636 raise KeyError('unknown getbundle option type %s'
647 raise KeyError('unknown getbundle option type %s'
637 % keytype)
648 % keytype)
638 cg = exchange.getbundle(repo, 'serve', **opts)
649 cg = exchange.getbundle(repo, 'serve', **opts)
639 return streamres(proto.groupchunks(cg))
650 return streamres(proto.groupchunks(cg))
640
651
641 @wireprotocommand('heads')
652 @wireprotocommand('heads')
642 def heads(repo, proto):
653 def heads(repo, proto):
643 h = repo.heads()
654 h = repo.heads()
644 return encodelist(h) + "\n"
655 return encodelist(h) + "\n"
645
656
646 @wireprotocommand('hello')
657 @wireprotocommand('hello')
647 def hello(repo, proto):
658 def hello(repo, proto):
648 '''the hello command returns a set of lines describing various
659 '''the hello command returns a set of lines describing various
649 interesting things about the server, in an RFC822-like format.
660 interesting things about the server, in an RFC822-like format.
650 Currently the only one defined is "capabilities", which
661 Currently the only one defined is "capabilities", which
651 consists of a line in the form:
662 consists of a line in the form:
652
663
653 capabilities: space separated list of tokens
664 capabilities: space separated list of tokens
654 '''
665 '''
655 return "capabilities: %s\n" % (capabilities(repo, proto))
666 return "capabilities: %s\n" % (capabilities(repo, proto))
656
667
657 @wireprotocommand('listkeys', 'namespace')
668 @wireprotocommand('listkeys', 'namespace')
658 def listkeys(repo, proto, namespace):
669 def listkeys(repo, proto, namespace):
659 d = repo.listkeys(encoding.tolocal(namespace)).items()
670 d = repo.listkeys(encoding.tolocal(namespace)).items()
660 return pushkeymod.encodekeys(d)
671 return pushkeymod.encodekeys(d)
661
672
662 @wireprotocommand('lookup', 'key')
673 @wireprotocommand('lookup', 'key')
663 def lookup(repo, proto, key):
674 def lookup(repo, proto, key):
664 try:
675 try:
665 k = encoding.tolocal(key)
676 k = encoding.tolocal(key)
666 c = repo[k]
677 c = repo[k]
667 r = c.hex()
678 r = c.hex()
668 success = 1
679 success = 1
669 except Exception as inst:
680 except Exception as inst:
670 r = str(inst)
681 r = str(inst)
671 success = 0
682 success = 0
672 return "%s %s\n" % (success, r)
683 return "%s %s\n" % (success, r)
673
684
674 @wireprotocommand('known', 'nodes *')
685 @wireprotocommand('known', 'nodes *')
675 def known(repo, proto, nodes, others):
686 def known(repo, proto, nodes, others):
676 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
687 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
677
688
678 @wireprotocommand('pushkey', 'namespace key old new')
689 @wireprotocommand('pushkey', 'namespace key old new')
679 def pushkey(repo, proto, namespace, key, old, new):
690 def pushkey(repo, proto, namespace, key, old, new):
680 # compatibility with pre-1.8 clients which were accidentally
691 # compatibility with pre-1.8 clients which were accidentally
681 # sending raw binary nodes rather than utf-8-encoded hex
692 # sending raw binary nodes rather than utf-8-encoded hex
682 if len(new) == 20 and new.encode('string-escape') != new:
693 if len(new) == 20 and new.encode('string-escape') != new:
683 # looks like it could be a binary node
694 # looks like it could be a binary node
684 try:
695 try:
685 new.decode('utf-8')
696 new.decode('utf-8')
686 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
697 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
687 except UnicodeDecodeError:
698 except UnicodeDecodeError:
688 pass # binary, leave unmodified
699 pass # binary, leave unmodified
689 else:
700 else:
690 new = encoding.tolocal(new) # normal path
701 new = encoding.tolocal(new) # normal path
691
702
692 if util.safehasattr(proto, 'restore'):
703 if util.safehasattr(proto, 'restore'):
693
704
694 proto.redirect()
705 proto.redirect()
695
706
696 try:
707 try:
697 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
708 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
698 encoding.tolocal(old), new) or False
709 encoding.tolocal(old), new) or False
699 except error.Abort:
710 except error.Abort:
700 r = False
711 r = False
701
712
702 output = proto.restore()
713 output = proto.restore()
703
714
704 return '%s\n%s' % (int(r), output)
715 return '%s\n%s' % (int(r), output)
705
716
706 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
717 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
707 encoding.tolocal(old), new)
718 encoding.tolocal(old), new)
708 return '%s\n' % int(r)
719 return '%s\n' % int(r)
709
720
710 @wireprotocommand('stream_out')
721 @wireprotocommand('stream_out')
711 def stream(repo, proto):
722 def stream(repo, proto):
712 '''If the server supports streaming clone, it advertises the "stream"
723 '''If the server supports streaming clone, it advertises the "stream"
713 capability with a value representing the version and flags of the repo
724 capability with a value representing the version and flags of the repo
714 it is serving. Client checks to see if it understands the format.
725 it is serving. Client checks to see if it understands the format.
715 '''
726 '''
716 if not streamclone.allowservergeneration(repo.ui):
727 if not streamclone.allowservergeneration(repo.ui):
717 return '1\n'
728 return '1\n'
718
729
719 def getstream(it):
730 def getstream(it):
720 yield '0\n'
731 yield '0\n'
721 for chunk in it:
732 for chunk in it:
722 yield chunk
733 yield chunk
723
734
724 try:
735 try:
725 # LockError may be raised before the first result is yielded. Don't
736 # LockError may be raised before the first result is yielded. Don't
726 # emit output until we're sure we got the lock successfully.
737 # emit output until we're sure we got the lock successfully.
727 it = streamclone.generatev1wireproto(repo)
738 it = streamclone.generatev1wireproto(repo)
728 return streamres(getstream(it))
739 return streamres(getstream(it))
729 except error.LockError:
740 except error.LockError:
730 return '2\n'
741 return '2\n'
731
742
732 @wireprotocommand('unbundle', 'heads')
743 @wireprotocommand('unbundle', 'heads')
733 def unbundle(repo, proto, heads):
744 def unbundle(repo, proto, heads):
734 their_heads = decodelist(heads)
745 their_heads = decodelist(heads)
735
746
736 try:
747 try:
737 proto.redirect()
748 proto.redirect()
738
749
739 exchange.check_heads(repo, their_heads, 'preparing changes')
750 exchange.check_heads(repo, their_heads, 'preparing changes')
740
751
741 # write bundle data to temporary file because it can be big
752 # write bundle data to temporary file because it can be big
742 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
753 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
743 fp = os.fdopen(fd, 'wb+')
754 fp = os.fdopen(fd, 'wb+')
744 r = 0
755 r = 0
745 try:
756 try:
746 proto.getfile(fp)
757 proto.getfile(fp)
747 fp.seek(0)
758 fp.seek(0)
748 gen = exchange.readbundle(repo.ui, fp, None)
759 gen = exchange.readbundle(repo.ui, fp, None)
749 r = exchange.unbundle(repo, gen, their_heads, 'serve',
760 r = exchange.unbundle(repo, gen, their_heads, 'serve',
750 proto._client())
761 proto._client())
751 if util.safehasattr(r, 'addpart'):
762 if util.safehasattr(r, 'addpart'):
752 # The return looks streamable, we are in the bundle2 case and
763 # The return looks streamable, we are in the bundle2 case and
753 # should return a stream.
764 # should return a stream.
754 return streamres(r.getchunks())
765 return streamres(r.getchunks())
755 return pushres(r)
766 return pushres(r)
756
767
757 finally:
768 finally:
758 fp.close()
769 fp.close()
759 os.unlink(tempname)
770 os.unlink(tempname)
760
771
761 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
772 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
762 # handle non-bundle2 case first
773 # handle non-bundle2 case first
763 if not getattr(exc, 'duringunbundle2', False):
774 if not getattr(exc, 'duringunbundle2', False):
764 try:
775 try:
765 raise
776 raise
766 except error.Abort:
777 except error.Abort:
767 # The old code we moved used sys.stderr directly.
778 # The old code we moved used sys.stderr directly.
768 # We did not change it to minimise code change.
779 # We did not change it to minimise code change.
769 # This need to be moved to something proper.
780 # This need to be moved to something proper.
770 # Feel free to do it.
781 # Feel free to do it.
771 sys.stderr.write("abort: %s\n" % exc)
782 sys.stderr.write("abort: %s\n" % exc)
772 return pushres(0)
783 return pushres(0)
773 except error.PushRaced:
784 except error.PushRaced:
774 return pusherr(str(exc))
785 return pusherr(str(exc))
775
786
776 bundler = bundle2.bundle20(repo.ui)
787 bundler = bundle2.bundle20(repo.ui)
777 for out in getattr(exc, '_bundle2salvagedoutput', ()):
788 for out in getattr(exc, '_bundle2salvagedoutput', ()):
778 bundler.addpart(out)
789 bundler.addpart(out)
779 try:
790 try:
780 try:
791 try:
781 raise
792 raise
782 except error.PushkeyFailed as exc:
793 except error.PushkeyFailed as exc:
783 # check client caps
794 # check client caps
784 remotecaps = getattr(exc, '_replycaps', None)
795 remotecaps = getattr(exc, '_replycaps', None)
785 if (remotecaps is not None
796 if (remotecaps is not None
786 and 'pushkey' not in remotecaps.get('error', ())):
797 and 'pushkey' not in remotecaps.get('error', ())):
787 # no support remote side, fallback to Abort handler.
798 # no support remote side, fallback to Abort handler.
788 raise
799 raise
789 part = bundler.newpart('error:pushkey')
800 part = bundler.newpart('error:pushkey')
790 part.addparam('in-reply-to', exc.partid)
801 part.addparam('in-reply-to', exc.partid)
791 if exc.namespace is not None:
802 if exc.namespace is not None:
792 part.addparam('namespace', exc.namespace, mandatory=False)
803 part.addparam('namespace', exc.namespace, mandatory=False)
793 if exc.key is not None:
804 if exc.key is not None:
794 part.addparam('key', exc.key, mandatory=False)
805 part.addparam('key', exc.key, mandatory=False)
795 if exc.new is not None:
806 if exc.new is not None:
796 part.addparam('new', exc.new, mandatory=False)
807 part.addparam('new', exc.new, mandatory=False)
797 if exc.old is not None:
808 if exc.old is not None:
798 part.addparam('old', exc.old, mandatory=False)
809 part.addparam('old', exc.old, mandatory=False)
799 if exc.ret is not None:
810 if exc.ret is not None:
800 part.addparam('ret', exc.ret, mandatory=False)
811 part.addparam('ret', exc.ret, mandatory=False)
801 except error.BundleValueError as exc:
812 except error.BundleValueError as exc:
802 errpart = bundler.newpart('error:unsupportedcontent')
813 errpart = bundler.newpart('error:unsupportedcontent')
803 if exc.parttype is not None:
814 if exc.parttype is not None:
804 errpart.addparam('parttype', exc.parttype)
815 errpart.addparam('parttype', exc.parttype)
805 if exc.params:
816 if exc.params:
806 errpart.addparam('params', '\0'.join(exc.params))
817 errpart.addparam('params', '\0'.join(exc.params))
807 except error.Abort as exc:
818 except error.Abort as exc:
808 manargs = [('message', str(exc))]
819 manargs = [('message', str(exc))]
809 advargs = []
820 advargs = []
810 if exc.hint is not None:
821 if exc.hint is not None:
811 advargs.append(('hint', exc.hint))
822 advargs.append(('hint', exc.hint))
812 bundler.addpart(bundle2.bundlepart('error:abort',
823 bundler.addpart(bundle2.bundlepart('error:abort',
813 manargs, advargs))
824 manargs, advargs))
814 except error.PushRaced as exc:
825 except error.PushRaced as exc:
815 bundler.newpart('error:pushraced', [('message', str(exc))])
826 bundler.newpart('error:pushraced', [('message', str(exc))])
816 return streamres(bundler.getchunks())
827 return streamres(bundler.getchunks())
@@ -1,450 +1,462 b''
1 Set up a server
1 Set up a server
2
2
3 $ hg init server
3 $ hg init server
4 $ cd server
4 $ cd server
5 $ cat >> .hg/hgrc << EOF
5 $ cat >> .hg/hgrc << EOF
6 > [extensions]
6 > [extensions]
7 > clonebundles =
7 > clonebundles =
8 > EOF
8 > EOF
9
9
10 $ touch foo
10 $ touch foo
11 $ hg -q commit -A -m 'add foo'
11 $ hg -q commit -A -m 'add foo'
12 $ touch bar
12 $ touch bar
13 $ hg -q commit -A -m 'add bar'
13 $ hg -q commit -A -m 'add bar'
14
14
15 $ hg serve -d -p $HGPORT --pid-file hg.pid --accesslog access.log
15 $ hg serve -d -p $HGPORT --pid-file hg.pid --accesslog access.log
16 $ cat hg.pid >> $DAEMON_PIDS
16 $ cat hg.pid >> $DAEMON_PIDS
17 $ cd ..
17 $ cd ..
18
18
19 Feature disabled by default
19 Feature disabled by default
20 (client should not request manifest)
20 (client should not request manifest)
21
21
22 $ hg clone -U http://localhost:$HGPORT feature-disabled
22 $ hg clone -U http://localhost:$HGPORT feature-disabled
23 requesting all changes
23 requesting all changes
24 adding changesets
24 adding changesets
25 adding manifests
25 adding manifests
26 adding file changes
26 adding file changes
27 added 2 changesets with 2 changes to 2 files
27 added 2 changesets with 2 changes to 2 files
28
28
29 $ cat server/access.log
29 $ cat server/access.log
30 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
30 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
31 * - - [*] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D (glob)
31 * - - [*] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D (glob)
32 * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=phase%2Cbookmarks (glob)
32 * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=phase%2Cbookmarks (glob)
33 * - - [*] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases (glob)
33 * - - [*] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases (glob)
34
34
35 $ cat >> $HGRCPATH << EOF
35 $ cat >> $HGRCPATH << EOF
36 > [experimental]
36 > [experimental]
37 > clonebundles = true
37 > clonebundles = true
38 > EOF
38 > EOF
39
39
40 Missing manifest should not result in server lookup
40 Missing manifest should not result in server lookup
41
41
42 $ hg --verbose clone -U http://localhost:$HGPORT no-manifest
42 $ hg --verbose clone -U http://localhost:$HGPORT no-manifest
43 requesting all changes
43 requesting all changes
44 adding changesets
44 adding changesets
45 adding manifests
45 adding manifests
46 adding file changes
46 adding file changes
47 added 2 changesets with 2 changes to 2 files
47 added 2 changesets with 2 changes to 2 files
48
48
49 $ tail -4 server/access.log
49 $ tail -4 server/access.log
50 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
50 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
51 * - - [*] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D (glob)
51 * - - [*] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D (glob)
52 * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=phase%2Cbookmarks (glob)
52 * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=phase%2Cbookmarks (glob)
53 * - - [*] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases (glob)
53 * - - [*] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases (glob)
54
54
55 Empty manifest file results in retrieval
55 Empty manifest file results in retrieval
56 (the extension only checks if the manifest file exists)
56 (the extension only checks if the manifest file exists)
57
57
58 $ touch server/.hg/clonebundles.manifest
58 $ touch server/.hg/clonebundles.manifest
59 $ hg --verbose clone -U http://localhost:$HGPORT empty-manifest
59 $ hg --verbose clone -U http://localhost:$HGPORT empty-manifest
60 no clone bundles available on remote; falling back to regular clone
60 no clone bundles available on remote; falling back to regular clone
61 requesting all changes
61 requesting all changes
62 adding changesets
62 adding changesets
63 adding manifests
63 adding manifests
64 adding file changes
64 adding file changes
65 added 2 changesets with 2 changes to 2 files
65 added 2 changesets with 2 changes to 2 files
66
66
67 Server advertises presence of feature to client requesting full clone
67 Server advertises presence of feature to client requesting full clone
68
68
69 $ hg --config experimental.clonebundles=false clone -U http://localhost:$HGPORT advertise-on-clone
69 $ hg --config experimental.clonebundles=false clone -U http://localhost:$HGPORT advertise-on-clone
70 requesting all changes
70 requesting all changes
71 remote: this server supports the experimental "clone bundles" feature that should enable faster and more reliable cloning
71 remote: this server supports the experimental "clone bundles" feature that should enable faster and more reliable cloning
72 remote: help test it by setting the "experimental.clonebundles" config flag to "true"
72 remote: help test it by setting the "experimental.clonebundles" config flag to "true"
73 adding changesets
73 adding changesets
74 adding manifests
74 adding manifests
75 adding file changes
75 adding file changes
76 added 2 changesets with 2 changes to 2 files
76 added 2 changesets with 2 changes to 2 files
77
77
78 Manifest file with invalid URL aborts
78 Manifest file with invalid URL aborts
79
79
80 $ echo 'http://does.not.exist/bundle.hg' > server/.hg/clonebundles.manifest
80 $ echo 'http://does.not.exist/bundle.hg' > server/.hg/clonebundles.manifest
81 $ hg clone http://localhost:$HGPORT 404-url
81 $ hg clone http://localhost:$HGPORT 404-url
82 applying clone bundle from http://does.not.exist/bundle.hg
82 applying clone bundle from http://does.not.exist/bundle.hg
83 error fetching bundle: * not known (glob)
83 error fetching bundle: * not known (glob)
84 abort: error applying bundle
84 abort: error applying bundle
85 (if this error persists, consider contacting the server operator or disable clone bundles via "--config experimental.clonebundles=false")
85 (if this error persists, consider contacting the server operator or disable clone bundles via "--config experimental.clonebundles=false")
86 [255]
86 [255]
87
87
88 Server is not running aborts
88 Server is not running aborts
89
89
90 $ echo "http://localhost:$HGPORT1/bundle.hg" > server/.hg/clonebundles.manifest
90 $ echo "http://localhost:$HGPORT1/bundle.hg" > server/.hg/clonebundles.manifest
91 $ hg clone http://localhost:$HGPORT server-not-runner
91 $ hg clone http://localhost:$HGPORT server-not-runner
92 applying clone bundle from http://localhost:$HGPORT1/bundle.hg
92 applying clone bundle from http://localhost:$HGPORT1/bundle.hg
93 error fetching bundle: Connection refused
93 error fetching bundle: Connection refused
94 abort: error applying bundle
94 abort: error applying bundle
95 (if this error persists, consider contacting the server operator or disable clone bundles via "--config experimental.clonebundles=false")
95 (if this error persists, consider contacting the server operator or disable clone bundles via "--config experimental.clonebundles=false")
96 [255]
96 [255]
97
97
98 Server returns 404
98 Server returns 404
99
99
100 $ python $TESTDIR/dumbhttp.py -p $HGPORT1 --pid http.pid
100 $ python $TESTDIR/dumbhttp.py -p $HGPORT1 --pid http.pid
101 $ cat http.pid >> $DAEMON_PIDS
101 $ cat http.pid >> $DAEMON_PIDS
102 $ hg clone http://localhost:$HGPORT running-404
102 $ hg clone http://localhost:$HGPORT running-404
103 applying clone bundle from http://localhost:$HGPORT1/bundle.hg
103 applying clone bundle from http://localhost:$HGPORT1/bundle.hg
104 HTTP error fetching bundle: HTTP Error 404: File not found
104 HTTP error fetching bundle: HTTP Error 404: File not found
105 abort: error applying bundle
105 abort: error applying bundle
106 (if this error persists, consider contacting the server operator or disable clone bundles via "--config experimental.clonebundles=false")
106 (if this error persists, consider contacting the server operator or disable clone bundles via "--config experimental.clonebundles=false")
107 [255]
107 [255]
108
108
109 We can override failure to fall back to regular clone
109 We can override failure to fall back to regular clone
110
110
111 $ hg --config ui.clonebundlefallback=true clone -U http://localhost:$HGPORT 404-fallback
111 $ hg --config ui.clonebundlefallback=true clone -U http://localhost:$HGPORT 404-fallback
112 applying clone bundle from http://localhost:$HGPORT1/bundle.hg
112 applying clone bundle from http://localhost:$HGPORT1/bundle.hg
113 HTTP error fetching bundle: HTTP Error 404: File not found
113 HTTP error fetching bundle: HTTP Error 404: File not found
114 falling back to normal clone
114 falling back to normal clone
115 requesting all changes
115 requesting all changes
116 adding changesets
116 adding changesets
117 adding manifests
117 adding manifests
118 adding file changes
118 adding file changes
119 added 2 changesets with 2 changes to 2 files
119 added 2 changesets with 2 changes to 2 files
120
120
121 Bundle with partial content works
121 Bundle with partial content works
122
122
123 $ hg -R server bundle --type gzip-v1 --base null -r 53245c60e682 partial.hg
123 $ hg -R server bundle --type gzip-v1 --base null -r 53245c60e682 partial.hg
124 1 changesets found
124 1 changesets found
125
125
126 We verify exact bundle content as an extra check against accidental future
126 We verify exact bundle content as an extra check against accidental future
127 changes. If this output changes, we could break old clients.
127 changes. If this output changes, we could break old clients.
128
128
129 $ f --size --hexdump partial.hg
129 $ f --size --hexdump partial.hg
130 partial.hg: size=208
130 partial.hg: size=208
131 0000: 48 47 31 30 47 5a 78 9c 63 60 60 98 17 ac 12 93 |HG10GZx.c``.....|
131 0000: 48 47 31 30 47 5a 78 9c 63 60 60 98 17 ac 12 93 |HG10GZx.c``.....|
132 0010: f0 ac a9 23 45 70 cb bf 0d 5f 59 4e 4a 7f 79 21 |...#Ep..._YNJ.y!|
132 0010: f0 ac a9 23 45 70 cb bf 0d 5f 59 4e 4a 7f 79 21 |...#Ep..._YNJ.y!|
133 0020: 9b cc 40 24 20 a0 d7 ce 2c d1 38 25 cd 24 25 d5 |..@$ ...,.8%.$%.|
133 0020: 9b cc 40 24 20 a0 d7 ce 2c d1 38 25 cd 24 25 d5 |..@$ ...,.8%.$%.|
134 0030: d8 c2 22 cd 38 d9 24 cd 22 d5 c8 22 cd 24 cd 32 |..".8.$."..".$.2|
134 0030: d8 c2 22 cd 38 d9 24 cd 22 d5 c8 22 cd 24 cd 32 |..".8.$."..".$.2|
135 0040: d1 c2 d0 c4 c8 d2 32 d1 38 39 29 c9 34 cd d4 80 |......2.89).4...|
135 0040: d1 c2 d0 c4 c8 d2 32 d1 38 39 29 c9 34 cd d4 80 |......2.89).4...|
136 0050: ab 24 b5 b8 84 cb 40 c1 80 2b 2d 3f 9f 8b 2b 31 |.$....@..+-?..+1|
136 0050: ab 24 b5 b8 84 cb 40 c1 80 2b 2d 3f 9f 8b 2b 31 |.$....@..+-?..+1|
137 0060: 25 45 01 c8 80 9a d2 9b 65 fb e5 9e 45 bf 8d 7f |%E......e...E...|
137 0060: 25 45 01 c8 80 9a d2 9b 65 fb e5 9e 45 bf 8d 7f |%E......e...E...|
138 0070: 9f c6 97 9f 2b 44 34 67 d9 ec 8e 0f a0 92 0b 75 |....+D4g.......u|
138 0070: 9f c6 97 9f 2b 44 34 67 d9 ec 8e 0f a0 92 0b 75 |....+D4g.......u|
139 0080: 41 d6 24 59 18 a4 a4 9a a6 18 1a 5b 98 9b 5a 98 |A.$Y.......[..Z.|
139 0080: 41 d6 24 59 18 a4 a4 9a a6 18 1a 5b 98 9b 5a 98 |A.$Y.......[..Z.|
140 0090: 9a 18 26 9b a6 19 98 1a 99 99 26 a6 18 9a 98 24 |..&.......&....$|
140 0090: 9a 18 26 9b a6 19 98 1a 99 99 26 a6 18 9a 98 24 |..&.......&....$|
141 00a0: 26 59 a6 25 5a 98 a5 18 a6 24 71 41 35 b1 43 dc |&Y.%Z....$qA5.C.|
141 00a0: 26 59 a6 25 5a 98 a5 18 a6 24 71 41 35 b1 43 dc |&Y.%Z....$qA5.C.|
142 00b0: 96 b0 83 f7 e9 45 8b d2 56 c7 a3 1f 82 52 d7 8a |.....E..V....R..|
142 00b0: 96 b0 83 f7 e9 45 8b d2 56 c7 a3 1f 82 52 d7 8a |.....E..V....R..|
143 00c0: 78 ed fc d5 76 f1 36 95 dc 05 07 00 ad 39 5e d3 |x...v.6......9^.|
143 00c0: 78 ed fc d5 76 f1 36 95 dc 05 07 00 ad 39 5e d3 |x...v.6......9^.|
144
144
145 $ echo "http://localhost:$HGPORT1/partial.hg" > server/.hg/clonebundles.manifest
145 $ echo "http://localhost:$HGPORT1/partial.hg" > server/.hg/clonebundles.manifest
146 $ hg clone -U http://localhost:$HGPORT partial-bundle
146 $ hg clone -U http://localhost:$HGPORT partial-bundle
147 applying clone bundle from http://localhost:$HGPORT1/partial.hg
147 applying clone bundle from http://localhost:$HGPORT1/partial.hg
148 adding changesets
148 adding changesets
149 adding manifests
149 adding manifests
150 adding file changes
150 adding file changes
151 added 1 changesets with 1 changes to 1 files
151 added 1 changesets with 1 changes to 1 files
152 finished applying clone bundle
152 finished applying clone bundle
153 searching for changes
153 searching for changes
154 adding changesets
154 adding changesets
155 adding manifests
155 adding manifests
156 adding file changes
156 adding file changes
157 added 1 changesets with 1 changes to 1 files
157 added 1 changesets with 1 changes to 1 files
158
158
159 Incremental pull doesn't fetch bundle
159 Incremental pull doesn't fetch bundle
160
160
161 $ hg clone -r 53245c60e682 -U http://localhost:$HGPORT partial-clone
161 $ hg clone -r 53245c60e682 -U http://localhost:$HGPORT partial-clone
162 adding changesets
162 adding changesets
163 adding manifests
163 adding manifests
164 adding file changes
164 adding file changes
165 added 1 changesets with 1 changes to 1 files
165 added 1 changesets with 1 changes to 1 files
166
166
167 $ cd partial-clone
167 $ cd partial-clone
168 $ hg pull
168 $ hg pull
169 pulling from http://localhost:$HGPORT/
169 pulling from http://localhost:$HGPORT/
170 searching for changes
170 searching for changes
171 adding changesets
171 adding changesets
172 adding manifests
172 adding manifests
173 adding file changes
173 adding file changes
174 added 1 changesets with 1 changes to 1 files
174 added 1 changesets with 1 changes to 1 files
175 (run 'hg update' to get a working copy)
175 (run 'hg update' to get a working copy)
176 $ cd ..
176 $ cd ..
177
177
178 Bundle with full content works
178 Bundle with full content works
179
179
180 $ hg -R server bundle --type gzip-v2 --base null -r tip full.hg
180 $ hg -R server bundle --type gzip-v2 --base null -r tip full.hg
181 2 changesets found
181 2 changesets found
182
182
183 Again, we perform an extra check against bundle content changes. If this content
183 Again, we perform an extra check against bundle content changes. If this content
184 changes, clone bundles produced by new Mercurial versions may not be readable
184 changes, clone bundles produced by new Mercurial versions may not be readable
185 by old clients.
185 by old clients.
186
186
187 $ f --size --hexdump full.hg
187 $ f --size --hexdump full.hg
188 full.hg: size=408
188 full.hg: size=408
189 0000: 48 47 32 30 00 00 00 0e 43 6f 6d 70 72 65 73 73 |HG20....Compress|
189 0000: 48 47 32 30 00 00 00 0e 43 6f 6d 70 72 65 73 73 |HG20....Compress|
190 0010: 69 6f 6e 3d 47 5a 78 9c 63 60 60 90 e5 76 f6 70 |ion=GZx.c``..v.p|
190 0010: 69 6f 6e 3d 47 5a 78 9c 63 60 60 90 e5 76 f6 70 |ion=GZx.c``..v.p|
191 0020: f4 73 77 75 0f f2 0f 0d 60 00 02 46 06 76 a6 b2 |.swu....`..F.v..|
191 0020: f4 73 77 75 0f f2 0f 0d 60 00 02 46 06 76 a6 b2 |.swu....`..F.v..|
192 0030: d4 a2 e2 cc fc 3c 03 23 06 06 e6 7d 40 b1 4d c1 |.....<.#...}@.M.|
192 0030: d4 a2 e2 cc fc 3c 03 23 06 06 e6 7d 40 b1 4d c1 |.....<.#...}@.M.|
193 0040: 2a 31 09 cf 9a 3a 52 04 b7 fc db f0 95 e5 a4 f4 |*1...:R.........|
193 0040: 2a 31 09 cf 9a 3a 52 04 b7 fc db f0 95 e5 a4 f4 |*1...:R.........|
194 0050: 97 17 b2 c9 0c 14 00 02 e6 d9 99 25 1a a7 a4 99 |...........%....|
194 0050: 97 17 b2 c9 0c 14 00 02 e6 d9 99 25 1a a7 a4 99 |...........%....|
195 0060: a4 a4 1a 5b 58 a4 19 27 9b a4 59 a4 1a 59 a4 99 |...[X..'..Y..Y..|
195 0060: a4 a4 1a 5b 58 a4 19 27 9b a4 59 a4 1a 59 a4 99 |...[X..'..Y..Y..|
196 0070: a4 59 26 5a 18 9a 18 59 5a 26 1a 27 27 25 99 a6 |.Y&Z...YZ&.''%..|
196 0070: a4 59 26 5a 18 9a 18 59 5a 26 1a 27 27 25 99 a6 |.Y&Z...YZ&.''%..|
197 0080: 99 1a 70 95 a4 16 97 70 19 28 18 70 a5 e5 e7 73 |..p....p.(.p...s|
197 0080: 99 1a 70 95 a4 16 97 70 19 28 18 70 a5 e5 e7 73 |..p....p.(.p...s|
198 0090: 71 25 a6 a4 28 00 19 40 13 0e ac fa df ab ff 7b |q%..(..@.......{|
198 0090: 71 25 a6 a4 28 00 19 40 13 0e ac fa df ab ff 7b |q%..(..@.......{|
199 00a0: 3f fb 92 dc 8b 1f 62 bb 9e b7 d7 d9 87 3d 5a 44 |?.....b......=ZD|
199 00a0: 3f fb 92 dc 8b 1f 62 bb 9e b7 d7 d9 87 3d 5a 44 |?.....b......=ZD|
200 00b0: ac 2f b0 a9 c3 66 1e 54 b9 26 08 a7 1a 1b 1a a7 |./...f.T.&......|
200 00b0: ac 2f b0 a9 c3 66 1e 54 b9 26 08 a7 1a 1b 1a a7 |./...f.T.&......|
201 00c0: 25 1b 9a 1b 99 19 9a 5a 18 9b a6 18 19 00 dd 67 |%......Z.......g|
201 00c0: 25 1b 9a 1b 99 19 9a 5a 18 9b a6 18 19 00 dd 67 |%......Z.......g|
202 00d0: 61 61 98 06 f4 80 49 4a 8a 65 52 92 41 9a 81 81 |aa....IJ.eR.A...|
202 00d0: 61 61 98 06 f4 80 49 4a 8a 65 52 92 41 9a 81 81 |aa....IJ.eR.A...|
203 00e0: a5 11 17 50 31 30 58 19 cc 80 98 25 29 b1 08 c4 |...P10X....%)...|
203 00e0: a5 11 17 50 31 30 58 19 cc 80 98 25 29 b1 08 c4 |...P10X....%)...|
204 00f0: 37 07 79 19 88 d9 41 ee 07 8a 41 cd 5d 98 65 fb |7.y...A...A.].e.|
204 00f0: 37 07 79 19 88 d9 41 ee 07 8a 41 cd 5d 98 65 fb |7.y...A...A.].e.|
205 0100: e5 9e 45 bf 8d 7f 9f c6 97 9f 2b 44 34 67 d9 ec |..E.......+D4g..|
205 0100: e5 9e 45 bf 8d 7f 9f c6 97 9f 2b 44 34 67 d9 ec |..E.......+D4g..|
206 0110: 8e 0f a0 61 a8 eb 82 82 2e c9 c2 20 25 d5 34 c5 |...a....... %.4.|
206 0110: 8e 0f a0 61 a8 eb 82 82 2e c9 c2 20 25 d5 34 c5 |...a....... %.4.|
207 0120: d0 d8 c2 dc d4 c2 d4 c4 30 d9 34 cd c0 d4 c8 cc |........0.4.....|
207 0120: d0 d8 c2 dc d4 c2 d4 c4 30 d9 34 cd c0 d4 c8 cc |........0.4.....|
208 0130: 34 31 c5 d0 c4 24 31 c9 32 2d d1 c2 2c c5 30 25 |41...$1.2-..,.0%|
208 0130: 34 31 c5 d0 c4 24 31 c9 32 2d d1 c2 2c c5 30 25 |41...$1.2-..,.0%|
209 0140: 09 e4 ee 85 8f 85 ff 88 ab 89 36 c7 2a c4 47 34 |..........6.*.G4|
209 0140: 09 e4 ee 85 8f 85 ff 88 ab 89 36 c7 2a c4 47 34 |..........6.*.G4|
210 0150: fe f8 ec 7b 73 37 3f c3 24 62 1d 8d 4d 1d 9e 40 |...{s7?.$b..M..@|
210 0150: fe f8 ec 7b 73 37 3f c3 24 62 1d 8d 4d 1d 9e 40 |...{s7?.$b..M..@|
211 0160: 06 3b 10 14 36 a4 38 10 04 d8 21 01 5a b2 83 f7 |.;..6.8...!.Z...|
211 0160: 06 3b 10 14 36 a4 38 10 04 d8 21 01 5a b2 83 f7 |.;..6.8...!.Z...|
212 0170: e9 45 8b d2 56 c7 a3 1f 82 52 d7 8a 78 ed fc d5 |.E..V....R..x...|
212 0170: e9 45 8b d2 56 c7 a3 1f 82 52 d7 8a 78 ed fc d5 |.E..V....R..x...|
213 0180: 76 f1 36 25 81 49 c0 ad 30 c0 0e 49 8f 54 b7 9e |v.6%.I..0..I.T..|
213 0180: 76 f1 36 25 81 49 c0 ad 30 c0 0e 49 8f 54 b7 9e |v.6%.I..0..I.T..|
214 0190: d4 1c 09 00 bb 8d f0 bd |........|
214 0190: d4 1c 09 00 bb 8d f0 bd |........|
215
215
216 $ echo "http://localhost:$HGPORT1/full.hg" > server/.hg/clonebundles.manifest
216 $ echo "http://localhost:$HGPORT1/full.hg" > server/.hg/clonebundles.manifest
217 $ hg clone -U http://localhost:$HGPORT full-bundle
217 $ hg clone -U http://localhost:$HGPORT full-bundle
218 applying clone bundle from http://localhost:$HGPORT1/full.hg
218 applying clone bundle from http://localhost:$HGPORT1/full.hg
219 adding changesets
219 adding changesets
220 adding manifests
220 adding manifests
221 adding file changes
221 adding file changes
222 added 2 changesets with 2 changes to 2 files
222 added 2 changesets with 2 changes to 2 files
223 finished applying clone bundle
223 finished applying clone bundle
224 searching for changes
224 searching for changes
225 no changes found
225 no changes found
226
226
227 Feature works over SSH
228
229 $ hg clone -U -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/server ssh-full-clone
230 applying clone bundle from http://localhost:$HGPORT1/full.hg
231 adding changesets
232 adding manifests
233 adding file changes
234 added 2 changesets with 2 changes to 2 files
235 finished applying clone bundle
236 searching for changes
237 no changes found
238
227 Entry with unknown BUNDLESPEC is filtered and not used
239 Entry with unknown BUNDLESPEC is filtered and not used
228
240
229 $ cat > server/.hg/clonebundles.manifest << EOF
241 $ cat > server/.hg/clonebundles.manifest << EOF
230 > http://bad.entry1 BUNDLESPEC=UNKNOWN
242 > http://bad.entry1 BUNDLESPEC=UNKNOWN
231 > http://bad.entry2 BUNDLESPEC=xz-v1
243 > http://bad.entry2 BUNDLESPEC=xz-v1
232 > http://bad.entry3 BUNDLESPEC=none-v100
244 > http://bad.entry3 BUNDLESPEC=none-v100
233 > http://localhost:$HGPORT1/full.hg BUNDLESPEC=gzip-v2
245 > http://localhost:$HGPORT1/full.hg BUNDLESPEC=gzip-v2
234 > EOF
246 > EOF
235
247
236 $ hg clone -U http://localhost:$HGPORT filter-unknown-type
248 $ hg clone -U http://localhost:$HGPORT filter-unknown-type
237 applying clone bundle from http://localhost:$HGPORT1/full.hg
249 applying clone bundle from http://localhost:$HGPORT1/full.hg
238 adding changesets
250 adding changesets
239 adding manifests
251 adding manifests
240 adding file changes
252 adding file changes
241 added 2 changesets with 2 changes to 2 files
253 added 2 changesets with 2 changes to 2 files
242 finished applying clone bundle
254 finished applying clone bundle
243 searching for changes
255 searching for changes
244 no changes found
256 no changes found
245
257
246 Automatic fallback when all entries are filtered
258 Automatic fallback when all entries are filtered
247
259
248 $ cat > server/.hg/clonebundles.manifest << EOF
260 $ cat > server/.hg/clonebundles.manifest << EOF
249 > http://bad.entry BUNDLESPEC=UNKNOWN
261 > http://bad.entry BUNDLESPEC=UNKNOWN
250 > EOF
262 > EOF
251
263
252 $ hg clone -U http://localhost:$HGPORT filter-all
264 $ hg clone -U http://localhost:$HGPORT filter-all
253 no compatible clone bundles available on server; falling back to regular clone
265 no compatible clone bundles available on server; falling back to regular clone
254 (you may want to report this to the server operator)
266 (you may want to report this to the server operator)
255 requesting all changes
267 requesting all changes
256 adding changesets
268 adding changesets
257 adding manifests
269 adding manifests
258 adding file changes
270 adding file changes
259 added 2 changesets with 2 changes to 2 files
271 added 2 changesets with 2 changes to 2 files
260
272
261 URLs requiring SNI are filtered in Python <2.7.9
273 URLs requiring SNI are filtered in Python <2.7.9
262
274
263 $ cp full.hg sni.hg
275 $ cp full.hg sni.hg
264 $ cat > server/.hg/clonebundles.manifest << EOF
276 $ cat > server/.hg/clonebundles.manifest << EOF
265 > http://localhost:$HGPORT1/sni.hg REQUIRESNI=true
277 > http://localhost:$HGPORT1/sni.hg REQUIRESNI=true
266 > http://localhost:$HGPORT1/full.hg
278 > http://localhost:$HGPORT1/full.hg
267 > EOF
279 > EOF
268
280
269 #if sslcontext
281 #if sslcontext
270 Python 2.7.9+ support SNI
282 Python 2.7.9+ support SNI
271
283
272 $ hg clone -U http://localhost:$HGPORT sni-supported
284 $ hg clone -U http://localhost:$HGPORT sni-supported
273 applying clone bundle from http://localhost:$HGPORT1/sni.hg
285 applying clone bundle from http://localhost:$HGPORT1/sni.hg
274 adding changesets
286 adding changesets
275 adding manifests
287 adding manifests
276 adding file changes
288 adding file changes
277 added 2 changesets with 2 changes to 2 files
289 added 2 changesets with 2 changes to 2 files
278 finished applying clone bundle
290 finished applying clone bundle
279 searching for changes
291 searching for changes
280 no changes found
292 no changes found
281 #else
293 #else
282 Python <2.7.9 will filter SNI URLs
294 Python <2.7.9 will filter SNI URLs
283
295
284 $ hg clone -U http://localhost:$HGPORT sni-unsupported
296 $ hg clone -U http://localhost:$HGPORT sni-unsupported
285 applying clone bundle from http://localhost:$HGPORT1/full.hg
297 applying clone bundle from http://localhost:$HGPORT1/full.hg
286 adding changesets
298 adding changesets
287 adding manifests
299 adding manifests
288 adding file changes
300 adding file changes
289 added 2 changesets with 2 changes to 2 files
301 added 2 changesets with 2 changes to 2 files
290 finished applying clone bundle
302 finished applying clone bundle
291 searching for changes
303 searching for changes
292 no changes found
304 no changes found
293 #endif
305 #endif
294
306
295 Stream clone bundles are supported
307 Stream clone bundles are supported
296
308
297 $ hg -R server debugcreatestreamclonebundle packed.hg
309 $ hg -R server debugcreatestreamclonebundle packed.hg
298 writing 613 bytes for 4 files
310 writing 613 bytes for 4 files
299 bundle requirements: revlogv1
311 bundle requirements: revlogv1
300
312
301 No bundle spec should work
313 No bundle spec should work
302
314
303 $ cat > server/.hg/clonebundles.manifest << EOF
315 $ cat > server/.hg/clonebundles.manifest << EOF
304 > http://localhost:$HGPORT1/packed.hg
316 > http://localhost:$HGPORT1/packed.hg
305 > EOF
317 > EOF
306
318
307 $ hg clone -U http://localhost:$HGPORT stream-clone-no-spec
319 $ hg clone -U http://localhost:$HGPORT stream-clone-no-spec
308 applying clone bundle from http://localhost:$HGPORT1/packed.hg
320 applying clone bundle from http://localhost:$HGPORT1/packed.hg
309 4 files to transfer, 613 bytes of data
321 4 files to transfer, 613 bytes of data
310 transferred 613 bytes in *.* seconds (*) (glob)
322 transferred 613 bytes in *.* seconds (*) (glob)
311 finished applying clone bundle
323 finished applying clone bundle
312 searching for changes
324 searching for changes
313 no changes found
325 no changes found
314
326
315 Bundle spec without parameters should work
327 Bundle spec without parameters should work
316
328
317 $ cat > server/.hg/clonebundles.manifest << EOF
329 $ cat > server/.hg/clonebundles.manifest << EOF
318 > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1
330 > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1
319 > EOF
331 > EOF
320
332
321 $ hg clone -U http://localhost:$HGPORT stream-clone-vanilla-spec
333 $ hg clone -U http://localhost:$HGPORT stream-clone-vanilla-spec
322 applying clone bundle from http://localhost:$HGPORT1/packed.hg
334 applying clone bundle from http://localhost:$HGPORT1/packed.hg
323 4 files to transfer, 613 bytes of data
335 4 files to transfer, 613 bytes of data
324 transferred 613 bytes in *.* seconds (*) (glob)
336 transferred 613 bytes in *.* seconds (*) (glob)
325 finished applying clone bundle
337 finished applying clone bundle
326 searching for changes
338 searching for changes
327 no changes found
339 no changes found
328
340
329 Bundle spec with format requirements should work
341 Bundle spec with format requirements should work
330
342
331 $ cat > server/.hg/clonebundles.manifest << EOF
343 $ cat > server/.hg/clonebundles.manifest << EOF
332 > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1;requirements%3Drevlogv1
344 > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1;requirements%3Drevlogv1
333 > EOF
345 > EOF
334
346
335 $ hg clone -U http://localhost:$HGPORT stream-clone-supported-requirements
347 $ hg clone -U http://localhost:$HGPORT stream-clone-supported-requirements
336 applying clone bundle from http://localhost:$HGPORT1/packed.hg
348 applying clone bundle from http://localhost:$HGPORT1/packed.hg
337 4 files to transfer, 613 bytes of data
349 4 files to transfer, 613 bytes of data
338 transferred 613 bytes in *.* seconds (*) (glob)
350 transferred 613 bytes in *.* seconds (*) (glob)
339 finished applying clone bundle
351 finished applying clone bundle
340 searching for changes
352 searching for changes
341 no changes found
353 no changes found
342
354
343 Stream bundle spec with unknown requirements should be filtered out
355 Stream bundle spec with unknown requirements should be filtered out
344
356
345 $ cat > server/.hg/clonebundles.manifest << EOF
357 $ cat > server/.hg/clonebundles.manifest << EOF
346 > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1;requirements%3Drevlogv42
358 > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1;requirements%3Drevlogv42
347 > EOF
359 > EOF
348
360
349 $ hg clone -U http://localhost:$HGPORT stream-clone-unsupported-requirements
361 $ hg clone -U http://localhost:$HGPORT stream-clone-unsupported-requirements
350 no compatible clone bundles available on server; falling back to regular clone
362 no compatible clone bundles available on server; falling back to regular clone
351 (you may want to report this to the server operator)
363 (you may want to report this to the server operator)
352 requesting all changes
364 requesting all changes
353 adding changesets
365 adding changesets
354 adding manifests
366 adding manifests
355 adding file changes
367 adding file changes
356 added 2 changesets with 2 changes to 2 files
368 added 2 changesets with 2 changes to 2 files
357
369
358 Set up manifest for testing preferences
370 Set up manifest for testing preferences
359 (Remember, the TYPE does not have to match reality - the URL is
371 (Remember, the TYPE does not have to match reality - the URL is
360 important)
372 important)
361
373
362 $ cp full.hg gz-a.hg
374 $ cp full.hg gz-a.hg
363 $ cp full.hg gz-b.hg
375 $ cp full.hg gz-b.hg
364 $ cp full.hg bz2-a.hg
376 $ cp full.hg bz2-a.hg
365 $ cp full.hg bz2-b.hg
377 $ cp full.hg bz2-b.hg
366 $ cat > server/.hg/clonebundles.manifest << EOF
378 $ cat > server/.hg/clonebundles.manifest << EOF
367 > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2 extra=a
379 > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2 extra=a
368 > http://localhost:$HGPORT1/bz2-a.hg BUNDLESPEC=bzip2-v2 extra=a
380 > http://localhost:$HGPORT1/bz2-a.hg BUNDLESPEC=bzip2-v2 extra=a
369 > http://localhost:$HGPORT1/gz-b.hg BUNDLESPEC=gzip-v2 extra=b
381 > http://localhost:$HGPORT1/gz-b.hg BUNDLESPEC=gzip-v2 extra=b
370 > http://localhost:$HGPORT1/bz2-b.hg BUNDLESPEC=bzip2-v2 extra=b
382 > http://localhost:$HGPORT1/bz2-b.hg BUNDLESPEC=bzip2-v2 extra=b
371 > EOF
383 > EOF
372
384
373 Preferring an undefined attribute will take first entry
385 Preferring an undefined attribute will take first entry
374
386
375 $ hg --config experimental.clonebundleprefers=foo=bar clone -U http://localhost:$HGPORT prefer-foo
387 $ hg --config experimental.clonebundleprefers=foo=bar clone -U http://localhost:$HGPORT prefer-foo
376 applying clone bundle from http://localhost:$HGPORT1/gz-a.hg
388 applying clone bundle from http://localhost:$HGPORT1/gz-a.hg
377 adding changesets
389 adding changesets
378 adding manifests
390 adding manifests
379 adding file changes
391 adding file changes
380 added 2 changesets with 2 changes to 2 files
392 added 2 changesets with 2 changes to 2 files
381 finished applying clone bundle
393 finished applying clone bundle
382 searching for changes
394 searching for changes
383 no changes found
395 no changes found
384
396
385 Preferring bz2 type will download first entry of that type
397 Preferring bz2 type will download first entry of that type
386
398
387 $ hg --config experimental.clonebundleprefers=COMPRESSION=bzip2 clone -U http://localhost:$HGPORT prefer-bz
399 $ hg --config experimental.clonebundleprefers=COMPRESSION=bzip2 clone -U http://localhost:$HGPORT prefer-bz
388 applying clone bundle from http://localhost:$HGPORT1/bz2-a.hg
400 applying clone bundle from http://localhost:$HGPORT1/bz2-a.hg
389 adding changesets
401 adding changesets
390 adding manifests
402 adding manifests
391 adding file changes
403 adding file changes
392 added 2 changesets with 2 changes to 2 files
404 added 2 changesets with 2 changes to 2 files
393 finished applying clone bundle
405 finished applying clone bundle
394 searching for changes
406 searching for changes
395 no changes found
407 no changes found
396
408
397 Preferring multiple values of an option works
409 Preferring multiple values of an option works
398
410
399 $ hg --config experimental.clonebundleprefers=COMPRESSION=unknown,COMPRESSION=bzip2 clone -U http://localhost:$HGPORT prefer-multiple-bz
411 $ hg --config experimental.clonebundleprefers=COMPRESSION=unknown,COMPRESSION=bzip2 clone -U http://localhost:$HGPORT prefer-multiple-bz
400 applying clone bundle from http://localhost:$HGPORT1/bz2-a.hg
412 applying clone bundle from http://localhost:$HGPORT1/bz2-a.hg
401 adding changesets
413 adding changesets
402 adding manifests
414 adding manifests
403 adding file changes
415 adding file changes
404 added 2 changesets with 2 changes to 2 files
416 added 2 changesets with 2 changes to 2 files
405 finished applying clone bundle
417 finished applying clone bundle
406 searching for changes
418 searching for changes
407 no changes found
419 no changes found
408
420
409 Sorting multiple values should get us back to original first entry
421 Sorting multiple values should get us back to original first entry
410
422
411 $ hg --config experimental.clonebundleprefers=BUNDLESPEC=unknown,BUNDLESPEC=gzip-v2,BUNDLESPEC=bzip2-v2 clone -U http://localhost:$HGPORT prefer-multiple-gz
423 $ hg --config experimental.clonebundleprefers=BUNDLESPEC=unknown,BUNDLESPEC=gzip-v2,BUNDLESPEC=bzip2-v2 clone -U http://localhost:$HGPORT prefer-multiple-gz
412 applying clone bundle from http://localhost:$HGPORT1/gz-a.hg
424 applying clone bundle from http://localhost:$HGPORT1/gz-a.hg
413 adding changesets
425 adding changesets
414 adding manifests
426 adding manifests
415 adding file changes
427 adding file changes
416 added 2 changesets with 2 changes to 2 files
428 added 2 changesets with 2 changes to 2 files
417 finished applying clone bundle
429 finished applying clone bundle
418 searching for changes
430 searching for changes
419 no changes found
431 no changes found
420
432
421 Preferring multiple attributes has correct order
433 Preferring multiple attributes has correct order
422
434
423 $ hg --config experimental.clonebundleprefers=extra=b,BUNDLESPEC=bzip2-v2 clone -U http://localhost:$HGPORT prefer-separate-attributes
435 $ hg --config experimental.clonebundleprefers=extra=b,BUNDLESPEC=bzip2-v2 clone -U http://localhost:$HGPORT prefer-separate-attributes
424 applying clone bundle from http://localhost:$HGPORT1/bz2-b.hg
436 applying clone bundle from http://localhost:$HGPORT1/bz2-b.hg
425 adding changesets
437 adding changesets
426 adding manifests
438 adding manifests
427 adding file changes
439 adding file changes
428 added 2 changesets with 2 changes to 2 files
440 added 2 changesets with 2 changes to 2 files
429 finished applying clone bundle
441 finished applying clone bundle
430 searching for changes
442 searching for changes
431 no changes found
443 no changes found
432
444
433 Test where attribute is missing from some entries
445 Test where attribute is missing from some entries
434
446
435 $ cat > server/.hg/clonebundles.manifest << EOF
447 $ cat > server/.hg/clonebundles.manifest << EOF
436 > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2
448 > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2
437 > http://localhost:$HGPORT1/bz2-a.hg BUNDLESPEC=bzip2-v2
449 > http://localhost:$HGPORT1/bz2-a.hg BUNDLESPEC=bzip2-v2
438 > http://localhost:$HGPORT1/gz-b.hg BUNDLESPEC=gzip-v2 extra=b
450 > http://localhost:$HGPORT1/gz-b.hg BUNDLESPEC=gzip-v2 extra=b
439 > http://localhost:$HGPORT1/bz2-b.hg BUNDLESPEC=bzip2-v2 extra=b
451 > http://localhost:$HGPORT1/bz2-b.hg BUNDLESPEC=bzip2-v2 extra=b
440 > EOF
452 > EOF
441
453
442 $ hg --config experimental.clonebundleprefers=extra=b clone -U http://localhost:$HGPORT prefer-partially-defined-attribute
454 $ hg --config experimental.clonebundleprefers=extra=b clone -U http://localhost:$HGPORT prefer-partially-defined-attribute
443 applying clone bundle from http://localhost:$HGPORT1/gz-b.hg
455 applying clone bundle from http://localhost:$HGPORT1/gz-b.hg
444 adding changesets
456 adding changesets
445 adding manifests
457 adding manifests
446 adding file changes
458 adding file changes
447 added 2 changesets with 2 changes to 2 files
459 added 2 changesets with 2 changes to 2 files
448 finished applying clone bundle
460 finished applying clone bundle
449 searching for changes
461 searching for changes
450 no changes found
462 no changes found
General Comments 0
You need to be logged in to leave comments. Login now