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