##// END OF EJS Templates
pullbundle: fix handling of gzip bundlespecs...
Joerg Sonnenberger -
r38700:7e4a856a default
parent child Browse files
Show More
@@ -1,666 +1,668
1 # wireprotov1server.py - Wire protocol version 1 server functionality
1 # wireprotov1server.py - Wire protocol version 1 server functionality
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
11
12 from .i18n import _
12 from .i18n import _
13 from .node import (
13 from .node import (
14 hex,
14 hex,
15 nullid,
15 nullid,
16 )
16 )
17
17
18 from . import (
18 from . import (
19 bundle2,
19 bundle2,
20 changegroup as changegroupmod,
20 changegroup as changegroupmod,
21 discovery,
21 discovery,
22 encoding,
22 encoding,
23 error,
23 error,
24 exchange,
24 exchange,
25 pushkey as pushkeymod,
25 pushkey as pushkeymod,
26 pycompat,
26 pycompat,
27 streamclone,
27 streamclone,
28 util,
28 util,
29 wireprototypes,
29 wireprototypes,
30 )
30 )
31
31
32 from .utils import (
32 from .utils import (
33 procutil,
33 procutil,
34 stringutil,
34 stringutil,
35 )
35 )
36
36
37 urlerr = util.urlerr
37 urlerr = util.urlerr
38 urlreq = util.urlreq
38 urlreq = util.urlreq
39
39
40 bundle2requiredmain = _('incompatible Mercurial client; bundle2 required')
40 bundle2requiredmain = _('incompatible Mercurial client; bundle2 required')
41 bundle2requiredhint = _('see https://www.mercurial-scm.org/wiki/'
41 bundle2requiredhint = _('see https://www.mercurial-scm.org/wiki/'
42 'IncompatibleClient')
42 'IncompatibleClient')
43 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
43 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
44
44
45 def clientcompressionsupport(proto):
45 def clientcompressionsupport(proto):
46 """Returns a list of compression methods supported by the client.
46 """Returns a list of compression methods supported by the client.
47
47
48 Returns a list of the compression methods supported by the client
48 Returns a list of the compression methods supported by the client
49 according to the protocol capabilities. If no such capability has
49 according to the protocol capabilities. If no such capability has
50 been announced, fallback to the default of zlib and uncompressed.
50 been announced, fallback to the default of zlib and uncompressed.
51 """
51 """
52 for cap in proto.getprotocaps():
52 for cap in proto.getprotocaps():
53 if cap.startswith('comp='):
53 if cap.startswith('comp='):
54 return cap[5:].split(',')
54 return cap[5:].split(',')
55 return ['zlib', 'none']
55 return ['zlib', 'none']
56
56
57 # wire protocol command can either return a string or one of these classes.
57 # wire protocol command can either return a string or one of these classes.
58
58
59 def getdispatchrepo(repo, proto, command):
59 def getdispatchrepo(repo, proto, command):
60 """Obtain the repo used for processing wire protocol commands.
60 """Obtain the repo used for processing wire protocol commands.
61
61
62 The intent of this function is to serve as a monkeypatch point for
62 The intent of this function is to serve as a monkeypatch point for
63 extensions that need commands to operate on different repo views under
63 extensions that need commands to operate on different repo views under
64 specialized circumstances.
64 specialized circumstances.
65 """
65 """
66 return repo.filtered('served')
66 return repo.filtered('served')
67
67
68 def dispatch(repo, proto, command):
68 def dispatch(repo, proto, command):
69 repo = getdispatchrepo(repo, proto, command)
69 repo = getdispatchrepo(repo, proto, command)
70
70
71 func, spec = commands[command]
71 func, spec = commands[command]
72 args = proto.getargs(spec)
72 args = proto.getargs(spec)
73
73
74 return func(repo, proto, *args)
74 return func(repo, proto, *args)
75
75
76 def options(cmd, keys, others):
76 def options(cmd, keys, others):
77 opts = {}
77 opts = {}
78 for k in keys:
78 for k in keys:
79 if k in others:
79 if k in others:
80 opts[k] = others[k]
80 opts[k] = others[k]
81 del others[k]
81 del others[k]
82 if others:
82 if others:
83 procutil.stderr.write("warning: %s ignored unexpected arguments %s\n"
83 procutil.stderr.write("warning: %s ignored unexpected arguments %s\n"
84 % (cmd, ",".join(others)))
84 % (cmd, ",".join(others)))
85 return opts
85 return opts
86
86
87 def bundle1allowed(repo, action):
87 def bundle1allowed(repo, action):
88 """Whether a bundle1 operation is allowed from the server.
88 """Whether a bundle1 operation is allowed from the server.
89
89
90 Priority is:
90 Priority is:
91
91
92 1. server.bundle1gd.<action> (if generaldelta active)
92 1. server.bundle1gd.<action> (if generaldelta active)
93 2. server.bundle1.<action>
93 2. server.bundle1.<action>
94 3. server.bundle1gd (if generaldelta active)
94 3. server.bundle1gd (if generaldelta active)
95 4. server.bundle1
95 4. server.bundle1
96 """
96 """
97 ui = repo.ui
97 ui = repo.ui
98 gd = 'generaldelta' in repo.requirements
98 gd = 'generaldelta' in repo.requirements
99
99
100 if gd:
100 if gd:
101 v = ui.configbool('server', 'bundle1gd.%s' % action)
101 v = ui.configbool('server', 'bundle1gd.%s' % action)
102 if v is not None:
102 if v is not None:
103 return v
103 return v
104
104
105 v = ui.configbool('server', 'bundle1.%s' % action)
105 v = ui.configbool('server', 'bundle1.%s' % action)
106 if v is not None:
106 if v is not None:
107 return v
107 return v
108
108
109 if gd:
109 if gd:
110 v = ui.configbool('server', 'bundle1gd')
110 v = ui.configbool('server', 'bundle1gd')
111 if v is not None:
111 if v is not None:
112 return v
112 return v
113
113
114 return ui.configbool('server', 'bundle1')
114 return ui.configbool('server', 'bundle1')
115
115
116 commands = wireprototypes.commanddict()
116 commands = wireprototypes.commanddict()
117
117
118 def wireprotocommand(name, args=None, permission='push'):
118 def wireprotocommand(name, args=None, permission='push'):
119 """Decorator to declare a wire protocol command.
119 """Decorator to declare a wire protocol command.
120
120
121 ``name`` is the name of the wire protocol command being provided.
121 ``name`` is the name of the wire protocol command being provided.
122
122
123 ``args`` defines the named arguments accepted by the command. It is
123 ``args`` defines the named arguments accepted by the command. It is
124 a space-delimited list of argument names. ``*`` denotes a special value
124 a space-delimited list of argument names. ``*`` denotes a special value
125 that says to accept all named arguments.
125 that says to accept all named arguments.
126
126
127 ``permission`` defines the permission type needed to run this command.
127 ``permission`` defines the permission type needed to run this command.
128 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
128 Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
129 respectively. Default is to assume command requires ``push`` permissions
129 respectively. Default is to assume command requires ``push`` permissions
130 because otherwise commands not declaring their permissions could modify
130 because otherwise commands not declaring their permissions could modify
131 a repository that is supposed to be read-only.
131 a repository that is supposed to be read-only.
132 """
132 """
133 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
133 transports = {k for k, v in wireprototypes.TRANSPORTS.items()
134 if v['version'] == 1}
134 if v['version'] == 1}
135
135
136 # Because SSHv2 is a mirror of SSHv1, we allow "batch" commands through to
136 # Because SSHv2 is a mirror of SSHv1, we allow "batch" commands through to
137 # SSHv2.
137 # SSHv2.
138 # TODO undo this hack when SSH is using the unified frame protocol.
138 # TODO undo this hack when SSH is using the unified frame protocol.
139 if name == b'batch':
139 if name == b'batch':
140 transports.add(wireprototypes.SSHV2)
140 transports.add(wireprototypes.SSHV2)
141
141
142 if permission not in ('push', 'pull'):
142 if permission not in ('push', 'pull'):
143 raise error.ProgrammingError('invalid wire protocol permission; '
143 raise error.ProgrammingError('invalid wire protocol permission; '
144 'got %s; expected "push" or "pull"' %
144 'got %s; expected "push" or "pull"' %
145 permission)
145 permission)
146
146
147 if args is None:
147 if args is None:
148 args = ''
148 args = ''
149
149
150 if not isinstance(args, bytes):
150 if not isinstance(args, bytes):
151 raise error.ProgrammingError('arguments for version 1 commands '
151 raise error.ProgrammingError('arguments for version 1 commands '
152 'must be declared as bytes')
152 'must be declared as bytes')
153
153
154 def register(func):
154 def register(func):
155 if name in commands:
155 if name in commands:
156 raise error.ProgrammingError('%s command already registered '
156 raise error.ProgrammingError('%s command already registered '
157 'for version 1' % name)
157 'for version 1' % name)
158 commands[name] = wireprototypes.commandentry(
158 commands[name] = wireprototypes.commandentry(
159 func, args=args, transports=transports, permission=permission)
159 func, args=args, transports=transports, permission=permission)
160
160
161 return func
161 return func
162 return register
162 return register
163
163
164 # TODO define a more appropriate permissions type to use for this.
164 # TODO define a more appropriate permissions type to use for this.
165 @wireprotocommand('batch', 'cmds *', permission='pull')
165 @wireprotocommand('batch', 'cmds *', permission='pull')
166 def batch(repo, proto, cmds, others):
166 def batch(repo, proto, cmds, others):
167 unescapearg = wireprototypes.unescapebatcharg
167 unescapearg = wireprototypes.unescapebatcharg
168 repo = repo.filtered("served")
168 repo = repo.filtered("served")
169 res = []
169 res = []
170 for pair in cmds.split(';'):
170 for pair in cmds.split(';'):
171 op, args = pair.split(' ', 1)
171 op, args = pair.split(' ', 1)
172 vals = {}
172 vals = {}
173 for a in args.split(','):
173 for a in args.split(','):
174 if a:
174 if a:
175 n, v = a.split('=')
175 n, v = a.split('=')
176 vals[unescapearg(n)] = unescapearg(v)
176 vals[unescapearg(n)] = unescapearg(v)
177 func, spec = commands[op]
177 func, spec = commands[op]
178
178
179 # Validate that client has permissions to perform this command.
179 # Validate that client has permissions to perform this command.
180 perm = commands[op].permission
180 perm = commands[op].permission
181 assert perm in ('push', 'pull')
181 assert perm in ('push', 'pull')
182 proto.checkperm(perm)
182 proto.checkperm(perm)
183
183
184 if spec:
184 if spec:
185 keys = spec.split()
185 keys = spec.split()
186 data = {}
186 data = {}
187 for k in keys:
187 for k in keys:
188 if k == '*':
188 if k == '*':
189 star = {}
189 star = {}
190 for key in vals.keys():
190 for key in vals.keys():
191 if key not in keys:
191 if key not in keys:
192 star[key] = vals[key]
192 star[key] = vals[key]
193 data['*'] = star
193 data['*'] = star
194 else:
194 else:
195 data[k] = vals[k]
195 data[k] = vals[k]
196 result = func(repo, proto, *[data[k] for k in keys])
196 result = func(repo, proto, *[data[k] for k in keys])
197 else:
197 else:
198 result = func(repo, proto)
198 result = func(repo, proto)
199 if isinstance(result, wireprototypes.ooberror):
199 if isinstance(result, wireprototypes.ooberror):
200 return result
200 return result
201
201
202 # For now, all batchable commands must return bytesresponse or
202 # For now, all batchable commands must return bytesresponse or
203 # raw bytes (for backwards compatibility).
203 # raw bytes (for backwards compatibility).
204 assert isinstance(result, (wireprototypes.bytesresponse, bytes))
204 assert isinstance(result, (wireprototypes.bytesresponse, bytes))
205 if isinstance(result, wireprototypes.bytesresponse):
205 if isinstance(result, wireprototypes.bytesresponse):
206 result = result.data
206 result = result.data
207 res.append(wireprototypes.escapebatcharg(result))
207 res.append(wireprototypes.escapebatcharg(result))
208
208
209 return wireprototypes.bytesresponse(';'.join(res))
209 return wireprototypes.bytesresponse(';'.join(res))
210
210
211 @wireprotocommand('between', 'pairs', permission='pull')
211 @wireprotocommand('between', 'pairs', permission='pull')
212 def between(repo, proto, pairs):
212 def between(repo, proto, pairs):
213 pairs = [wireprototypes.decodelist(p, '-') for p in pairs.split(" ")]
213 pairs = [wireprototypes.decodelist(p, '-') for p in pairs.split(" ")]
214 r = []
214 r = []
215 for b in repo.between(pairs):
215 for b in repo.between(pairs):
216 r.append(wireprototypes.encodelist(b) + "\n")
216 r.append(wireprototypes.encodelist(b) + "\n")
217
217
218 return wireprototypes.bytesresponse(''.join(r))
218 return wireprototypes.bytesresponse(''.join(r))
219
219
220 @wireprotocommand('branchmap', permission='pull')
220 @wireprotocommand('branchmap', permission='pull')
221 def branchmap(repo, proto):
221 def branchmap(repo, proto):
222 branchmap = repo.branchmap()
222 branchmap = repo.branchmap()
223 heads = []
223 heads = []
224 for branch, nodes in branchmap.iteritems():
224 for branch, nodes in branchmap.iteritems():
225 branchname = urlreq.quote(encoding.fromlocal(branch))
225 branchname = urlreq.quote(encoding.fromlocal(branch))
226 branchnodes = wireprototypes.encodelist(nodes)
226 branchnodes = wireprototypes.encodelist(nodes)
227 heads.append('%s %s' % (branchname, branchnodes))
227 heads.append('%s %s' % (branchname, branchnodes))
228
228
229 return wireprototypes.bytesresponse('\n'.join(heads))
229 return wireprototypes.bytesresponse('\n'.join(heads))
230
230
231 @wireprotocommand('branches', 'nodes', permission='pull')
231 @wireprotocommand('branches', 'nodes', permission='pull')
232 def branches(repo, proto, nodes):
232 def branches(repo, proto, nodes):
233 nodes = wireprototypes.decodelist(nodes)
233 nodes = wireprototypes.decodelist(nodes)
234 r = []
234 r = []
235 for b in repo.branches(nodes):
235 for b in repo.branches(nodes):
236 r.append(wireprototypes.encodelist(b) + "\n")
236 r.append(wireprototypes.encodelist(b) + "\n")
237
237
238 return wireprototypes.bytesresponse(''.join(r))
238 return wireprototypes.bytesresponse(''.join(r))
239
239
240 @wireprotocommand('clonebundles', '', permission='pull')
240 @wireprotocommand('clonebundles', '', permission='pull')
241 def clonebundles(repo, proto):
241 def clonebundles(repo, proto):
242 """Server command for returning info for available bundles to seed clones.
242 """Server command for returning info for available bundles to seed clones.
243
243
244 Clients will parse this response and determine what bundle to fetch.
244 Clients will parse this response and determine what bundle to fetch.
245
245
246 Extensions may wrap this command to filter or dynamically emit data
246 Extensions may wrap this command to filter or dynamically emit data
247 depending on the request. e.g. you could advertise URLs for the closest
247 depending on the request. e.g. you could advertise URLs for the closest
248 data center given the client's IP address.
248 data center given the client's IP address.
249 """
249 """
250 return wireprototypes.bytesresponse(
250 return wireprototypes.bytesresponse(
251 repo.vfs.tryread('clonebundles.manifest'))
251 repo.vfs.tryread('clonebundles.manifest'))
252
252
253 wireprotocaps = ['lookup', 'branchmap', 'pushkey',
253 wireprotocaps = ['lookup', 'branchmap', 'pushkey',
254 'known', 'getbundle', 'unbundlehash']
254 'known', 'getbundle', 'unbundlehash']
255
255
256 def _capabilities(repo, proto):
256 def _capabilities(repo, proto):
257 """return a list of capabilities for a repo
257 """return a list of capabilities for a repo
258
258
259 This function exists to allow extensions to easily wrap capabilities
259 This function exists to allow extensions to easily wrap capabilities
260 computation
260 computation
261
261
262 - returns a lists: easy to alter
262 - returns a lists: easy to alter
263 - change done here will be propagated to both `capabilities` and `hello`
263 - change done here will be propagated to both `capabilities` and `hello`
264 command without any other action needed.
264 command without any other action needed.
265 """
265 """
266 # copy to prevent modification of the global list
266 # copy to prevent modification of the global list
267 caps = list(wireprotocaps)
267 caps = list(wireprotocaps)
268
268
269 # Command of same name as capability isn't exposed to version 1 of
269 # Command of same name as capability isn't exposed to version 1 of
270 # transports. So conditionally add it.
270 # transports. So conditionally add it.
271 if commands.commandavailable('changegroupsubset', proto):
271 if commands.commandavailable('changegroupsubset', proto):
272 caps.append('changegroupsubset')
272 caps.append('changegroupsubset')
273
273
274 if streamclone.allowservergeneration(repo):
274 if streamclone.allowservergeneration(repo):
275 if repo.ui.configbool('server', 'preferuncompressed'):
275 if repo.ui.configbool('server', 'preferuncompressed'):
276 caps.append('stream-preferred')
276 caps.append('stream-preferred')
277 requiredformats = repo.requirements & repo.supportedformats
277 requiredformats = repo.requirements & repo.supportedformats
278 # if our local revlogs are just revlogv1, add 'stream' cap
278 # if our local revlogs are just revlogv1, add 'stream' cap
279 if not requiredformats - {'revlogv1'}:
279 if not requiredformats - {'revlogv1'}:
280 caps.append('stream')
280 caps.append('stream')
281 # otherwise, add 'streamreqs' detailing our local revlog format
281 # otherwise, add 'streamreqs' detailing our local revlog format
282 else:
282 else:
283 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
283 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
284 if repo.ui.configbool('experimental', 'bundle2-advertise'):
284 if repo.ui.configbool('experimental', 'bundle2-advertise'):
285 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role='server'))
285 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role='server'))
286 caps.append('bundle2=' + urlreq.quote(capsblob))
286 caps.append('bundle2=' + urlreq.quote(capsblob))
287 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
287 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
288
288
289 return proto.addcapabilities(repo, caps)
289 return proto.addcapabilities(repo, caps)
290
290
291 # If you are writing an extension and consider wrapping this function. Wrap
291 # If you are writing an extension and consider wrapping this function. Wrap
292 # `_capabilities` instead.
292 # `_capabilities` instead.
293 @wireprotocommand('capabilities', permission='pull')
293 @wireprotocommand('capabilities', permission='pull')
294 def capabilities(repo, proto):
294 def capabilities(repo, proto):
295 caps = _capabilities(repo, proto)
295 caps = _capabilities(repo, proto)
296 return wireprototypes.bytesresponse(' '.join(sorted(caps)))
296 return wireprototypes.bytesresponse(' '.join(sorted(caps)))
297
297
298 @wireprotocommand('changegroup', 'roots', permission='pull')
298 @wireprotocommand('changegroup', 'roots', permission='pull')
299 def changegroup(repo, proto, roots):
299 def changegroup(repo, proto, roots):
300 nodes = wireprototypes.decodelist(roots)
300 nodes = wireprototypes.decodelist(roots)
301 outgoing = discovery.outgoing(repo, missingroots=nodes,
301 outgoing = discovery.outgoing(repo, missingroots=nodes,
302 missingheads=repo.heads())
302 missingheads=repo.heads())
303 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
303 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
304 gen = iter(lambda: cg.read(32768), '')
304 gen = iter(lambda: cg.read(32768), '')
305 return wireprototypes.streamres(gen=gen)
305 return wireprototypes.streamres(gen=gen)
306
306
307 @wireprotocommand('changegroupsubset', 'bases heads',
307 @wireprotocommand('changegroupsubset', 'bases heads',
308 permission='pull')
308 permission='pull')
309 def changegroupsubset(repo, proto, bases, heads):
309 def changegroupsubset(repo, proto, bases, heads):
310 bases = wireprototypes.decodelist(bases)
310 bases = wireprototypes.decodelist(bases)
311 heads = wireprototypes.decodelist(heads)
311 heads = wireprototypes.decodelist(heads)
312 outgoing = discovery.outgoing(repo, missingroots=bases,
312 outgoing = discovery.outgoing(repo, missingroots=bases,
313 missingheads=heads)
313 missingheads=heads)
314 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
314 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
315 gen = iter(lambda: cg.read(32768), '')
315 gen = iter(lambda: cg.read(32768), '')
316 return wireprototypes.streamres(gen=gen)
316 return wireprototypes.streamres(gen=gen)
317
317
318 @wireprotocommand('debugwireargs', 'one two *',
318 @wireprotocommand('debugwireargs', 'one two *',
319 permission='pull')
319 permission='pull')
320 def debugwireargs(repo, proto, one, two, others):
320 def debugwireargs(repo, proto, one, two, others):
321 # only accept optional args from the known set
321 # only accept optional args from the known set
322 opts = options('debugwireargs', ['three', 'four'], others)
322 opts = options('debugwireargs', ['three', 'four'], others)
323 return wireprototypes.bytesresponse(repo.debugwireargs(
323 return wireprototypes.bytesresponse(repo.debugwireargs(
324 one, two, **pycompat.strkwargs(opts)))
324 one, two, **pycompat.strkwargs(opts)))
325
325
326 def find_pullbundle(repo, proto, opts, clheads, heads, common):
326 def find_pullbundle(repo, proto, opts, clheads, heads, common):
327 """Return a file object for the first matching pullbundle.
327 """Return a file object for the first matching pullbundle.
328
328
329 Pullbundles are specified in .hg/pullbundles.manifest similar to
329 Pullbundles are specified in .hg/pullbundles.manifest similar to
330 clonebundles.
330 clonebundles.
331 For each entry, the bundle specification is checked for compatibility:
331 For each entry, the bundle specification is checked for compatibility:
332 - Client features vs the BUNDLESPEC.
332 - Client features vs the BUNDLESPEC.
333 - Revisions shared with the clients vs base revisions of the bundle.
333 - Revisions shared with the clients vs base revisions of the bundle.
334 A bundle can be applied only if all its base revisions are known by
334 A bundle can be applied only if all its base revisions are known by
335 the client.
335 the client.
336 - At least one leaf of the bundle's DAG is missing on the client.
336 - At least one leaf of the bundle's DAG is missing on the client.
337 - Every leaf of the bundle's DAG is part of node set the client wants.
337 - Every leaf of the bundle's DAG is part of node set the client wants.
338 E.g. do not send a bundle of all changes if the client wants only
338 E.g. do not send a bundle of all changes if the client wants only
339 one specific branch of many.
339 one specific branch of many.
340 """
340 """
341 def decodehexstring(s):
341 def decodehexstring(s):
342 return set([h.decode('hex') for h in s.split(';')])
342 return set([h.decode('hex') for h in s.split(';')])
343
343
344 manifest = repo.vfs.tryread('pullbundles.manifest')
344 manifest = repo.vfs.tryread('pullbundles.manifest')
345 if not manifest:
345 if not manifest:
346 return None
346 return None
347 res = exchange.parseclonebundlesmanifest(repo, manifest)
347 res = exchange.parseclonebundlesmanifest(repo, manifest)
348 res = exchange.filterclonebundleentries(repo, res)
348 res = exchange.filterclonebundleentries(repo, res)
349 if not res:
349 if not res:
350 return None
350 return None
351 cl = repo.changelog
351 cl = repo.changelog
352 heads_anc = cl.ancestors([cl.rev(rev) for rev in heads], inclusive=True)
352 heads_anc = cl.ancestors([cl.rev(rev) for rev in heads], inclusive=True)
353 common_anc = cl.ancestors([cl.rev(rev) for rev in common], inclusive=True)
353 common_anc = cl.ancestors([cl.rev(rev) for rev in common], inclusive=True)
354 compformats = clientcompressionsupport(proto)
354 compformats = clientcompressionsupport(proto)
355 for entry in res:
355 for entry in res:
356 if 'COMPRESSION' in entry and entry['COMPRESSION'] not in compformats:
356 comp = entry.get('COMPRESSION')
357 altcomp = util.compengines._bundlenames.get(comp)
358 if comp and comp not in compformats and altcomp not in compformats:
357 continue
359 continue
358 # No test yet for VERSION, since V2 is supported by any client
360 # No test yet for VERSION, since V2 is supported by any client
359 # that advertises partial pulls
361 # that advertises partial pulls
360 if 'heads' in entry:
362 if 'heads' in entry:
361 try:
363 try:
362 bundle_heads = decodehexstring(entry['heads'])
364 bundle_heads = decodehexstring(entry['heads'])
363 except TypeError:
365 except TypeError:
364 # Bad heads entry
366 # Bad heads entry
365 continue
367 continue
366 if bundle_heads.issubset(common):
368 if bundle_heads.issubset(common):
367 continue # Nothing new
369 continue # Nothing new
368 if all(cl.rev(rev) in common_anc for rev in bundle_heads):
370 if all(cl.rev(rev) in common_anc for rev in bundle_heads):
369 continue # Still nothing new
371 continue # Still nothing new
370 if any(cl.rev(rev) not in heads_anc and
372 if any(cl.rev(rev) not in heads_anc and
371 cl.rev(rev) not in common_anc for rev in bundle_heads):
373 cl.rev(rev) not in common_anc for rev in bundle_heads):
372 continue
374 continue
373 if 'bases' in entry:
375 if 'bases' in entry:
374 try:
376 try:
375 bundle_bases = decodehexstring(entry['bases'])
377 bundle_bases = decodehexstring(entry['bases'])
376 except TypeError:
378 except TypeError:
377 # Bad bases entry
379 # Bad bases entry
378 continue
380 continue
379 if not all(cl.rev(rev) in common_anc for rev in bundle_bases):
381 if not all(cl.rev(rev) in common_anc for rev in bundle_bases):
380 continue
382 continue
381 path = entry['URL']
383 path = entry['URL']
382 repo.ui.debug('sending pullbundle "%s"\n' % path)
384 repo.ui.debug('sending pullbundle "%s"\n' % path)
383 try:
385 try:
384 return repo.vfs.open(path)
386 return repo.vfs.open(path)
385 except IOError:
387 except IOError:
386 repo.ui.debug('pullbundle "%s" not accessible\n' % path)
388 repo.ui.debug('pullbundle "%s" not accessible\n' % path)
387 continue
389 continue
388 return None
390 return None
389
391
390 @wireprotocommand('getbundle', '*', permission='pull')
392 @wireprotocommand('getbundle', '*', permission='pull')
391 def getbundle(repo, proto, others):
393 def getbundle(repo, proto, others):
392 opts = options('getbundle', wireprototypes.GETBUNDLE_ARGUMENTS.keys(),
394 opts = options('getbundle', wireprototypes.GETBUNDLE_ARGUMENTS.keys(),
393 others)
395 others)
394 for k, v in opts.iteritems():
396 for k, v in opts.iteritems():
395 keytype = wireprototypes.GETBUNDLE_ARGUMENTS[k]
397 keytype = wireprototypes.GETBUNDLE_ARGUMENTS[k]
396 if keytype == 'nodes':
398 if keytype == 'nodes':
397 opts[k] = wireprototypes.decodelist(v)
399 opts[k] = wireprototypes.decodelist(v)
398 elif keytype == 'csv':
400 elif keytype == 'csv':
399 opts[k] = list(v.split(','))
401 opts[k] = list(v.split(','))
400 elif keytype == 'scsv':
402 elif keytype == 'scsv':
401 opts[k] = set(v.split(','))
403 opts[k] = set(v.split(','))
402 elif keytype == 'boolean':
404 elif keytype == 'boolean':
403 # Client should serialize False as '0', which is a non-empty string
405 # Client should serialize False as '0', which is a non-empty string
404 # so it evaluates as a True bool.
406 # so it evaluates as a True bool.
405 if v == '0':
407 if v == '0':
406 opts[k] = False
408 opts[k] = False
407 else:
409 else:
408 opts[k] = bool(v)
410 opts[k] = bool(v)
409 elif keytype != 'plain':
411 elif keytype != 'plain':
410 raise KeyError('unknown getbundle option type %s'
412 raise KeyError('unknown getbundle option type %s'
411 % keytype)
413 % keytype)
412
414
413 if not bundle1allowed(repo, 'pull'):
415 if not bundle1allowed(repo, 'pull'):
414 if not exchange.bundle2requested(opts.get('bundlecaps')):
416 if not exchange.bundle2requested(opts.get('bundlecaps')):
415 if proto.name == 'http-v1':
417 if proto.name == 'http-v1':
416 return wireprototypes.ooberror(bundle2required)
418 return wireprototypes.ooberror(bundle2required)
417 raise error.Abort(bundle2requiredmain,
419 raise error.Abort(bundle2requiredmain,
418 hint=bundle2requiredhint)
420 hint=bundle2requiredhint)
419
421
420 prefercompressed = True
422 prefercompressed = True
421
423
422 try:
424 try:
423 clheads = set(repo.changelog.heads())
425 clheads = set(repo.changelog.heads())
424 heads = set(opts.get('heads', set()))
426 heads = set(opts.get('heads', set()))
425 common = set(opts.get('common', set()))
427 common = set(opts.get('common', set()))
426 common.discard(nullid)
428 common.discard(nullid)
427 if (repo.ui.configbool('server', 'pullbundle') and
429 if (repo.ui.configbool('server', 'pullbundle') and
428 'partial-pull' in proto.getprotocaps()):
430 'partial-pull' in proto.getprotocaps()):
429 # Check if a pre-built bundle covers this request.
431 # Check if a pre-built bundle covers this request.
430 bundle = find_pullbundle(repo, proto, opts, clheads, heads, common)
432 bundle = find_pullbundle(repo, proto, opts, clheads, heads, common)
431 if bundle:
433 if bundle:
432 return wireprototypes.streamres(gen=util.filechunkiter(bundle),
434 return wireprototypes.streamres(gen=util.filechunkiter(bundle),
433 prefer_uncompressed=True)
435 prefer_uncompressed=True)
434
436
435 if repo.ui.configbool('server', 'disablefullbundle'):
437 if repo.ui.configbool('server', 'disablefullbundle'):
436 # Check to see if this is a full clone.
438 # Check to see if this is a full clone.
437 changegroup = opts.get('cg', True)
439 changegroup = opts.get('cg', True)
438 if changegroup and not common and clheads == heads:
440 if changegroup and not common and clheads == heads:
439 raise error.Abort(
441 raise error.Abort(
440 _('server has pull-based clones disabled'),
442 _('server has pull-based clones disabled'),
441 hint=_('remove --pull if specified or upgrade Mercurial'))
443 hint=_('remove --pull if specified or upgrade Mercurial'))
442
444
443 info, chunks = exchange.getbundlechunks(repo, 'serve',
445 info, chunks = exchange.getbundlechunks(repo, 'serve',
444 **pycompat.strkwargs(opts))
446 **pycompat.strkwargs(opts))
445 prefercompressed = info.get('prefercompressed', True)
447 prefercompressed = info.get('prefercompressed', True)
446 except error.Abort as exc:
448 except error.Abort as exc:
447 # cleanly forward Abort error to the client
449 # cleanly forward Abort error to the client
448 if not exchange.bundle2requested(opts.get('bundlecaps')):
450 if not exchange.bundle2requested(opts.get('bundlecaps')):
449 if proto.name == 'http-v1':
451 if proto.name == 'http-v1':
450 return wireprototypes.ooberror(pycompat.bytestr(exc) + '\n')
452 return wireprototypes.ooberror(pycompat.bytestr(exc) + '\n')
451 raise # cannot do better for bundle1 + ssh
453 raise # cannot do better for bundle1 + ssh
452 # bundle2 request expect a bundle2 reply
454 # bundle2 request expect a bundle2 reply
453 bundler = bundle2.bundle20(repo.ui)
455 bundler = bundle2.bundle20(repo.ui)
454 manargs = [('message', pycompat.bytestr(exc))]
456 manargs = [('message', pycompat.bytestr(exc))]
455 advargs = []
457 advargs = []
456 if exc.hint is not None:
458 if exc.hint is not None:
457 advargs.append(('hint', exc.hint))
459 advargs.append(('hint', exc.hint))
458 bundler.addpart(bundle2.bundlepart('error:abort',
460 bundler.addpart(bundle2.bundlepart('error:abort',
459 manargs, advargs))
461 manargs, advargs))
460 chunks = bundler.getchunks()
462 chunks = bundler.getchunks()
461 prefercompressed = False
463 prefercompressed = False
462
464
463 return wireprototypes.streamres(
465 return wireprototypes.streamres(
464 gen=chunks, prefer_uncompressed=not prefercompressed)
466 gen=chunks, prefer_uncompressed=not prefercompressed)
465
467
466 @wireprotocommand('heads', permission='pull')
468 @wireprotocommand('heads', permission='pull')
467 def heads(repo, proto):
469 def heads(repo, proto):
468 h = repo.heads()
470 h = repo.heads()
469 return wireprototypes.bytesresponse(wireprototypes.encodelist(h) + '\n')
471 return wireprototypes.bytesresponse(wireprototypes.encodelist(h) + '\n')
470
472
471 @wireprotocommand('hello', permission='pull')
473 @wireprotocommand('hello', permission='pull')
472 def hello(repo, proto):
474 def hello(repo, proto):
473 """Called as part of SSH handshake to obtain server info.
475 """Called as part of SSH handshake to obtain server info.
474
476
475 Returns a list of lines describing interesting things about the
477 Returns a list of lines describing interesting things about the
476 server, in an RFC822-like format.
478 server, in an RFC822-like format.
477
479
478 Currently, the only one defined is ``capabilities``, which consists of a
480 Currently, the only one defined is ``capabilities``, which consists of a
479 line of space separated tokens describing server abilities:
481 line of space separated tokens describing server abilities:
480
482
481 capabilities: <token0> <token1> <token2>
483 capabilities: <token0> <token1> <token2>
482 """
484 """
483 caps = capabilities(repo, proto).data
485 caps = capabilities(repo, proto).data
484 return wireprototypes.bytesresponse('capabilities: %s\n' % caps)
486 return wireprototypes.bytesresponse('capabilities: %s\n' % caps)
485
487
486 @wireprotocommand('listkeys', 'namespace', permission='pull')
488 @wireprotocommand('listkeys', 'namespace', permission='pull')
487 def listkeys(repo, proto, namespace):
489 def listkeys(repo, proto, namespace):
488 d = sorted(repo.listkeys(encoding.tolocal(namespace)).items())
490 d = sorted(repo.listkeys(encoding.tolocal(namespace)).items())
489 return wireprototypes.bytesresponse(pushkeymod.encodekeys(d))
491 return wireprototypes.bytesresponse(pushkeymod.encodekeys(d))
490
492
491 @wireprotocommand('lookup', 'key', permission='pull')
493 @wireprotocommand('lookup', 'key', permission='pull')
492 def lookup(repo, proto, key):
494 def lookup(repo, proto, key):
493 try:
495 try:
494 k = encoding.tolocal(key)
496 k = encoding.tolocal(key)
495 n = repo.lookup(k)
497 n = repo.lookup(k)
496 r = hex(n)
498 r = hex(n)
497 success = 1
499 success = 1
498 except Exception as inst:
500 except Exception as inst:
499 r = stringutil.forcebytestr(inst)
501 r = stringutil.forcebytestr(inst)
500 success = 0
502 success = 0
501 return wireprototypes.bytesresponse('%d %s\n' % (success, r))
503 return wireprototypes.bytesresponse('%d %s\n' % (success, r))
502
504
503 @wireprotocommand('known', 'nodes *', permission='pull')
505 @wireprotocommand('known', 'nodes *', permission='pull')
504 def known(repo, proto, nodes, others):
506 def known(repo, proto, nodes, others):
505 v = ''.join(b and '1' or '0'
507 v = ''.join(b and '1' or '0'
506 for b in repo.known(wireprototypes.decodelist(nodes)))
508 for b in repo.known(wireprototypes.decodelist(nodes)))
507 return wireprototypes.bytesresponse(v)
509 return wireprototypes.bytesresponse(v)
508
510
509 @wireprotocommand('protocaps', 'caps', permission='pull')
511 @wireprotocommand('protocaps', 'caps', permission='pull')
510 def protocaps(repo, proto, caps):
512 def protocaps(repo, proto, caps):
511 if proto.name == wireprototypes.SSHV1:
513 if proto.name == wireprototypes.SSHV1:
512 proto._protocaps = set(caps.split(' '))
514 proto._protocaps = set(caps.split(' '))
513 return wireprototypes.bytesresponse('OK')
515 return wireprototypes.bytesresponse('OK')
514
516
515 @wireprotocommand('pushkey', 'namespace key old new', permission='push')
517 @wireprotocommand('pushkey', 'namespace key old new', permission='push')
516 def pushkey(repo, proto, namespace, key, old, new):
518 def pushkey(repo, proto, namespace, key, old, new):
517 # compatibility with pre-1.8 clients which were accidentally
519 # compatibility with pre-1.8 clients which were accidentally
518 # sending raw binary nodes rather than utf-8-encoded hex
520 # sending raw binary nodes rather than utf-8-encoded hex
519 if len(new) == 20 and stringutil.escapestr(new) != new:
521 if len(new) == 20 and stringutil.escapestr(new) != new:
520 # looks like it could be a binary node
522 # looks like it could be a binary node
521 try:
523 try:
522 new.decode('utf-8')
524 new.decode('utf-8')
523 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
525 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
524 except UnicodeDecodeError:
526 except UnicodeDecodeError:
525 pass # binary, leave unmodified
527 pass # binary, leave unmodified
526 else:
528 else:
527 new = encoding.tolocal(new) # normal path
529 new = encoding.tolocal(new) # normal path
528
530
529 with proto.mayberedirectstdio() as output:
531 with proto.mayberedirectstdio() as output:
530 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
532 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
531 encoding.tolocal(old), new) or False
533 encoding.tolocal(old), new) or False
532
534
533 output = output.getvalue() if output else ''
535 output = output.getvalue() if output else ''
534 return wireprototypes.bytesresponse('%d\n%s' % (int(r), output))
536 return wireprototypes.bytesresponse('%d\n%s' % (int(r), output))
535
537
536 @wireprotocommand('stream_out', permission='pull')
538 @wireprotocommand('stream_out', permission='pull')
537 def stream(repo, proto):
539 def stream(repo, proto):
538 '''If the server supports streaming clone, it advertises the "stream"
540 '''If the server supports streaming clone, it advertises the "stream"
539 capability with a value representing the version and flags of the repo
541 capability with a value representing the version and flags of the repo
540 it is serving. Client checks to see if it understands the format.
542 it is serving. Client checks to see if it understands the format.
541 '''
543 '''
542 return wireprototypes.streamreslegacy(
544 return wireprototypes.streamreslegacy(
543 streamclone.generatev1wireproto(repo))
545 streamclone.generatev1wireproto(repo))
544
546
545 @wireprotocommand('unbundle', 'heads', permission='push')
547 @wireprotocommand('unbundle', 'heads', permission='push')
546 def unbundle(repo, proto, heads):
548 def unbundle(repo, proto, heads):
547 their_heads = wireprototypes.decodelist(heads)
549 their_heads = wireprototypes.decodelist(heads)
548
550
549 with proto.mayberedirectstdio() as output:
551 with proto.mayberedirectstdio() as output:
550 try:
552 try:
551 exchange.check_heads(repo, their_heads, 'preparing changes')
553 exchange.check_heads(repo, their_heads, 'preparing changes')
552 cleanup = lambda: None
554 cleanup = lambda: None
553 try:
555 try:
554 payload = proto.getpayload()
556 payload = proto.getpayload()
555 if repo.ui.configbool('server', 'streamunbundle'):
557 if repo.ui.configbool('server', 'streamunbundle'):
556 def cleanup():
558 def cleanup():
557 # Ensure that the full payload is consumed, so
559 # Ensure that the full payload is consumed, so
558 # that the connection doesn't contain trailing garbage.
560 # that the connection doesn't contain trailing garbage.
559 for p in payload:
561 for p in payload:
560 pass
562 pass
561 fp = util.chunkbuffer(payload)
563 fp = util.chunkbuffer(payload)
562 else:
564 else:
563 # write bundle data to temporary file as it can be big
565 # write bundle data to temporary file as it can be big
564 fp, tempname = None, None
566 fp, tempname = None, None
565 def cleanup():
567 def cleanup():
566 if fp:
568 if fp:
567 fp.close()
569 fp.close()
568 if tempname:
570 if tempname:
569 os.unlink(tempname)
571 os.unlink(tempname)
570 fd, tempname = pycompat.mkstemp(prefix='hg-unbundle-')
572 fd, tempname = pycompat.mkstemp(prefix='hg-unbundle-')
571 repo.ui.debug('redirecting incoming bundle to %s\n' %
573 repo.ui.debug('redirecting incoming bundle to %s\n' %
572 tempname)
574 tempname)
573 fp = os.fdopen(fd, pycompat.sysstr('wb+'))
575 fp = os.fdopen(fd, pycompat.sysstr('wb+'))
574 r = 0
576 r = 0
575 for p in payload:
577 for p in payload:
576 fp.write(p)
578 fp.write(p)
577 fp.seek(0)
579 fp.seek(0)
578
580
579 gen = exchange.readbundle(repo.ui, fp, None)
581 gen = exchange.readbundle(repo.ui, fp, None)
580 if (isinstance(gen, changegroupmod.cg1unpacker)
582 if (isinstance(gen, changegroupmod.cg1unpacker)
581 and not bundle1allowed(repo, 'push')):
583 and not bundle1allowed(repo, 'push')):
582 if proto.name == 'http-v1':
584 if proto.name == 'http-v1':
583 # need to special case http because stderr do not get to
585 # need to special case http because stderr do not get to
584 # the http client on failed push so we need to abuse
586 # the http client on failed push so we need to abuse
585 # some other error type to make sure the message get to
587 # some other error type to make sure the message get to
586 # the user.
588 # the user.
587 return wireprototypes.ooberror(bundle2required)
589 return wireprototypes.ooberror(bundle2required)
588 raise error.Abort(bundle2requiredmain,
590 raise error.Abort(bundle2requiredmain,
589 hint=bundle2requiredhint)
591 hint=bundle2requiredhint)
590
592
591 r = exchange.unbundle(repo, gen, their_heads, 'serve',
593 r = exchange.unbundle(repo, gen, their_heads, 'serve',
592 proto.client())
594 proto.client())
593 if util.safehasattr(r, 'addpart'):
595 if util.safehasattr(r, 'addpart'):
594 # The return looks streamable, we are in the bundle2 case
596 # The return looks streamable, we are in the bundle2 case
595 # and should return a stream.
597 # and should return a stream.
596 return wireprototypes.streamreslegacy(gen=r.getchunks())
598 return wireprototypes.streamreslegacy(gen=r.getchunks())
597 return wireprototypes.pushres(
599 return wireprototypes.pushres(
598 r, output.getvalue() if output else '')
600 r, output.getvalue() if output else '')
599
601
600 finally:
602 finally:
601 cleanup()
603 cleanup()
602
604
603 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
605 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
604 # handle non-bundle2 case first
606 # handle non-bundle2 case first
605 if not getattr(exc, 'duringunbundle2', False):
607 if not getattr(exc, 'duringunbundle2', False):
606 try:
608 try:
607 raise
609 raise
608 except error.Abort:
610 except error.Abort:
609 # The old code we moved used procutil.stderr directly.
611 # The old code we moved used procutil.stderr directly.
610 # We did not change it to minimise code change.
612 # We did not change it to minimise code change.
611 # This need to be moved to something proper.
613 # This need to be moved to something proper.
612 # Feel free to do it.
614 # Feel free to do it.
613 procutil.stderr.write("abort: %s\n" % exc)
615 procutil.stderr.write("abort: %s\n" % exc)
614 if exc.hint is not None:
616 if exc.hint is not None:
615 procutil.stderr.write("(%s)\n" % exc.hint)
617 procutil.stderr.write("(%s)\n" % exc.hint)
616 procutil.stderr.flush()
618 procutil.stderr.flush()
617 return wireprototypes.pushres(
619 return wireprototypes.pushres(
618 0, output.getvalue() if output else '')
620 0, output.getvalue() if output else '')
619 except error.PushRaced:
621 except error.PushRaced:
620 return wireprototypes.pusherr(
622 return wireprototypes.pusherr(
621 pycompat.bytestr(exc),
623 pycompat.bytestr(exc),
622 output.getvalue() if output else '')
624 output.getvalue() if output else '')
623
625
624 bundler = bundle2.bundle20(repo.ui)
626 bundler = bundle2.bundle20(repo.ui)
625 for out in getattr(exc, '_bundle2salvagedoutput', ()):
627 for out in getattr(exc, '_bundle2salvagedoutput', ()):
626 bundler.addpart(out)
628 bundler.addpart(out)
627 try:
629 try:
628 try:
630 try:
629 raise
631 raise
630 except error.PushkeyFailed as exc:
632 except error.PushkeyFailed as exc:
631 # check client caps
633 # check client caps
632 remotecaps = getattr(exc, '_replycaps', None)
634 remotecaps = getattr(exc, '_replycaps', None)
633 if (remotecaps is not None
635 if (remotecaps is not None
634 and 'pushkey' not in remotecaps.get('error', ())):
636 and 'pushkey' not in remotecaps.get('error', ())):
635 # no support remote side, fallback to Abort handler.
637 # no support remote side, fallback to Abort handler.
636 raise
638 raise
637 part = bundler.newpart('error:pushkey')
639 part = bundler.newpart('error:pushkey')
638 part.addparam('in-reply-to', exc.partid)
640 part.addparam('in-reply-to', exc.partid)
639 if exc.namespace is not None:
641 if exc.namespace is not None:
640 part.addparam('namespace', exc.namespace,
642 part.addparam('namespace', exc.namespace,
641 mandatory=False)
643 mandatory=False)
642 if exc.key is not None:
644 if exc.key is not None:
643 part.addparam('key', exc.key, mandatory=False)
645 part.addparam('key', exc.key, mandatory=False)
644 if exc.new is not None:
646 if exc.new is not None:
645 part.addparam('new', exc.new, mandatory=False)
647 part.addparam('new', exc.new, mandatory=False)
646 if exc.old is not None:
648 if exc.old is not None:
647 part.addparam('old', exc.old, mandatory=False)
649 part.addparam('old', exc.old, mandatory=False)
648 if exc.ret is not None:
650 if exc.ret is not None:
649 part.addparam('ret', exc.ret, mandatory=False)
651 part.addparam('ret', exc.ret, mandatory=False)
650 except error.BundleValueError as exc:
652 except error.BundleValueError as exc:
651 errpart = bundler.newpart('error:unsupportedcontent')
653 errpart = bundler.newpart('error:unsupportedcontent')
652 if exc.parttype is not None:
654 if exc.parttype is not None:
653 errpart.addparam('parttype', exc.parttype)
655 errpart.addparam('parttype', exc.parttype)
654 if exc.params:
656 if exc.params:
655 errpart.addparam('params', '\0'.join(exc.params))
657 errpart.addparam('params', '\0'.join(exc.params))
656 except error.Abort as exc:
658 except error.Abort as exc:
657 manargs = [('message', stringutil.forcebytestr(exc))]
659 manargs = [('message', stringutil.forcebytestr(exc))]
658 advargs = []
660 advargs = []
659 if exc.hint is not None:
661 if exc.hint is not None:
660 advargs.append(('hint', exc.hint))
662 advargs.append(('hint', exc.hint))
661 bundler.addpart(bundle2.bundlepart('error:abort',
663 bundler.addpart(bundle2.bundlepart('error:abort',
662 manargs, advargs))
664 manargs, advargs))
663 except error.PushRaced as exc:
665 except error.PushRaced as exc:
664 bundler.newpart('error:pushraced',
666 bundler.newpart('error:pushraced',
665 [('message', stringutil.forcebytestr(exc))])
667 [('message', stringutil.forcebytestr(exc))])
666 return wireprototypes.streamreslegacy(gen=bundler.getchunks())
668 return wireprototypes.streamreslegacy(gen=bundler.getchunks())
@@ -1,157 +1,157
1 #require no-chg
1 #require no-chg
2
2
3 $ hg init repo
3 $ hg init repo
4 $ cd repo
4 $ cd repo
5 $ echo foo > foo
5 $ echo foo > foo
6 $ hg ci -qAm 'add foo'
6 $ hg ci -qAm 'add foo'
7 $ echo >> foo
7 $ echo >> foo
8 $ hg ci -m 'change foo'
8 $ hg ci -m 'change foo'
9 $ hg up -qC 0
9 $ hg up -qC 0
10 $ echo bar > bar
10 $ echo bar > bar
11 $ hg ci -qAm 'add bar'
11 $ hg ci -qAm 'add bar'
12
12
13 $ hg log
13 $ hg log
14 changeset: 2:effea6de0384
14 changeset: 2:effea6de0384
15 tag: tip
15 tag: tip
16 parent: 0:bbd179dfa0a7
16 parent: 0:bbd179dfa0a7
17 user: test
17 user: test
18 date: Thu Jan 01 00:00:00 1970 +0000
18 date: Thu Jan 01 00:00:00 1970 +0000
19 summary: add bar
19 summary: add bar
20
20
21 changeset: 1:ed1b79f46b9a
21 changeset: 1:ed1b79f46b9a
22 user: test
22 user: test
23 date: Thu Jan 01 00:00:00 1970 +0000
23 date: Thu Jan 01 00:00:00 1970 +0000
24 summary: change foo
24 summary: change foo
25
25
26 changeset: 0:bbd179dfa0a7
26 changeset: 0:bbd179dfa0a7
27 user: test
27 user: test
28 date: Thu Jan 01 00:00:00 1970 +0000
28 date: Thu Jan 01 00:00:00 1970 +0000
29 summary: add foo
29 summary: add foo
30
30
31 $ cd ..
31 $ cd ..
32
32
33 Test pullbundle functionality
33 Test pullbundle functionality
34
34
35 $ cd repo
35 $ cd repo
36 $ cat <<EOF > .hg/hgrc
36 $ cat <<EOF > .hg/hgrc
37 > [server]
37 > [server]
38 > pullbundle = True
38 > pullbundle = True
39 > [extensions]
39 > [extensions]
40 > blackbox =
40 > blackbox =
41 > EOF
41 > EOF
42 $ hg bundle --base null -r 0 .hg/0.hg
42 $ hg bundle --base null -r 0 .hg/0.hg
43 1 changesets found
43 1 changesets found
44 $ hg bundle --base 0 -r 1 .hg/1.hg
44 $ hg bundle --base 0 -r 1 .hg/1.hg
45 1 changesets found
45 1 changesets found
46 $ hg bundle --base 1 -r 2 .hg/2.hg
46 $ hg bundle --base 1 -r 2 .hg/2.hg
47 1 changesets found
47 1 changesets found
48 $ cat <<EOF > .hg/pullbundles.manifest
48 $ cat <<EOF > .hg/pullbundles.manifest
49 > 2.hg heads=effea6de0384e684f44435651cb7bd70b8735bd4 bases=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
49 > 2.hg BUNDLESPEC=none-v2 heads=effea6de0384e684f44435651cb7bd70b8735bd4 bases=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
50 > 1.hg heads=ed1b79f46b9a29f5a6efa59cf12fcfca43bead5a bases=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
50 > 1.hg BUNDLESPEC=bzip2-v2 heads=ed1b79f46b9a29f5a6efa59cf12fcfca43bead5a bases=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
51 > 0.hg heads=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
51 > 0.hg BUNDLESPEC=gzip-v2 heads=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
52 > EOF
52 > EOF
53 $ hg --config blackbox.track=debug --debug serve -p $HGPORT2 -d --pid-file=../repo.pid
53 $ hg --config blackbox.track=debug --debug serve -p $HGPORT2 -d --pid-file=../repo.pid
54 listening at http://*:$HGPORT2/ (bound to $LOCALIP:$HGPORT2) (glob) (?)
54 listening at http://*:$HGPORT2/ (bound to $LOCALIP:$HGPORT2) (glob) (?)
55 $ cat ../repo.pid >> $DAEMON_PIDS
55 $ cat ../repo.pid >> $DAEMON_PIDS
56 $ cd ..
56 $ cd ..
57 $ hg clone -r 0 http://localhost:$HGPORT2/ repo.pullbundle
57 $ hg clone -r 0 http://localhost:$HGPORT2/ repo.pullbundle
58 adding changesets
58 adding changesets
59 adding manifests
59 adding manifests
60 adding file changes
60 adding file changes
61 added 1 changesets with 1 changes to 1 files
61 added 1 changesets with 1 changes to 1 files
62 new changesets bbd179dfa0a7
62 new changesets bbd179dfa0a7
63 updating to branch default
63 updating to branch default
64 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
65 $ cd repo.pullbundle
65 $ cd repo.pullbundle
66 $ hg pull -r 1
66 $ hg pull -r 1
67 pulling from http://localhost:$HGPORT2/
67 pulling from http://localhost:$HGPORT2/
68 searching for changes
68 searching for changes
69 adding changesets
69 adding changesets
70 adding manifests
70 adding manifests
71 adding file changes
71 adding file changes
72 added 1 changesets with 1 changes to 1 files
72 added 1 changesets with 1 changes to 1 files
73 new changesets ed1b79f46b9a
73 new changesets ed1b79f46b9a
74 (run 'hg update' to get a working copy)
74 (run 'hg update' to get a working copy)
75 $ hg pull -r 2
75 $ hg pull -r 2
76 pulling from http://localhost:$HGPORT2/
76 pulling from http://localhost:$HGPORT2/
77 searching for changes
77 searching for changes
78 adding changesets
78 adding changesets
79 adding manifests
79 adding manifests
80 adding file changes
80 adding file changes
81 added 1 changesets with 1 changes to 1 files (+1 heads)
81 added 1 changesets with 1 changes to 1 files (+1 heads)
82 new changesets effea6de0384
82 new changesets effea6de0384
83 (run 'hg heads' to see heads, 'hg merge' to merge)
83 (run 'hg heads' to see heads, 'hg merge' to merge)
84 $ cd ..
84 $ cd ..
85 $ killdaemons.py
85 $ killdaemons.py
86 $ grep 'sending pullbundle ' repo/.hg/blackbox.log
86 $ grep 'sending pullbundle ' repo/.hg/blackbox.log
87 * sending pullbundle "0.hg" (glob)
87 * sending pullbundle "0.hg" (glob)
88 * sending pullbundle "1.hg" (glob)
88 * sending pullbundle "1.hg" (glob)
89 * sending pullbundle "2.hg" (glob)
89 * sending pullbundle "2.hg" (glob)
90 $ rm repo/.hg/blackbox.log
90 $ rm repo/.hg/blackbox.log
91
91
92 Test pullbundle functionality for incremental pulls
92 Test pullbundle functionality for incremental pulls
93
93
94 $ cd repo
94 $ cd repo
95 $ hg --config blackbox.track=debug --debug serve -p $HGPORT2 -d --pid-file=../repo.pid
95 $ hg --config blackbox.track=debug --debug serve -p $HGPORT2 -d --pid-file=../repo.pid
96 listening at http://*:$HGPORT2/ (bound to $LOCALIP:$HGPORT2) (glob) (?)
96 listening at http://*:$HGPORT2/ (bound to $LOCALIP:$HGPORT2) (glob) (?)
97 $ cat ../repo.pid >> $DAEMON_PIDS
97 $ cat ../repo.pid >> $DAEMON_PIDS
98 $ cd ..
98 $ cd ..
99 $ hg clone http://localhost:$HGPORT2/ repo.pullbundle2
99 $ hg clone http://localhost:$HGPORT2/ repo.pullbundle2
100 requesting all changes
100 requesting all changes
101 adding changesets
101 adding changesets
102 adding manifests
102 adding manifests
103 adding file changes
103 adding file changes
104 added 1 changesets with 1 changes to 1 files
104 added 1 changesets with 1 changes to 1 files
105 adding changesets
105 adding changesets
106 adding manifests
106 adding manifests
107 adding file changes
107 adding file changes
108 added 1 changesets with 1 changes to 1 files
108 added 1 changesets with 1 changes to 1 files
109 adding changesets
109 adding changesets
110 adding manifests
110 adding manifests
111 adding file changes
111 adding file changes
112 added 1 changesets with 1 changes to 1 files (+1 heads)
112 added 1 changesets with 1 changes to 1 files (+1 heads)
113 new changesets bbd179dfa0a7:ed1b79f46b9a
113 new changesets bbd179dfa0a7:ed1b79f46b9a
114 updating to branch default
114 updating to branch default
115 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
115 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 $ killdaemons.py
116 $ killdaemons.py
117 $ grep 'sending pullbundle ' repo/.hg/blackbox.log
117 $ grep 'sending pullbundle ' repo/.hg/blackbox.log
118 * sending pullbundle "0.hg" (glob)
118 * sending pullbundle "0.hg" (glob)
119 * sending pullbundle "2.hg" (glob)
119 * sending pullbundle "2.hg" (glob)
120 * sending pullbundle "1.hg" (glob)
120 * sending pullbundle "1.hg" (glob)
121 $ rm repo/.hg/blackbox.log
121 $ rm repo/.hg/blackbox.log
122
122
123 Test recovery from misconfigured server sending no new data
123 Test recovery from misconfigured server sending no new data
124
124
125 $ cd repo
125 $ cd repo
126 $ cat <<EOF > .hg/pullbundles.manifest
126 $ cat <<EOF > .hg/pullbundles.manifest
127 > 0.hg heads=ed1b79f46b9a29f5a6efa59cf12fcfca43bead5a bases=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
127 > 0.hg heads=ed1b79f46b9a29f5a6efa59cf12fcfca43bead5a bases=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
128 > 0.hg heads=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
128 > 0.hg heads=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
129 > EOF
129 > EOF
130 $ hg --config blackbox.track=debug --debug serve -p $HGPORT2 -d --pid-file=../repo.pid
130 $ hg --config blackbox.track=debug --debug serve -p $HGPORT2 -d --pid-file=../repo.pid
131 listening at http://*:$HGPORT2/ (bound to $LOCALIP:$HGPORT2) (glob) (?)
131 listening at http://*:$HGPORT2/ (bound to $LOCALIP:$HGPORT2) (glob) (?)
132 $ cat ../repo.pid >> $DAEMON_PIDS
132 $ cat ../repo.pid >> $DAEMON_PIDS
133 $ cd ..
133 $ cd ..
134 $ hg clone -r 0 http://localhost:$HGPORT2/ repo.pullbundle3
134 $ hg clone -r 0 http://localhost:$HGPORT2/ repo.pullbundle3
135 adding changesets
135 adding changesets
136 adding manifests
136 adding manifests
137 adding file changes
137 adding file changes
138 added 1 changesets with 1 changes to 1 files
138 added 1 changesets with 1 changes to 1 files
139 new changesets bbd179dfa0a7
139 new changesets bbd179dfa0a7
140 updating to branch default
140 updating to branch default
141 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 $ cd repo.pullbundle3
142 $ cd repo.pullbundle3
143 $ hg pull -r 1
143 $ hg pull -r 1
144 pulling from http://localhost:$HGPORT2/
144 pulling from http://localhost:$HGPORT2/
145 searching for changes
145 searching for changes
146 adding changesets
146 adding changesets
147 adding manifests
147 adding manifests
148 adding file changes
148 adding file changes
149 added 0 changesets with 0 changes to 1 files
149 added 0 changesets with 0 changes to 1 files
150 abort: 00changelog.i@ed1b79f46b9a: no node!
150 abort: 00changelog.i@ed1b79f46b9a: no node!
151 [255]
151 [255]
152 $ cd ..
152 $ cd ..
153 $ killdaemons.py
153 $ killdaemons.py
154 $ grep 'sending pullbundle ' repo/.hg/blackbox.log
154 $ grep 'sending pullbundle ' repo/.hg/blackbox.log
155 * sending pullbundle "0.hg" (glob)
155 * sending pullbundle "0.hg" (glob)
156 * sending pullbundle "0.hg" (glob)
156 * sending pullbundle "0.hg" (glob)
157 $ rm repo/.hg/blackbox.log
157 $ rm repo/.hg/blackbox.log
General Comments 0
You need to be logged in to leave comments. Login now