##// END OF EJS Templates
bundle2: add support for a 'stream' parameter to 'getbundle'...
Boris Feld -
r35777:c24dad55 default
parent child Browse files
Show More
@@ -1,2209 +1,2222 b''
1 # exchange.py - utility to exchange data between repos.
1 # exchange.py - utility to exchange data between repos.
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 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 collections
10 import collections
11 import errno
11 import errno
12 import hashlib
12 import hashlib
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 bin,
16 bin,
17 hex,
17 hex,
18 nullid,
18 nullid,
19 )
19 )
20 from . import (
20 from . import (
21 bookmarks as bookmod,
21 bookmarks as bookmod,
22 bundle2,
22 bundle2,
23 changegroup,
23 changegroup,
24 discovery,
24 discovery,
25 error,
25 error,
26 lock as lockmod,
26 lock as lockmod,
27 logexchange,
27 logexchange,
28 obsolete,
28 obsolete,
29 phases,
29 phases,
30 pushkey,
30 pushkey,
31 pycompat,
31 pycompat,
32 scmutil,
32 scmutil,
33 sslutil,
33 sslutil,
34 streamclone,
34 streamclone,
35 url as urlmod,
35 url as urlmod,
36 util,
36 util,
37 )
37 )
38
38
39 urlerr = util.urlerr
39 urlerr = util.urlerr
40 urlreq = util.urlreq
40 urlreq = util.urlreq
41
41
42 # Maps bundle version human names to changegroup versions.
42 # Maps bundle version human names to changegroup versions.
43 _bundlespeccgversions = {'v1': '01',
43 _bundlespeccgversions = {'v1': '01',
44 'v2': '02',
44 'v2': '02',
45 'packed1': 's1',
45 'packed1': 's1',
46 'bundle2': '02', #legacy
46 'bundle2': '02', #legacy
47 }
47 }
48
48
49 # Compression engines allowed in version 1. THIS SHOULD NEVER CHANGE.
49 # Compression engines allowed in version 1. THIS SHOULD NEVER CHANGE.
50 _bundlespecv1compengines = {'gzip', 'bzip2', 'none'}
50 _bundlespecv1compengines = {'gzip', 'bzip2', 'none'}
51
51
52 def parsebundlespec(repo, spec, strict=True, externalnames=False):
52 def parsebundlespec(repo, spec, strict=True, externalnames=False):
53 """Parse a bundle string specification into parts.
53 """Parse a bundle string specification into parts.
54
54
55 Bundle specifications denote a well-defined bundle/exchange format.
55 Bundle specifications denote a well-defined bundle/exchange format.
56 The content of a given specification should not change over time in
56 The content of a given specification should not change over time in
57 order to ensure that bundles produced by a newer version of Mercurial are
57 order to ensure that bundles produced by a newer version of Mercurial are
58 readable from an older version.
58 readable from an older version.
59
59
60 The string currently has the form:
60 The string currently has the form:
61
61
62 <compression>-<type>[;<parameter0>[;<parameter1>]]
62 <compression>-<type>[;<parameter0>[;<parameter1>]]
63
63
64 Where <compression> is one of the supported compression formats
64 Where <compression> is one of the supported compression formats
65 and <type> is (currently) a version string. A ";" can follow the type and
65 and <type> is (currently) a version string. A ";" can follow the type and
66 all text afterwards is interpreted as URI encoded, ";" delimited key=value
66 all text afterwards is interpreted as URI encoded, ";" delimited key=value
67 pairs.
67 pairs.
68
68
69 If ``strict`` is True (the default) <compression> is required. Otherwise,
69 If ``strict`` is True (the default) <compression> is required. Otherwise,
70 it is optional.
70 it is optional.
71
71
72 If ``externalnames`` is False (the default), the human-centric names will
72 If ``externalnames`` is False (the default), the human-centric names will
73 be converted to their internal representation.
73 be converted to their internal representation.
74
74
75 Returns a 3-tuple of (compression, version, parameters). Compression will
75 Returns a 3-tuple of (compression, version, parameters). Compression will
76 be ``None`` if not in strict mode and a compression isn't defined.
76 be ``None`` if not in strict mode and a compression isn't defined.
77
77
78 An ``InvalidBundleSpecification`` is raised when the specification is
78 An ``InvalidBundleSpecification`` is raised when the specification is
79 not syntactically well formed.
79 not syntactically well formed.
80
80
81 An ``UnsupportedBundleSpecification`` is raised when the compression or
81 An ``UnsupportedBundleSpecification`` is raised when the compression or
82 bundle type/version is not recognized.
82 bundle type/version is not recognized.
83
83
84 Note: this function will likely eventually return a more complex data
84 Note: this function will likely eventually return a more complex data
85 structure, including bundle2 part information.
85 structure, including bundle2 part information.
86 """
86 """
87 def parseparams(s):
87 def parseparams(s):
88 if ';' not in s:
88 if ';' not in s:
89 return s, {}
89 return s, {}
90
90
91 params = {}
91 params = {}
92 version, paramstr = s.split(';', 1)
92 version, paramstr = s.split(';', 1)
93
93
94 for p in paramstr.split(';'):
94 for p in paramstr.split(';'):
95 if '=' not in p:
95 if '=' not in p:
96 raise error.InvalidBundleSpecification(
96 raise error.InvalidBundleSpecification(
97 _('invalid bundle specification: '
97 _('invalid bundle specification: '
98 'missing "=" in parameter: %s') % p)
98 'missing "=" in parameter: %s') % p)
99
99
100 key, value = p.split('=', 1)
100 key, value = p.split('=', 1)
101 key = urlreq.unquote(key)
101 key = urlreq.unquote(key)
102 value = urlreq.unquote(value)
102 value = urlreq.unquote(value)
103 params[key] = value
103 params[key] = value
104
104
105 return version, params
105 return version, params
106
106
107
107
108 if strict and '-' not in spec:
108 if strict and '-' not in spec:
109 raise error.InvalidBundleSpecification(
109 raise error.InvalidBundleSpecification(
110 _('invalid bundle specification; '
110 _('invalid bundle specification; '
111 'must be prefixed with compression: %s') % spec)
111 'must be prefixed with compression: %s') % spec)
112
112
113 if '-' in spec:
113 if '-' in spec:
114 compression, version = spec.split('-', 1)
114 compression, version = spec.split('-', 1)
115
115
116 if compression not in util.compengines.supportedbundlenames:
116 if compression not in util.compengines.supportedbundlenames:
117 raise error.UnsupportedBundleSpecification(
117 raise error.UnsupportedBundleSpecification(
118 _('%s compression is not supported') % compression)
118 _('%s compression is not supported') % compression)
119
119
120 version, params = parseparams(version)
120 version, params = parseparams(version)
121
121
122 if version not in _bundlespeccgversions:
122 if version not in _bundlespeccgversions:
123 raise error.UnsupportedBundleSpecification(
123 raise error.UnsupportedBundleSpecification(
124 _('%s is not a recognized bundle version') % version)
124 _('%s is not a recognized bundle version') % version)
125 else:
125 else:
126 # Value could be just the compression or just the version, in which
126 # Value could be just the compression or just the version, in which
127 # case some defaults are assumed (but only when not in strict mode).
127 # case some defaults are assumed (but only when not in strict mode).
128 assert not strict
128 assert not strict
129
129
130 spec, params = parseparams(spec)
130 spec, params = parseparams(spec)
131
131
132 if spec in util.compengines.supportedbundlenames:
132 if spec in util.compengines.supportedbundlenames:
133 compression = spec
133 compression = spec
134 version = 'v1'
134 version = 'v1'
135 # Generaldelta repos require v2.
135 # Generaldelta repos require v2.
136 if 'generaldelta' in repo.requirements:
136 if 'generaldelta' in repo.requirements:
137 version = 'v2'
137 version = 'v2'
138 # Modern compression engines require v2.
138 # Modern compression engines require v2.
139 if compression not in _bundlespecv1compengines:
139 if compression not in _bundlespecv1compengines:
140 version = 'v2'
140 version = 'v2'
141 elif spec in _bundlespeccgversions:
141 elif spec in _bundlespeccgversions:
142 if spec == 'packed1':
142 if spec == 'packed1':
143 compression = 'none'
143 compression = 'none'
144 else:
144 else:
145 compression = 'bzip2'
145 compression = 'bzip2'
146 version = spec
146 version = spec
147 else:
147 else:
148 raise error.UnsupportedBundleSpecification(
148 raise error.UnsupportedBundleSpecification(
149 _('%s is not a recognized bundle specification') % spec)
149 _('%s is not a recognized bundle specification') % spec)
150
150
151 # Bundle version 1 only supports a known set of compression engines.
151 # Bundle version 1 only supports a known set of compression engines.
152 if version == 'v1' and compression not in _bundlespecv1compengines:
152 if version == 'v1' and compression not in _bundlespecv1compengines:
153 raise error.UnsupportedBundleSpecification(
153 raise error.UnsupportedBundleSpecification(
154 _('compression engine %s is not supported on v1 bundles') %
154 _('compression engine %s is not supported on v1 bundles') %
155 compression)
155 compression)
156
156
157 # The specification for packed1 can optionally declare the data formats
157 # The specification for packed1 can optionally declare the data formats
158 # required to apply it. If we see this metadata, compare against what the
158 # required to apply it. If we see this metadata, compare against what the
159 # repo supports and error if the bundle isn't compatible.
159 # repo supports and error if the bundle isn't compatible.
160 if version == 'packed1' and 'requirements' in params:
160 if version == 'packed1' and 'requirements' in params:
161 requirements = set(params['requirements'].split(','))
161 requirements = set(params['requirements'].split(','))
162 missingreqs = requirements - repo.supportedformats
162 missingreqs = requirements - repo.supportedformats
163 if missingreqs:
163 if missingreqs:
164 raise error.UnsupportedBundleSpecification(
164 raise error.UnsupportedBundleSpecification(
165 _('missing support for repository features: %s') %
165 _('missing support for repository features: %s') %
166 ', '.join(sorted(missingreqs)))
166 ', '.join(sorted(missingreqs)))
167
167
168 if not externalnames:
168 if not externalnames:
169 engine = util.compengines.forbundlename(compression)
169 engine = util.compengines.forbundlename(compression)
170 compression = engine.bundletype()[1]
170 compression = engine.bundletype()[1]
171 version = _bundlespeccgversions[version]
171 version = _bundlespeccgversions[version]
172 return compression, version, params
172 return compression, version, params
173
173
174 def readbundle(ui, fh, fname, vfs=None):
174 def readbundle(ui, fh, fname, vfs=None):
175 header = changegroup.readexactly(fh, 4)
175 header = changegroup.readexactly(fh, 4)
176
176
177 alg = None
177 alg = None
178 if not fname:
178 if not fname:
179 fname = "stream"
179 fname = "stream"
180 if not header.startswith('HG') and header.startswith('\0'):
180 if not header.startswith('HG') and header.startswith('\0'):
181 fh = changegroup.headerlessfixup(fh, header)
181 fh = changegroup.headerlessfixup(fh, header)
182 header = "HG10"
182 header = "HG10"
183 alg = 'UN'
183 alg = 'UN'
184 elif vfs:
184 elif vfs:
185 fname = vfs.join(fname)
185 fname = vfs.join(fname)
186
186
187 magic, version = header[0:2], header[2:4]
187 magic, version = header[0:2], header[2:4]
188
188
189 if magic != 'HG':
189 if magic != 'HG':
190 raise error.Abort(_('%s: not a Mercurial bundle') % fname)
190 raise error.Abort(_('%s: not a Mercurial bundle') % fname)
191 if version == '10':
191 if version == '10':
192 if alg is None:
192 if alg is None:
193 alg = changegroup.readexactly(fh, 2)
193 alg = changegroup.readexactly(fh, 2)
194 return changegroup.cg1unpacker(fh, alg)
194 return changegroup.cg1unpacker(fh, alg)
195 elif version.startswith('2'):
195 elif version.startswith('2'):
196 return bundle2.getunbundler(ui, fh, magicstring=magic + version)
196 return bundle2.getunbundler(ui, fh, magicstring=magic + version)
197 elif version == 'S1':
197 elif version == 'S1':
198 return streamclone.streamcloneapplier(fh)
198 return streamclone.streamcloneapplier(fh)
199 else:
199 else:
200 raise error.Abort(_('%s: unknown bundle version %s') % (fname, version))
200 raise error.Abort(_('%s: unknown bundle version %s') % (fname, version))
201
201
202 def getbundlespec(ui, fh):
202 def getbundlespec(ui, fh):
203 """Infer the bundlespec from a bundle file handle.
203 """Infer the bundlespec from a bundle file handle.
204
204
205 The input file handle is seeked and the original seek position is not
205 The input file handle is seeked and the original seek position is not
206 restored.
206 restored.
207 """
207 """
208 def speccompression(alg):
208 def speccompression(alg):
209 try:
209 try:
210 return util.compengines.forbundletype(alg).bundletype()[0]
210 return util.compengines.forbundletype(alg).bundletype()[0]
211 except KeyError:
211 except KeyError:
212 return None
212 return None
213
213
214 b = readbundle(ui, fh, None)
214 b = readbundle(ui, fh, None)
215 if isinstance(b, changegroup.cg1unpacker):
215 if isinstance(b, changegroup.cg1unpacker):
216 alg = b._type
216 alg = b._type
217 if alg == '_truncatedBZ':
217 if alg == '_truncatedBZ':
218 alg = 'BZ'
218 alg = 'BZ'
219 comp = speccompression(alg)
219 comp = speccompression(alg)
220 if not comp:
220 if not comp:
221 raise error.Abort(_('unknown compression algorithm: %s') % alg)
221 raise error.Abort(_('unknown compression algorithm: %s') % alg)
222 return '%s-v1' % comp
222 return '%s-v1' % comp
223 elif isinstance(b, bundle2.unbundle20):
223 elif isinstance(b, bundle2.unbundle20):
224 if 'Compression' in b.params:
224 if 'Compression' in b.params:
225 comp = speccompression(b.params['Compression'])
225 comp = speccompression(b.params['Compression'])
226 if not comp:
226 if not comp:
227 raise error.Abort(_('unknown compression algorithm: %s') % comp)
227 raise error.Abort(_('unknown compression algorithm: %s') % comp)
228 else:
228 else:
229 comp = 'none'
229 comp = 'none'
230
230
231 version = None
231 version = None
232 for part in b.iterparts():
232 for part in b.iterparts():
233 if part.type == 'changegroup':
233 if part.type == 'changegroup':
234 version = part.params['version']
234 version = part.params['version']
235 if version in ('01', '02'):
235 if version in ('01', '02'):
236 version = 'v2'
236 version = 'v2'
237 else:
237 else:
238 raise error.Abort(_('changegroup version %s does not have '
238 raise error.Abort(_('changegroup version %s does not have '
239 'a known bundlespec') % version,
239 'a known bundlespec') % version,
240 hint=_('try upgrading your Mercurial '
240 hint=_('try upgrading your Mercurial '
241 'client'))
241 'client'))
242
242
243 if not version:
243 if not version:
244 raise error.Abort(_('could not identify changegroup version in '
244 raise error.Abort(_('could not identify changegroup version in '
245 'bundle'))
245 'bundle'))
246
246
247 return '%s-%s' % (comp, version)
247 return '%s-%s' % (comp, version)
248 elif isinstance(b, streamclone.streamcloneapplier):
248 elif isinstance(b, streamclone.streamcloneapplier):
249 requirements = streamclone.readbundle1header(fh)[2]
249 requirements = streamclone.readbundle1header(fh)[2]
250 params = 'requirements=%s' % ','.join(sorted(requirements))
250 params = 'requirements=%s' % ','.join(sorted(requirements))
251 return 'none-packed1;%s' % urlreq.quote(params)
251 return 'none-packed1;%s' % urlreq.quote(params)
252 else:
252 else:
253 raise error.Abort(_('unknown bundle type: %s') % b)
253 raise error.Abort(_('unknown bundle type: %s') % b)
254
254
255 def _computeoutgoing(repo, heads, common):
255 def _computeoutgoing(repo, heads, common):
256 """Computes which revs are outgoing given a set of common
256 """Computes which revs are outgoing given a set of common
257 and a set of heads.
257 and a set of heads.
258
258
259 This is a separate function so extensions can have access to
259 This is a separate function so extensions can have access to
260 the logic.
260 the logic.
261
261
262 Returns a discovery.outgoing object.
262 Returns a discovery.outgoing object.
263 """
263 """
264 cl = repo.changelog
264 cl = repo.changelog
265 if common:
265 if common:
266 hasnode = cl.hasnode
266 hasnode = cl.hasnode
267 common = [n for n in common if hasnode(n)]
267 common = [n for n in common if hasnode(n)]
268 else:
268 else:
269 common = [nullid]
269 common = [nullid]
270 if not heads:
270 if not heads:
271 heads = cl.heads()
271 heads = cl.heads()
272 return discovery.outgoing(repo, common, heads)
272 return discovery.outgoing(repo, common, heads)
273
273
274 def _forcebundle1(op):
274 def _forcebundle1(op):
275 """return true if a pull/push must use bundle1
275 """return true if a pull/push must use bundle1
276
276
277 This function is used to allow testing of the older bundle version"""
277 This function is used to allow testing of the older bundle version"""
278 ui = op.repo.ui
278 ui = op.repo.ui
279 forcebundle1 = False
279 forcebundle1 = False
280 # The goal is this config is to allow developer to choose the bundle
280 # The goal is this config is to allow developer to choose the bundle
281 # version used during exchanged. This is especially handy during test.
281 # version used during exchanged. This is especially handy during test.
282 # Value is a list of bundle version to be picked from, highest version
282 # Value is a list of bundle version to be picked from, highest version
283 # should be used.
283 # should be used.
284 #
284 #
285 # developer config: devel.legacy.exchange
285 # developer config: devel.legacy.exchange
286 exchange = ui.configlist('devel', 'legacy.exchange')
286 exchange = ui.configlist('devel', 'legacy.exchange')
287 forcebundle1 = 'bundle2' not in exchange and 'bundle1' in exchange
287 forcebundle1 = 'bundle2' not in exchange and 'bundle1' in exchange
288 return forcebundle1 or not op.remote.capable('bundle2')
288 return forcebundle1 or not op.remote.capable('bundle2')
289
289
290 class pushoperation(object):
290 class pushoperation(object):
291 """A object that represent a single push operation
291 """A object that represent a single push operation
292
292
293 Its purpose is to carry push related state and very common operations.
293 Its purpose is to carry push related state and very common operations.
294
294
295 A new pushoperation should be created at the beginning of each push and
295 A new pushoperation should be created at the beginning of each push and
296 discarded afterward.
296 discarded afterward.
297 """
297 """
298
298
299 def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
299 def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
300 bookmarks=(), pushvars=None):
300 bookmarks=(), pushvars=None):
301 # repo we push from
301 # repo we push from
302 self.repo = repo
302 self.repo = repo
303 self.ui = repo.ui
303 self.ui = repo.ui
304 # repo we push to
304 # repo we push to
305 self.remote = remote
305 self.remote = remote
306 # force option provided
306 # force option provided
307 self.force = force
307 self.force = force
308 # revs to be pushed (None is "all")
308 # revs to be pushed (None is "all")
309 self.revs = revs
309 self.revs = revs
310 # bookmark explicitly pushed
310 # bookmark explicitly pushed
311 self.bookmarks = bookmarks
311 self.bookmarks = bookmarks
312 # allow push of new branch
312 # allow push of new branch
313 self.newbranch = newbranch
313 self.newbranch = newbranch
314 # step already performed
314 # step already performed
315 # (used to check what steps have been already performed through bundle2)
315 # (used to check what steps have been already performed through bundle2)
316 self.stepsdone = set()
316 self.stepsdone = set()
317 # Integer version of the changegroup push result
317 # Integer version of the changegroup push result
318 # - None means nothing to push
318 # - None means nothing to push
319 # - 0 means HTTP error
319 # - 0 means HTTP error
320 # - 1 means we pushed and remote head count is unchanged *or*
320 # - 1 means we pushed and remote head count is unchanged *or*
321 # we have outgoing changesets but refused to push
321 # we have outgoing changesets but refused to push
322 # - other values as described by addchangegroup()
322 # - other values as described by addchangegroup()
323 self.cgresult = None
323 self.cgresult = None
324 # Boolean value for the bookmark push
324 # Boolean value for the bookmark push
325 self.bkresult = None
325 self.bkresult = None
326 # discover.outgoing object (contains common and outgoing data)
326 # discover.outgoing object (contains common and outgoing data)
327 self.outgoing = None
327 self.outgoing = None
328 # all remote topological heads before the push
328 # all remote topological heads before the push
329 self.remoteheads = None
329 self.remoteheads = None
330 # Details of the remote branch pre and post push
330 # Details of the remote branch pre and post push
331 #
331 #
332 # mapping: {'branch': ([remoteheads],
332 # mapping: {'branch': ([remoteheads],
333 # [newheads],
333 # [newheads],
334 # [unsyncedheads],
334 # [unsyncedheads],
335 # [discardedheads])}
335 # [discardedheads])}
336 # - branch: the branch name
336 # - branch: the branch name
337 # - remoteheads: the list of remote heads known locally
337 # - remoteheads: the list of remote heads known locally
338 # None if the branch is new
338 # None if the branch is new
339 # - newheads: the new remote heads (known locally) with outgoing pushed
339 # - newheads: the new remote heads (known locally) with outgoing pushed
340 # - unsyncedheads: the list of remote heads unknown locally.
340 # - unsyncedheads: the list of remote heads unknown locally.
341 # - discardedheads: the list of remote heads made obsolete by the push
341 # - discardedheads: the list of remote heads made obsolete by the push
342 self.pushbranchmap = None
342 self.pushbranchmap = None
343 # testable as a boolean indicating if any nodes are missing locally.
343 # testable as a boolean indicating if any nodes are missing locally.
344 self.incoming = None
344 self.incoming = None
345 # summary of the remote phase situation
345 # summary of the remote phase situation
346 self.remotephases = None
346 self.remotephases = None
347 # phases changes that must be pushed along side the changesets
347 # phases changes that must be pushed along side the changesets
348 self.outdatedphases = None
348 self.outdatedphases = None
349 # phases changes that must be pushed if changeset push fails
349 # phases changes that must be pushed if changeset push fails
350 self.fallbackoutdatedphases = None
350 self.fallbackoutdatedphases = None
351 # outgoing obsmarkers
351 # outgoing obsmarkers
352 self.outobsmarkers = set()
352 self.outobsmarkers = set()
353 # outgoing bookmarks
353 # outgoing bookmarks
354 self.outbookmarks = []
354 self.outbookmarks = []
355 # transaction manager
355 # transaction manager
356 self.trmanager = None
356 self.trmanager = None
357 # map { pushkey partid -> callback handling failure}
357 # map { pushkey partid -> callback handling failure}
358 # used to handle exception from mandatory pushkey part failure
358 # used to handle exception from mandatory pushkey part failure
359 self.pkfailcb = {}
359 self.pkfailcb = {}
360 # an iterable of pushvars or None
360 # an iterable of pushvars or None
361 self.pushvars = pushvars
361 self.pushvars = pushvars
362
362
363 @util.propertycache
363 @util.propertycache
364 def futureheads(self):
364 def futureheads(self):
365 """future remote heads if the changeset push succeeds"""
365 """future remote heads if the changeset push succeeds"""
366 return self.outgoing.missingheads
366 return self.outgoing.missingheads
367
367
368 @util.propertycache
368 @util.propertycache
369 def fallbackheads(self):
369 def fallbackheads(self):
370 """future remote heads if the changeset push fails"""
370 """future remote heads if the changeset push fails"""
371 if self.revs is None:
371 if self.revs is None:
372 # not target to push, all common are relevant
372 # not target to push, all common are relevant
373 return self.outgoing.commonheads
373 return self.outgoing.commonheads
374 unfi = self.repo.unfiltered()
374 unfi = self.repo.unfiltered()
375 # I want cheads = heads(::missingheads and ::commonheads)
375 # I want cheads = heads(::missingheads and ::commonheads)
376 # (missingheads is revs with secret changeset filtered out)
376 # (missingheads is revs with secret changeset filtered out)
377 #
377 #
378 # This can be expressed as:
378 # This can be expressed as:
379 # cheads = ( (missingheads and ::commonheads)
379 # cheads = ( (missingheads and ::commonheads)
380 # + (commonheads and ::missingheads))"
380 # + (commonheads and ::missingheads))"
381 # )
381 # )
382 #
382 #
383 # while trying to push we already computed the following:
383 # while trying to push we already computed the following:
384 # common = (::commonheads)
384 # common = (::commonheads)
385 # missing = ((commonheads::missingheads) - commonheads)
385 # missing = ((commonheads::missingheads) - commonheads)
386 #
386 #
387 # We can pick:
387 # We can pick:
388 # * missingheads part of common (::commonheads)
388 # * missingheads part of common (::commonheads)
389 common = self.outgoing.common
389 common = self.outgoing.common
390 nm = self.repo.changelog.nodemap
390 nm = self.repo.changelog.nodemap
391 cheads = [node for node in self.revs if nm[node] in common]
391 cheads = [node for node in self.revs if nm[node] in common]
392 # and
392 # and
393 # * commonheads parents on missing
393 # * commonheads parents on missing
394 revset = unfi.set('%ln and parents(roots(%ln))',
394 revset = unfi.set('%ln and parents(roots(%ln))',
395 self.outgoing.commonheads,
395 self.outgoing.commonheads,
396 self.outgoing.missing)
396 self.outgoing.missing)
397 cheads.extend(c.node() for c in revset)
397 cheads.extend(c.node() for c in revset)
398 return cheads
398 return cheads
399
399
400 @property
400 @property
401 def commonheads(self):
401 def commonheads(self):
402 """set of all common heads after changeset bundle push"""
402 """set of all common heads after changeset bundle push"""
403 if self.cgresult:
403 if self.cgresult:
404 return self.futureheads
404 return self.futureheads
405 else:
405 else:
406 return self.fallbackheads
406 return self.fallbackheads
407
407
408 # mapping of message used when pushing bookmark
408 # mapping of message used when pushing bookmark
409 bookmsgmap = {'update': (_("updating bookmark %s\n"),
409 bookmsgmap = {'update': (_("updating bookmark %s\n"),
410 _('updating bookmark %s failed!\n')),
410 _('updating bookmark %s failed!\n')),
411 'export': (_("exporting bookmark %s\n"),
411 'export': (_("exporting bookmark %s\n"),
412 _('exporting bookmark %s failed!\n')),
412 _('exporting bookmark %s failed!\n')),
413 'delete': (_("deleting remote bookmark %s\n"),
413 'delete': (_("deleting remote bookmark %s\n"),
414 _('deleting remote bookmark %s failed!\n')),
414 _('deleting remote bookmark %s failed!\n')),
415 }
415 }
416
416
417
417
418 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=(),
418 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=(),
419 opargs=None):
419 opargs=None):
420 '''Push outgoing changesets (limited by revs) from a local
420 '''Push outgoing changesets (limited by revs) from a local
421 repository to remote. Return an integer:
421 repository to remote. Return an integer:
422 - None means nothing to push
422 - None means nothing to push
423 - 0 means HTTP error
423 - 0 means HTTP error
424 - 1 means we pushed and remote head count is unchanged *or*
424 - 1 means we pushed and remote head count is unchanged *or*
425 we have outgoing changesets but refused to push
425 we have outgoing changesets but refused to push
426 - other values as described by addchangegroup()
426 - other values as described by addchangegroup()
427 '''
427 '''
428 if opargs is None:
428 if opargs is None:
429 opargs = {}
429 opargs = {}
430 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
430 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
431 **pycompat.strkwargs(opargs))
431 **pycompat.strkwargs(opargs))
432 if pushop.remote.local():
432 if pushop.remote.local():
433 missing = (set(pushop.repo.requirements)
433 missing = (set(pushop.repo.requirements)
434 - pushop.remote.local().supported)
434 - pushop.remote.local().supported)
435 if missing:
435 if missing:
436 msg = _("required features are not"
436 msg = _("required features are not"
437 " supported in the destination:"
437 " supported in the destination:"
438 " %s") % (', '.join(sorted(missing)))
438 " %s") % (', '.join(sorted(missing)))
439 raise error.Abort(msg)
439 raise error.Abort(msg)
440
440
441 if not pushop.remote.canpush():
441 if not pushop.remote.canpush():
442 raise error.Abort(_("destination does not support push"))
442 raise error.Abort(_("destination does not support push"))
443
443
444 if not pushop.remote.capable('unbundle'):
444 if not pushop.remote.capable('unbundle'):
445 raise error.Abort(_('cannot push: destination does not support the '
445 raise error.Abort(_('cannot push: destination does not support the '
446 'unbundle wire protocol command'))
446 'unbundle wire protocol command'))
447
447
448 # get lock as we might write phase data
448 # get lock as we might write phase data
449 wlock = lock = None
449 wlock = lock = None
450 try:
450 try:
451 # bundle2 push may receive a reply bundle touching bookmarks or other
451 # bundle2 push may receive a reply bundle touching bookmarks or other
452 # things requiring the wlock. Take it now to ensure proper ordering.
452 # things requiring the wlock. Take it now to ensure proper ordering.
453 maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
453 maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
454 if (not _forcebundle1(pushop)) and maypushback:
454 if (not _forcebundle1(pushop)) and maypushback:
455 wlock = pushop.repo.wlock()
455 wlock = pushop.repo.wlock()
456 lock = pushop.repo.lock()
456 lock = pushop.repo.lock()
457 pushop.trmanager = transactionmanager(pushop.repo,
457 pushop.trmanager = transactionmanager(pushop.repo,
458 'push-response',
458 'push-response',
459 pushop.remote.url())
459 pushop.remote.url())
460 except IOError as err:
460 except IOError as err:
461 if err.errno != errno.EACCES:
461 if err.errno != errno.EACCES:
462 raise
462 raise
463 # source repo cannot be locked.
463 # source repo cannot be locked.
464 # We do not abort the push, but just disable the local phase
464 # We do not abort the push, but just disable the local phase
465 # synchronisation.
465 # synchronisation.
466 msg = 'cannot lock source repository: %s\n' % err
466 msg = 'cannot lock source repository: %s\n' % err
467 pushop.ui.debug(msg)
467 pushop.ui.debug(msg)
468
468
469 with wlock or util.nullcontextmanager(), \
469 with wlock or util.nullcontextmanager(), \
470 lock or util.nullcontextmanager(), \
470 lock or util.nullcontextmanager(), \
471 pushop.trmanager or util.nullcontextmanager():
471 pushop.trmanager or util.nullcontextmanager():
472 pushop.repo.checkpush(pushop)
472 pushop.repo.checkpush(pushop)
473 _pushdiscovery(pushop)
473 _pushdiscovery(pushop)
474 if not _forcebundle1(pushop):
474 if not _forcebundle1(pushop):
475 _pushbundle2(pushop)
475 _pushbundle2(pushop)
476 _pushchangeset(pushop)
476 _pushchangeset(pushop)
477 _pushsyncphase(pushop)
477 _pushsyncphase(pushop)
478 _pushobsolete(pushop)
478 _pushobsolete(pushop)
479 _pushbookmark(pushop)
479 _pushbookmark(pushop)
480
480
481 return pushop
481 return pushop
482
482
483 # list of steps to perform discovery before push
483 # list of steps to perform discovery before push
484 pushdiscoveryorder = []
484 pushdiscoveryorder = []
485
485
486 # Mapping between step name and function
486 # Mapping between step name and function
487 #
487 #
488 # This exists to help extensions wrap steps if necessary
488 # This exists to help extensions wrap steps if necessary
489 pushdiscoverymapping = {}
489 pushdiscoverymapping = {}
490
490
491 def pushdiscovery(stepname):
491 def pushdiscovery(stepname):
492 """decorator for function performing discovery before push
492 """decorator for function performing discovery before push
493
493
494 The function is added to the step -> function mapping and appended to the
494 The function is added to the step -> function mapping and appended to the
495 list of steps. Beware that decorated function will be added in order (this
495 list of steps. Beware that decorated function will be added in order (this
496 may matter).
496 may matter).
497
497
498 You can only use this decorator for a new step, if you want to wrap a step
498 You can only use this decorator for a new step, if you want to wrap a step
499 from an extension, change the pushdiscovery dictionary directly."""
499 from an extension, change the pushdiscovery dictionary directly."""
500 def dec(func):
500 def dec(func):
501 assert stepname not in pushdiscoverymapping
501 assert stepname not in pushdiscoverymapping
502 pushdiscoverymapping[stepname] = func
502 pushdiscoverymapping[stepname] = func
503 pushdiscoveryorder.append(stepname)
503 pushdiscoveryorder.append(stepname)
504 return func
504 return func
505 return dec
505 return dec
506
506
507 def _pushdiscovery(pushop):
507 def _pushdiscovery(pushop):
508 """Run all discovery steps"""
508 """Run all discovery steps"""
509 for stepname in pushdiscoveryorder:
509 for stepname in pushdiscoveryorder:
510 step = pushdiscoverymapping[stepname]
510 step = pushdiscoverymapping[stepname]
511 step(pushop)
511 step(pushop)
512
512
513 @pushdiscovery('changeset')
513 @pushdiscovery('changeset')
514 def _pushdiscoverychangeset(pushop):
514 def _pushdiscoverychangeset(pushop):
515 """discover the changeset that need to be pushed"""
515 """discover the changeset that need to be pushed"""
516 fci = discovery.findcommonincoming
516 fci = discovery.findcommonincoming
517 if pushop.revs:
517 if pushop.revs:
518 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force,
518 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force,
519 ancestorsof=pushop.revs)
519 ancestorsof=pushop.revs)
520 else:
520 else:
521 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force)
521 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force)
522 common, inc, remoteheads = commoninc
522 common, inc, remoteheads = commoninc
523 fco = discovery.findcommonoutgoing
523 fco = discovery.findcommonoutgoing
524 outgoing = fco(pushop.repo, pushop.remote, onlyheads=pushop.revs,
524 outgoing = fco(pushop.repo, pushop.remote, onlyheads=pushop.revs,
525 commoninc=commoninc, force=pushop.force)
525 commoninc=commoninc, force=pushop.force)
526 pushop.outgoing = outgoing
526 pushop.outgoing = outgoing
527 pushop.remoteheads = remoteheads
527 pushop.remoteheads = remoteheads
528 pushop.incoming = inc
528 pushop.incoming = inc
529
529
530 @pushdiscovery('phase')
530 @pushdiscovery('phase')
531 def _pushdiscoveryphase(pushop):
531 def _pushdiscoveryphase(pushop):
532 """discover the phase that needs to be pushed
532 """discover the phase that needs to be pushed
533
533
534 (computed for both success and failure case for changesets push)"""
534 (computed for both success and failure case for changesets push)"""
535 outgoing = pushop.outgoing
535 outgoing = pushop.outgoing
536 unfi = pushop.repo.unfiltered()
536 unfi = pushop.repo.unfiltered()
537 remotephases = pushop.remote.listkeys('phases')
537 remotephases = pushop.remote.listkeys('phases')
538 if (pushop.ui.configbool('ui', '_usedassubrepo')
538 if (pushop.ui.configbool('ui', '_usedassubrepo')
539 and remotephases # server supports phases
539 and remotephases # server supports phases
540 and not pushop.outgoing.missing # no changesets to be pushed
540 and not pushop.outgoing.missing # no changesets to be pushed
541 and remotephases.get('publishing', False)):
541 and remotephases.get('publishing', False)):
542 # When:
542 # When:
543 # - this is a subrepo push
543 # - this is a subrepo push
544 # - and remote support phase
544 # - and remote support phase
545 # - and no changeset are to be pushed
545 # - and no changeset are to be pushed
546 # - and remote is publishing
546 # - and remote is publishing
547 # We may be in issue 3781 case!
547 # We may be in issue 3781 case!
548 # We drop the possible phase synchronisation done by
548 # We drop the possible phase synchronisation done by
549 # courtesy to publish changesets possibly locally draft
549 # courtesy to publish changesets possibly locally draft
550 # on the remote.
550 # on the remote.
551 pushop.outdatedphases = []
551 pushop.outdatedphases = []
552 pushop.fallbackoutdatedphases = []
552 pushop.fallbackoutdatedphases = []
553 return
553 return
554
554
555 pushop.remotephases = phases.remotephasessummary(pushop.repo,
555 pushop.remotephases = phases.remotephasessummary(pushop.repo,
556 pushop.fallbackheads,
556 pushop.fallbackheads,
557 remotephases)
557 remotephases)
558 droots = pushop.remotephases.draftroots
558 droots = pushop.remotephases.draftroots
559
559
560 extracond = ''
560 extracond = ''
561 if not pushop.remotephases.publishing:
561 if not pushop.remotephases.publishing:
562 extracond = ' and public()'
562 extracond = ' and public()'
563 revset = 'heads((%%ln::%%ln) %s)' % extracond
563 revset = 'heads((%%ln::%%ln) %s)' % extracond
564 # Get the list of all revs draft on remote by public here.
564 # Get the list of all revs draft on remote by public here.
565 # XXX Beware that revset break if droots is not strictly
565 # XXX Beware that revset break if droots is not strictly
566 # XXX root we may want to ensure it is but it is costly
566 # XXX root we may want to ensure it is but it is costly
567 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
567 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
568 if not outgoing.missing:
568 if not outgoing.missing:
569 future = fallback
569 future = fallback
570 else:
570 else:
571 # adds changeset we are going to push as draft
571 # adds changeset we are going to push as draft
572 #
572 #
573 # should not be necessary for publishing server, but because of an
573 # should not be necessary for publishing server, but because of an
574 # issue fixed in xxxxx we have to do it anyway.
574 # issue fixed in xxxxx we have to do it anyway.
575 fdroots = list(unfi.set('roots(%ln + %ln::)',
575 fdroots = list(unfi.set('roots(%ln + %ln::)',
576 outgoing.missing, droots))
576 outgoing.missing, droots))
577 fdroots = [f.node() for f in fdroots]
577 fdroots = [f.node() for f in fdroots]
578 future = list(unfi.set(revset, fdroots, pushop.futureheads))
578 future = list(unfi.set(revset, fdroots, pushop.futureheads))
579 pushop.outdatedphases = future
579 pushop.outdatedphases = future
580 pushop.fallbackoutdatedphases = fallback
580 pushop.fallbackoutdatedphases = fallback
581
581
582 @pushdiscovery('obsmarker')
582 @pushdiscovery('obsmarker')
583 def _pushdiscoveryobsmarkers(pushop):
583 def _pushdiscoveryobsmarkers(pushop):
584 if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
584 if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
585 and pushop.repo.obsstore
585 and pushop.repo.obsstore
586 and 'obsolete' in pushop.remote.listkeys('namespaces')):
586 and 'obsolete' in pushop.remote.listkeys('namespaces')):
587 repo = pushop.repo
587 repo = pushop.repo
588 # very naive computation, that can be quite expensive on big repo.
588 # very naive computation, that can be quite expensive on big repo.
589 # However: evolution is currently slow on them anyway.
589 # However: evolution is currently slow on them anyway.
590 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
590 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
591 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
591 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
592
592
593 @pushdiscovery('bookmarks')
593 @pushdiscovery('bookmarks')
594 def _pushdiscoverybookmarks(pushop):
594 def _pushdiscoverybookmarks(pushop):
595 ui = pushop.ui
595 ui = pushop.ui
596 repo = pushop.repo.unfiltered()
596 repo = pushop.repo.unfiltered()
597 remote = pushop.remote
597 remote = pushop.remote
598 ui.debug("checking for updated bookmarks\n")
598 ui.debug("checking for updated bookmarks\n")
599 ancestors = ()
599 ancestors = ()
600 if pushop.revs:
600 if pushop.revs:
601 revnums = map(repo.changelog.rev, pushop.revs)
601 revnums = map(repo.changelog.rev, pushop.revs)
602 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
602 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
603 remotebookmark = remote.listkeys('bookmarks')
603 remotebookmark = remote.listkeys('bookmarks')
604
604
605 explicit = set([repo._bookmarks.expandname(bookmark)
605 explicit = set([repo._bookmarks.expandname(bookmark)
606 for bookmark in pushop.bookmarks])
606 for bookmark in pushop.bookmarks])
607
607
608 remotebookmark = bookmod.unhexlifybookmarks(remotebookmark)
608 remotebookmark = bookmod.unhexlifybookmarks(remotebookmark)
609 comp = bookmod.comparebookmarks(repo, repo._bookmarks, remotebookmark)
609 comp = bookmod.comparebookmarks(repo, repo._bookmarks, remotebookmark)
610
610
611 def safehex(x):
611 def safehex(x):
612 if x is None:
612 if x is None:
613 return x
613 return x
614 return hex(x)
614 return hex(x)
615
615
616 def hexifycompbookmarks(bookmarks):
616 def hexifycompbookmarks(bookmarks):
617 for b, scid, dcid in bookmarks:
617 for b, scid, dcid in bookmarks:
618 yield b, safehex(scid), safehex(dcid)
618 yield b, safehex(scid), safehex(dcid)
619
619
620 comp = [hexifycompbookmarks(marks) for marks in comp]
620 comp = [hexifycompbookmarks(marks) for marks in comp]
621 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
621 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
622
622
623 for b, scid, dcid in advsrc:
623 for b, scid, dcid in advsrc:
624 if b in explicit:
624 if b in explicit:
625 explicit.remove(b)
625 explicit.remove(b)
626 if not ancestors or repo[scid].rev() in ancestors:
626 if not ancestors or repo[scid].rev() in ancestors:
627 pushop.outbookmarks.append((b, dcid, scid))
627 pushop.outbookmarks.append((b, dcid, scid))
628 # search added bookmark
628 # search added bookmark
629 for b, scid, dcid in addsrc:
629 for b, scid, dcid in addsrc:
630 if b in explicit:
630 if b in explicit:
631 explicit.remove(b)
631 explicit.remove(b)
632 pushop.outbookmarks.append((b, '', scid))
632 pushop.outbookmarks.append((b, '', scid))
633 # search for overwritten bookmark
633 # search for overwritten bookmark
634 for b, scid, dcid in list(advdst) + list(diverge) + list(differ):
634 for b, scid, dcid in list(advdst) + list(diverge) + list(differ):
635 if b in explicit:
635 if b in explicit:
636 explicit.remove(b)
636 explicit.remove(b)
637 pushop.outbookmarks.append((b, dcid, scid))
637 pushop.outbookmarks.append((b, dcid, scid))
638 # search for bookmark to delete
638 # search for bookmark to delete
639 for b, scid, dcid in adddst:
639 for b, scid, dcid in adddst:
640 if b in explicit:
640 if b in explicit:
641 explicit.remove(b)
641 explicit.remove(b)
642 # treat as "deleted locally"
642 # treat as "deleted locally"
643 pushop.outbookmarks.append((b, dcid, ''))
643 pushop.outbookmarks.append((b, dcid, ''))
644 # identical bookmarks shouldn't get reported
644 # identical bookmarks shouldn't get reported
645 for b, scid, dcid in same:
645 for b, scid, dcid in same:
646 if b in explicit:
646 if b in explicit:
647 explicit.remove(b)
647 explicit.remove(b)
648
648
649 if explicit:
649 if explicit:
650 explicit = sorted(explicit)
650 explicit = sorted(explicit)
651 # we should probably list all of them
651 # we should probably list all of them
652 ui.warn(_('bookmark %s does not exist on the local '
652 ui.warn(_('bookmark %s does not exist on the local '
653 'or remote repository!\n') % explicit[0])
653 'or remote repository!\n') % explicit[0])
654 pushop.bkresult = 2
654 pushop.bkresult = 2
655
655
656 pushop.outbookmarks.sort()
656 pushop.outbookmarks.sort()
657
657
658 def _pushcheckoutgoing(pushop):
658 def _pushcheckoutgoing(pushop):
659 outgoing = pushop.outgoing
659 outgoing = pushop.outgoing
660 unfi = pushop.repo.unfiltered()
660 unfi = pushop.repo.unfiltered()
661 if not outgoing.missing:
661 if not outgoing.missing:
662 # nothing to push
662 # nothing to push
663 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
663 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
664 return False
664 return False
665 # something to push
665 # something to push
666 if not pushop.force:
666 if not pushop.force:
667 # if repo.obsstore == False --> no obsolete
667 # if repo.obsstore == False --> no obsolete
668 # then, save the iteration
668 # then, save the iteration
669 if unfi.obsstore:
669 if unfi.obsstore:
670 # this message are here for 80 char limit reason
670 # this message are here for 80 char limit reason
671 mso = _("push includes obsolete changeset: %s!")
671 mso = _("push includes obsolete changeset: %s!")
672 mspd = _("push includes phase-divergent changeset: %s!")
672 mspd = _("push includes phase-divergent changeset: %s!")
673 mscd = _("push includes content-divergent changeset: %s!")
673 mscd = _("push includes content-divergent changeset: %s!")
674 mst = {"orphan": _("push includes orphan changeset: %s!"),
674 mst = {"orphan": _("push includes orphan changeset: %s!"),
675 "phase-divergent": mspd,
675 "phase-divergent": mspd,
676 "content-divergent": mscd}
676 "content-divergent": mscd}
677 # If we are to push if there is at least one
677 # If we are to push if there is at least one
678 # obsolete or unstable changeset in missing, at
678 # obsolete or unstable changeset in missing, at
679 # least one of the missinghead will be obsolete or
679 # least one of the missinghead will be obsolete or
680 # unstable. So checking heads only is ok
680 # unstable. So checking heads only is ok
681 for node in outgoing.missingheads:
681 for node in outgoing.missingheads:
682 ctx = unfi[node]
682 ctx = unfi[node]
683 if ctx.obsolete():
683 if ctx.obsolete():
684 raise error.Abort(mso % ctx)
684 raise error.Abort(mso % ctx)
685 elif ctx.isunstable():
685 elif ctx.isunstable():
686 # TODO print more than one instability in the abort
686 # TODO print more than one instability in the abort
687 # message
687 # message
688 raise error.Abort(mst[ctx.instabilities()[0]] % ctx)
688 raise error.Abort(mst[ctx.instabilities()[0]] % ctx)
689
689
690 discovery.checkheads(pushop)
690 discovery.checkheads(pushop)
691 return True
691 return True
692
692
693 # List of names of steps to perform for an outgoing bundle2, order matters.
693 # List of names of steps to perform for an outgoing bundle2, order matters.
694 b2partsgenorder = []
694 b2partsgenorder = []
695
695
696 # Mapping between step name and function
696 # Mapping between step name and function
697 #
697 #
698 # This exists to help extensions wrap steps if necessary
698 # This exists to help extensions wrap steps if necessary
699 b2partsgenmapping = {}
699 b2partsgenmapping = {}
700
700
701 def b2partsgenerator(stepname, idx=None):
701 def b2partsgenerator(stepname, idx=None):
702 """decorator for function generating bundle2 part
702 """decorator for function generating bundle2 part
703
703
704 The function is added to the step -> function mapping and appended to the
704 The function is added to the step -> function mapping and appended to the
705 list of steps. Beware that decorated functions will be added in order
705 list of steps. Beware that decorated functions will be added in order
706 (this may matter).
706 (this may matter).
707
707
708 You can only use this decorator for new steps, if you want to wrap a step
708 You can only use this decorator for new steps, if you want to wrap a step
709 from an extension, attack the b2partsgenmapping dictionary directly."""
709 from an extension, attack the b2partsgenmapping dictionary directly."""
710 def dec(func):
710 def dec(func):
711 assert stepname not in b2partsgenmapping
711 assert stepname not in b2partsgenmapping
712 b2partsgenmapping[stepname] = func
712 b2partsgenmapping[stepname] = func
713 if idx is None:
713 if idx is None:
714 b2partsgenorder.append(stepname)
714 b2partsgenorder.append(stepname)
715 else:
715 else:
716 b2partsgenorder.insert(idx, stepname)
716 b2partsgenorder.insert(idx, stepname)
717 return func
717 return func
718 return dec
718 return dec
719
719
720 def _pushb2ctxcheckheads(pushop, bundler):
720 def _pushb2ctxcheckheads(pushop, bundler):
721 """Generate race condition checking parts
721 """Generate race condition checking parts
722
722
723 Exists as an independent function to aid extensions
723 Exists as an independent function to aid extensions
724 """
724 """
725 # * 'force' do not check for push race,
725 # * 'force' do not check for push race,
726 # * if we don't push anything, there are nothing to check.
726 # * if we don't push anything, there are nothing to check.
727 if not pushop.force and pushop.outgoing.missingheads:
727 if not pushop.force and pushop.outgoing.missingheads:
728 allowunrelated = 'related' in bundler.capabilities.get('checkheads', ())
728 allowunrelated = 'related' in bundler.capabilities.get('checkheads', ())
729 emptyremote = pushop.pushbranchmap is None
729 emptyremote = pushop.pushbranchmap is None
730 if not allowunrelated or emptyremote:
730 if not allowunrelated or emptyremote:
731 bundler.newpart('check:heads', data=iter(pushop.remoteheads))
731 bundler.newpart('check:heads', data=iter(pushop.remoteheads))
732 else:
732 else:
733 affected = set()
733 affected = set()
734 for branch, heads in pushop.pushbranchmap.iteritems():
734 for branch, heads in pushop.pushbranchmap.iteritems():
735 remoteheads, newheads, unsyncedheads, discardedheads = heads
735 remoteheads, newheads, unsyncedheads, discardedheads = heads
736 if remoteheads is not None:
736 if remoteheads is not None:
737 remote = set(remoteheads)
737 remote = set(remoteheads)
738 affected |= set(discardedheads) & remote
738 affected |= set(discardedheads) & remote
739 affected |= remote - set(newheads)
739 affected |= remote - set(newheads)
740 if affected:
740 if affected:
741 data = iter(sorted(affected))
741 data = iter(sorted(affected))
742 bundler.newpart('check:updated-heads', data=data)
742 bundler.newpart('check:updated-heads', data=data)
743
743
744 def _pushing(pushop):
744 def _pushing(pushop):
745 """return True if we are pushing anything"""
745 """return True if we are pushing anything"""
746 return bool(pushop.outgoing.missing
746 return bool(pushop.outgoing.missing
747 or pushop.outdatedphases
747 or pushop.outdatedphases
748 or pushop.outobsmarkers
748 or pushop.outobsmarkers
749 or pushop.outbookmarks)
749 or pushop.outbookmarks)
750
750
751 @b2partsgenerator('check-bookmarks')
751 @b2partsgenerator('check-bookmarks')
752 def _pushb2checkbookmarks(pushop, bundler):
752 def _pushb2checkbookmarks(pushop, bundler):
753 """insert bookmark move checking"""
753 """insert bookmark move checking"""
754 if not _pushing(pushop) or pushop.force:
754 if not _pushing(pushop) or pushop.force:
755 return
755 return
756 b2caps = bundle2.bundle2caps(pushop.remote)
756 b2caps = bundle2.bundle2caps(pushop.remote)
757 hasbookmarkcheck = 'bookmarks' in b2caps
757 hasbookmarkcheck = 'bookmarks' in b2caps
758 if not (pushop.outbookmarks and hasbookmarkcheck):
758 if not (pushop.outbookmarks and hasbookmarkcheck):
759 return
759 return
760 data = []
760 data = []
761 for book, old, new in pushop.outbookmarks:
761 for book, old, new in pushop.outbookmarks:
762 old = bin(old)
762 old = bin(old)
763 data.append((book, old))
763 data.append((book, old))
764 checkdata = bookmod.binaryencode(data)
764 checkdata = bookmod.binaryencode(data)
765 bundler.newpart('check:bookmarks', data=checkdata)
765 bundler.newpart('check:bookmarks', data=checkdata)
766
766
767 @b2partsgenerator('check-phases')
767 @b2partsgenerator('check-phases')
768 def _pushb2checkphases(pushop, bundler):
768 def _pushb2checkphases(pushop, bundler):
769 """insert phase move checking"""
769 """insert phase move checking"""
770 if not _pushing(pushop) or pushop.force:
770 if not _pushing(pushop) or pushop.force:
771 return
771 return
772 b2caps = bundle2.bundle2caps(pushop.remote)
772 b2caps = bundle2.bundle2caps(pushop.remote)
773 hasphaseheads = 'heads' in b2caps.get('phases', ())
773 hasphaseheads = 'heads' in b2caps.get('phases', ())
774 if pushop.remotephases is not None and hasphaseheads:
774 if pushop.remotephases is not None and hasphaseheads:
775 # check that the remote phase has not changed
775 # check that the remote phase has not changed
776 checks = [[] for p in phases.allphases]
776 checks = [[] for p in phases.allphases]
777 checks[phases.public].extend(pushop.remotephases.publicheads)
777 checks[phases.public].extend(pushop.remotephases.publicheads)
778 checks[phases.draft].extend(pushop.remotephases.draftroots)
778 checks[phases.draft].extend(pushop.remotephases.draftroots)
779 if any(checks):
779 if any(checks):
780 for nodes in checks:
780 for nodes in checks:
781 nodes.sort()
781 nodes.sort()
782 checkdata = phases.binaryencode(checks)
782 checkdata = phases.binaryencode(checks)
783 bundler.newpart('check:phases', data=checkdata)
783 bundler.newpart('check:phases', data=checkdata)
784
784
785 @b2partsgenerator('changeset')
785 @b2partsgenerator('changeset')
786 def _pushb2ctx(pushop, bundler):
786 def _pushb2ctx(pushop, bundler):
787 """handle changegroup push through bundle2
787 """handle changegroup push through bundle2
788
788
789 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
789 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
790 """
790 """
791 if 'changesets' in pushop.stepsdone:
791 if 'changesets' in pushop.stepsdone:
792 return
792 return
793 pushop.stepsdone.add('changesets')
793 pushop.stepsdone.add('changesets')
794 # Send known heads to the server for race detection.
794 # Send known heads to the server for race detection.
795 if not _pushcheckoutgoing(pushop):
795 if not _pushcheckoutgoing(pushop):
796 return
796 return
797 pushop.repo.prepushoutgoinghooks(pushop)
797 pushop.repo.prepushoutgoinghooks(pushop)
798
798
799 _pushb2ctxcheckheads(pushop, bundler)
799 _pushb2ctxcheckheads(pushop, bundler)
800
800
801 b2caps = bundle2.bundle2caps(pushop.remote)
801 b2caps = bundle2.bundle2caps(pushop.remote)
802 version = '01'
802 version = '01'
803 cgversions = b2caps.get('changegroup')
803 cgversions = b2caps.get('changegroup')
804 if cgversions: # 3.1 and 3.2 ship with an empty value
804 if cgversions: # 3.1 and 3.2 ship with an empty value
805 cgversions = [v for v in cgversions
805 cgversions = [v for v in cgversions
806 if v in changegroup.supportedoutgoingversions(
806 if v in changegroup.supportedoutgoingversions(
807 pushop.repo)]
807 pushop.repo)]
808 if not cgversions:
808 if not cgversions:
809 raise ValueError(_('no common changegroup version'))
809 raise ValueError(_('no common changegroup version'))
810 version = max(cgversions)
810 version = max(cgversions)
811 cgstream = changegroup.makestream(pushop.repo, pushop.outgoing, version,
811 cgstream = changegroup.makestream(pushop.repo, pushop.outgoing, version,
812 'push')
812 'push')
813 cgpart = bundler.newpart('changegroup', data=cgstream)
813 cgpart = bundler.newpart('changegroup', data=cgstream)
814 if cgversions:
814 if cgversions:
815 cgpart.addparam('version', version)
815 cgpart.addparam('version', version)
816 if 'treemanifest' in pushop.repo.requirements:
816 if 'treemanifest' in pushop.repo.requirements:
817 cgpart.addparam('treemanifest', '1')
817 cgpart.addparam('treemanifest', '1')
818 def handlereply(op):
818 def handlereply(op):
819 """extract addchangegroup returns from server reply"""
819 """extract addchangegroup returns from server reply"""
820 cgreplies = op.records.getreplies(cgpart.id)
820 cgreplies = op.records.getreplies(cgpart.id)
821 assert len(cgreplies['changegroup']) == 1
821 assert len(cgreplies['changegroup']) == 1
822 pushop.cgresult = cgreplies['changegroup'][0]['return']
822 pushop.cgresult = cgreplies['changegroup'][0]['return']
823 return handlereply
823 return handlereply
824
824
825 @b2partsgenerator('phase')
825 @b2partsgenerator('phase')
826 def _pushb2phases(pushop, bundler):
826 def _pushb2phases(pushop, bundler):
827 """handle phase push through bundle2"""
827 """handle phase push through bundle2"""
828 if 'phases' in pushop.stepsdone:
828 if 'phases' in pushop.stepsdone:
829 return
829 return
830 b2caps = bundle2.bundle2caps(pushop.remote)
830 b2caps = bundle2.bundle2caps(pushop.remote)
831 ui = pushop.repo.ui
831 ui = pushop.repo.ui
832
832
833 legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
833 legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
834 haspushkey = 'pushkey' in b2caps
834 haspushkey = 'pushkey' in b2caps
835 hasphaseheads = 'heads' in b2caps.get('phases', ())
835 hasphaseheads = 'heads' in b2caps.get('phases', ())
836
836
837 if hasphaseheads and not legacyphase:
837 if hasphaseheads and not legacyphase:
838 return _pushb2phaseheads(pushop, bundler)
838 return _pushb2phaseheads(pushop, bundler)
839 elif haspushkey:
839 elif haspushkey:
840 return _pushb2phasespushkey(pushop, bundler)
840 return _pushb2phasespushkey(pushop, bundler)
841
841
842 def _pushb2phaseheads(pushop, bundler):
842 def _pushb2phaseheads(pushop, bundler):
843 """push phase information through a bundle2 - binary part"""
843 """push phase information through a bundle2 - binary part"""
844 pushop.stepsdone.add('phases')
844 pushop.stepsdone.add('phases')
845 if pushop.outdatedphases:
845 if pushop.outdatedphases:
846 updates = [[] for p in phases.allphases]
846 updates = [[] for p in phases.allphases]
847 updates[0].extend(h.node() for h in pushop.outdatedphases)
847 updates[0].extend(h.node() for h in pushop.outdatedphases)
848 phasedata = phases.binaryencode(updates)
848 phasedata = phases.binaryencode(updates)
849 bundler.newpart('phase-heads', data=phasedata)
849 bundler.newpart('phase-heads', data=phasedata)
850
850
851 def _pushb2phasespushkey(pushop, bundler):
851 def _pushb2phasespushkey(pushop, bundler):
852 """push phase information through a bundle2 - pushkey part"""
852 """push phase information through a bundle2 - pushkey part"""
853 pushop.stepsdone.add('phases')
853 pushop.stepsdone.add('phases')
854 part2node = []
854 part2node = []
855
855
856 def handlefailure(pushop, exc):
856 def handlefailure(pushop, exc):
857 targetid = int(exc.partid)
857 targetid = int(exc.partid)
858 for partid, node in part2node:
858 for partid, node in part2node:
859 if partid == targetid:
859 if partid == targetid:
860 raise error.Abort(_('updating %s to public failed') % node)
860 raise error.Abort(_('updating %s to public failed') % node)
861
861
862 enc = pushkey.encode
862 enc = pushkey.encode
863 for newremotehead in pushop.outdatedphases:
863 for newremotehead in pushop.outdatedphases:
864 part = bundler.newpart('pushkey')
864 part = bundler.newpart('pushkey')
865 part.addparam('namespace', enc('phases'))
865 part.addparam('namespace', enc('phases'))
866 part.addparam('key', enc(newremotehead.hex()))
866 part.addparam('key', enc(newremotehead.hex()))
867 part.addparam('old', enc('%d' % phases.draft))
867 part.addparam('old', enc('%d' % phases.draft))
868 part.addparam('new', enc('%d' % phases.public))
868 part.addparam('new', enc('%d' % phases.public))
869 part2node.append((part.id, newremotehead))
869 part2node.append((part.id, newremotehead))
870 pushop.pkfailcb[part.id] = handlefailure
870 pushop.pkfailcb[part.id] = handlefailure
871
871
872 def handlereply(op):
872 def handlereply(op):
873 for partid, node in part2node:
873 for partid, node in part2node:
874 partrep = op.records.getreplies(partid)
874 partrep = op.records.getreplies(partid)
875 results = partrep['pushkey']
875 results = partrep['pushkey']
876 assert len(results) <= 1
876 assert len(results) <= 1
877 msg = None
877 msg = None
878 if not results:
878 if not results:
879 msg = _('server ignored update of %s to public!\n') % node
879 msg = _('server ignored update of %s to public!\n') % node
880 elif not int(results[0]['return']):
880 elif not int(results[0]['return']):
881 msg = _('updating %s to public failed!\n') % node
881 msg = _('updating %s to public failed!\n') % node
882 if msg is not None:
882 if msg is not None:
883 pushop.ui.warn(msg)
883 pushop.ui.warn(msg)
884 return handlereply
884 return handlereply
885
885
886 @b2partsgenerator('obsmarkers')
886 @b2partsgenerator('obsmarkers')
887 def _pushb2obsmarkers(pushop, bundler):
887 def _pushb2obsmarkers(pushop, bundler):
888 if 'obsmarkers' in pushop.stepsdone:
888 if 'obsmarkers' in pushop.stepsdone:
889 return
889 return
890 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
890 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
891 if obsolete.commonversion(remoteversions) is None:
891 if obsolete.commonversion(remoteversions) is None:
892 return
892 return
893 pushop.stepsdone.add('obsmarkers')
893 pushop.stepsdone.add('obsmarkers')
894 if pushop.outobsmarkers:
894 if pushop.outobsmarkers:
895 markers = sorted(pushop.outobsmarkers)
895 markers = sorted(pushop.outobsmarkers)
896 bundle2.buildobsmarkerspart(bundler, markers)
896 bundle2.buildobsmarkerspart(bundler, markers)
897
897
898 @b2partsgenerator('bookmarks')
898 @b2partsgenerator('bookmarks')
899 def _pushb2bookmarks(pushop, bundler):
899 def _pushb2bookmarks(pushop, bundler):
900 """handle bookmark push through bundle2"""
900 """handle bookmark push through bundle2"""
901 if 'bookmarks' in pushop.stepsdone:
901 if 'bookmarks' in pushop.stepsdone:
902 return
902 return
903 b2caps = bundle2.bundle2caps(pushop.remote)
903 b2caps = bundle2.bundle2caps(pushop.remote)
904
904
905 legacy = pushop.repo.ui.configlist('devel', 'legacy.exchange')
905 legacy = pushop.repo.ui.configlist('devel', 'legacy.exchange')
906 legacybooks = 'bookmarks' in legacy
906 legacybooks = 'bookmarks' in legacy
907
907
908 if not legacybooks and 'bookmarks' in b2caps:
908 if not legacybooks and 'bookmarks' in b2caps:
909 return _pushb2bookmarkspart(pushop, bundler)
909 return _pushb2bookmarkspart(pushop, bundler)
910 elif 'pushkey' in b2caps:
910 elif 'pushkey' in b2caps:
911 return _pushb2bookmarkspushkey(pushop, bundler)
911 return _pushb2bookmarkspushkey(pushop, bundler)
912
912
913 def _bmaction(old, new):
913 def _bmaction(old, new):
914 """small utility for bookmark pushing"""
914 """small utility for bookmark pushing"""
915 if not old:
915 if not old:
916 return 'export'
916 return 'export'
917 elif not new:
917 elif not new:
918 return 'delete'
918 return 'delete'
919 return 'update'
919 return 'update'
920
920
921 def _pushb2bookmarkspart(pushop, bundler):
921 def _pushb2bookmarkspart(pushop, bundler):
922 pushop.stepsdone.add('bookmarks')
922 pushop.stepsdone.add('bookmarks')
923 if not pushop.outbookmarks:
923 if not pushop.outbookmarks:
924 return
924 return
925
925
926 allactions = []
926 allactions = []
927 data = []
927 data = []
928 for book, old, new in pushop.outbookmarks:
928 for book, old, new in pushop.outbookmarks:
929 new = bin(new)
929 new = bin(new)
930 data.append((book, new))
930 data.append((book, new))
931 allactions.append((book, _bmaction(old, new)))
931 allactions.append((book, _bmaction(old, new)))
932 checkdata = bookmod.binaryencode(data)
932 checkdata = bookmod.binaryencode(data)
933 bundler.newpart('bookmarks', data=checkdata)
933 bundler.newpart('bookmarks', data=checkdata)
934
934
935 def handlereply(op):
935 def handlereply(op):
936 ui = pushop.ui
936 ui = pushop.ui
937 # if success
937 # if success
938 for book, action in allactions:
938 for book, action in allactions:
939 ui.status(bookmsgmap[action][0] % book)
939 ui.status(bookmsgmap[action][0] % book)
940
940
941 return handlereply
941 return handlereply
942
942
943 def _pushb2bookmarkspushkey(pushop, bundler):
943 def _pushb2bookmarkspushkey(pushop, bundler):
944 pushop.stepsdone.add('bookmarks')
944 pushop.stepsdone.add('bookmarks')
945 part2book = []
945 part2book = []
946 enc = pushkey.encode
946 enc = pushkey.encode
947
947
948 def handlefailure(pushop, exc):
948 def handlefailure(pushop, exc):
949 targetid = int(exc.partid)
949 targetid = int(exc.partid)
950 for partid, book, action in part2book:
950 for partid, book, action in part2book:
951 if partid == targetid:
951 if partid == targetid:
952 raise error.Abort(bookmsgmap[action][1].rstrip() % book)
952 raise error.Abort(bookmsgmap[action][1].rstrip() % book)
953 # we should not be called for part we did not generated
953 # we should not be called for part we did not generated
954 assert False
954 assert False
955
955
956 for book, old, new in pushop.outbookmarks:
956 for book, old, new in pushop.outbookmarks:
957 part = bundler.newpart('pushkey')
957 part = bundler.newpart('pushkey')
958 part.addparam('namespace', enc('bookmarks'))
958 part.addparam('namespace', enc('bookmarks'))
959 part.addparam('key', enc(book))
959 part.addparam('key', enc(book))
960 part.addparam('old', enc(old))
960 part.addparam('old', enc(old))
961 part.addparam('new', enc(new))
961 part.addparam('new', enc(new))
962 action = 'update'
962 action = 'update'
963 if not old:
963 if not old:
964 action = 'export'
964 action = 'export'
965 elif not new:
965 elif not new:
966 action = 'delete'
966 action = 'delete'
967 part2book.append((part.id, book, action))
967 part2book.append((part.id, book, action))
968 pushop.pkfailcb[part.id] = handlefailure
968 pushop.pkfailcb[part.id] = handlefailure
969
969
970 def handlereply(op):
970 def handlereply(op):
971 ui = pushop.ui
971 ui = pushop.ui
972 for partid, book, action in part2book:
972 for partid, book, action in part2book:
973 partrep = op.records.getreplies(partid)
973 partrep = op.records.getreplies(partid)
974 results = partrep['pushkey']
974 results = partrep['pushkey']
975 assert len(results) <= 1
975 assert len(results) <= 1
976 if not results:
976 if not results:
977 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
977 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
978 else:
978 else:
979 ret = int(results[0]['return'])
979 ret = int(results[0]['return'])
980 if ret:
980 if ret:
981 ui.status(bookmsgmap[action][0] % book)
981 ui.status(bookmsgmap[action][0] % book)
982 else:
982 else:
983 ui.warn(bookmsgmap[action][1] % book)
983 ui.warn(bookmsgmap[action][1] % book)
984 if pushop.bkresult is not None:
984 if pushop.bkresult is not None:
985 pushop.bkresult = 1
985 pushop.bkresult = 1
986 return handlereply
986 return handlereply
987
987
988 @b2partsgenerator('pushvars', idx=0)
988 @b2partsgenerator('pushvars', idx=0)
989 def _getbundlesendvars(pushop, bundler):
989 def _getbundlesendvars(pushop, bundler):
990 '''send shellvars via bundle2'''
990 '''send shellvars via bundle2'''
991 pushvars = pushop.pushvars
991 pushvars = pushop.pushvars
992 if pushvars:
992 if pushvars:
993 shellvars = {}
993 shellvars = {}
994 for raw in pushvars:
994 for raw in pushvars:
995 if '=' not in raw:
995 if '=' not in raw:
996 msg = ("unable to parse variable '%s', should follow "
996 msg = ("unable to parse variable '%s', should follow "
997 "'KEY=VALUE' or 'KEY=' format")
997 "'KEY=VALUE' or 'KEY=' format")
998 raise error.Abort(msg % raw)
998 raise error.Abort(msg % raw)
999 k, v = raw.split('=', 1)
999 k, v = raw.split('=', 1)
1000 shellvars[k] = v
1000 shellvars[k] = v
1001
1001
1002 part = bundler.newpart('pushvars')
1002 part = bundler.newpart('pushvars')
1003
1003
1004 for key, value in shellvars.iteritems():
1004 for key, value in shellvars.iteritems():
1005 part.addparam(key, value, mandatory=False)
1005 part.addparam(key, value, mandatory=False)
1006
1006
1007 def _pushbundle2(pushop):
1007 def _pushbundle2(pushop):
1008 """push data to the remote using bundle2
1008 """push data to the remote using bundle2
1009
1009
1010 The only currently supported type of data is changegroup but this will
1010 The only currently supported type of data is changegroup but this will
1011 evolve in the future."""
1011 evolve in the future."""
1012 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
1012 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
1013 pushback = (pushop.trmanager
1013 pushback = (pushop.trmanager
1014 and pushop.ui.configbool('experimental', 'bundle2.pushback'))
1014 and pushop.ui.configbool('experimental', 'bundle2.pushback'))
1015
1015
1016 # create reply capability
1016 # create reply capability
1017 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo,
1017 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo,
1018 allowpushback=pushback))
1018 allowpushback=pushback))
1019 bundler.newpart('replycaps', data=capsblob)
1019 bundler.newpart('replycaps', data=capsblob)
1020 replyhandlers = []
1020 replyhandlers = []
1021 for partgenname in b2partsgenorder:
1021 for partgenname in b2partsgenorder:
1022 partgen = b2partsgenmapping[partgenname]
1022 partgen = b2partsgenmapping[partgenname]
1023 ret = partgen(pushop, bundler)
1023 ret = partgen(pushop, bundler)
1024 if callable(ret):
1024 if callable(ret):
1025 replyhandlers.append(ret)
1025 replyhandlers.append(ret)
1026 # do not push if nothing to push
1026 # do not push if nothing to push
1027 if bundler.nbparts <= 1:
1027 if bundler.nbparts <= 1:
1028 return
1028 return
1029 stream = util.chunkbuffer(bundler.getchunks())
1029 stream = util.chunkbuffer(bundler.getchunks())
1030 try:
1030 try:
1031 try:
1031 try:
1032 reply = pushop.remote.unbundle(
1032 reply = pushop.remote.unbundle(
1033 stream, ['force'], pushop.remote.url())
1033 stream, ['force'], pushop.remote.url())
1034 except error.BundleValueError as exc:
1034 except error.BundleValueError as exc:
1035 raise error.Abort(_('missing support for %s') % exc)
1035 raise error.Abort(_('missing support for %s') % exc)
1036 try:
1036 try:
1037 trgetter = None
1037 trgetter = None
1038 if pushback:
1038 if pushback:
1039 trgetter = pushop.trmanager.transaction
1039 trgetter = pushop.trmanager.transaction
1040 op = bundle2.processbundle(pushop.repo, reply, trgetter)
1040 op = bundle2.processbundle(pushop.repo, reply, trgetter)
1041 except error.BundleValueError as exc:
1041 except error.BundleValueError as exc:
1042 raise error.Abort(_('missing support for %s') % exc)
1042 raise error.Abort(_('missing support for %s') % exc)
1043 except bundle2.AbortFromPart as exc:
1043 except bundle2.AbortFromPart as exc:
1044 pushop.ui.status(_('remote: %s\n') % exc)
1044 pushop.ui.status(_('remote: %s\n') % exc)
1045 if exc.hint is not None:
1045 if exc.hint is not None:
1046 pushop.ui.status(_('remote: %s\n') % ('(%s)' % exc.hint))
1046 pushop.ui.status(_('remote: %s\n') % ('(%s)' % exc.hint))
1047 raise error.Abort(_('push failed on remote'))
1047 raise error.Abort(_('push failed on remote'))
1048 except error.PushkeyFailed as exc:
1048 except error.PushkeyFailed as exc:
1049 partid = int(exc.partid)
1049 partid = int(exc.partid)
1050 if partid not in pushop.pkfailcb:
1050 if partid not in pushop.pkfailcb:
1051 raise
1051 raise
1052 pushop.pkfailcb[partid](pushop, exc)
1052 pushop.pkfailcb[partid](pushop, exc)
1053 for rephand in replyhandlers:
1053 for rephand in replyhandlers:
1054 rephand(op)
1054 rephand(op)
1055
1055
1056 def _pushchangeset(pushop):
1056 def _pushchangeset(pushop):
1057 """Make the actual push of changeset bundle to remote repo"""
1057 """Make the actual push of changeset bundle to remote repo"""
1058 if 'changesets' in pushop.stepsdone:
1058 if 'changesets' in pushop.stepsdone:
1059 return
1059 return
1060 pushop.stepsdone.add('changesets')
1060 pushop.stepsdone.add('changesets')
1061 if not _pushcheckoutgoing(pushop):
1061 if not _pushcheckoutgoing(pushop):
1062 return
1062 return
1063
1063
1064 # Should have verified this in push().
1064 # Should have verified this in push().
1065 assert pushop.remote.capable('unbundle')
1065 assert pushop.remote.capable('unbundle')
1066
1066
1067 pushop.repo.prepushoutgoinghooks(pushop)
1067 pushop.repo.prepushoutgoinghooks(pushop)
1068 outgoing = pushop.outgoing
1068 outgoing = pushop.outgoing
1069 # TODO: get bundlecaps from remote
1069 # TODO: get bundlecaps from remote
1070 bundlecaps = None
1070 bundlecaps = None
1071 # create a changegroup from local
1071 # create a changegroup from local
1072 if pushop.revs is None and not (outgoing.excluded
1072 if pushop.revs is None and not (outgoing.excluded
1073 or pushop.repo.changelog.filteredrevs):
1073 or pushop.repo.changelog.filteredrevs):
1074 # push everything,
1074 # push everything,
1075 # use the fast path, no race possible on push
1075 # use the fast path, no race possible on push
1076 cg = changegroup.makechangegroup(pushop.repo, outgoing, '01', 'push',
1076 cg = changegroup.makechangegroup(pushop.repo, outgoing, '01', 'push',
1077 fastpath=True, bundlecaps=bundlecaps)
1077 fastpath=True, bundlecaps=bundlecaps)
1078 else:
1078 else:
1079 cg = changegroup.makechangegroup(pushop.repo, outgoing, '01',
1079 cg = changegroup.makechangegroup(pushop.repo, outgoing, '01',
1080 'push', bundlecaps=bundlecaps)
1080 'push', bundlecaps=bundlecaps)
1081
1081
1082 # apply changegroup to remote
1082 # apply changegroup to remote
1083 # local repo finds heads on server, finds out what
1083 # local repo finds heads on server, finds out what
1084 # revs it must push. once revs transferred, if server
1084 # revs it must push. once revs transferred, if server
1085 # finds it has different heads (someone else won
1085 # finds it has different heads (someone else won
1086 # commit/push race), server aborts.
1086 # commit/push race), server aborts.
1087 if pushop.force:
1087 if pushop.force:
1088 remoteheads = ['force']
1088 remoteheads = ['force']
1089 else:
1089 else:
1090 remoteheads = pushop.remoteheads
1090 remoteheads = pushop.remoteheads
1091 # ssh: return remote's addchangegroup()
1091 # ssh: return remote's addchangegroup()
1092 # http: return remote's addchangegroup() or 0 for error
1092 # http: return remote's addchangegroup() or 0 for error
1093 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
1093 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
1094 pushop.repo.url())
1094 pushop.repo.url())
1095
1095
1096 def _pushsyncphase(pushop):
1096 def _pushsyncphase(pushop):
1097 """synchronise phase information locally and remotely"""
1097 """synchronise phase information locally and remotely"""
1098 cheads = pushop.commonheads
1098 cheads = pushop.commonheads
1099 # even when we don't push, exchanging phase data is useful
1099 # even when we don't push, exchanging phase data is useful
1100 remotephases = pushop.remote.listkeys('phases')
1100 remotephases = pushop.remote.listkeys('phases')
1101 if (pushop.ui.configbool('ui', '_usedassubrepo')
1101 if (pushop.ui.configbool('ui', '_usedassubrepo')
1102 and remotephases # server supports phases
1102 and remotephases # server supports phases
1103 and pushop.cgresult is None # nothing was pushed
1103 and pushop.cgresult is None # nothing was pushed
1104 and remotephases.get('publishing', False)):
1104 and remotephases.get('publishing', False)):
1105 # When:
1105 # When:
1106 # - this is a subrepo push
1106 # - this is a subrepo push
1107 # - and remote support phase
1107 # - and remote support phase
1108 # - and no changeset was pushed
1108 # - and no changeset was pushed
1109 # - and remote is publishing
1109 # - and remote is publishing
1110 # We may be in issue 3871 case!
1110 # We may be in issue 3871 case!
1111 # We drop the possible phase synchronisation done by
1111 # We drop the possible phase synchronisation done by
1112 # courtesy to publish changesets possibly locally draft
1112 # courtesy to publish changesets possibly locally draft
1113 # on the remote.
1113 # on the remote.
1114 remotephases = {'publishing': 'True'}
1114 remotephases = {'publishing': 'True'}
1115 if not remotephases: # old server or public only reply from non-publishing
1115 if not remotephases: # old server or public only reply from non-publishing
1116 _localphasemove(pushop, cheads)
1116 _localphasemove(pushop, cheads)
1117 # don't push any phase data as there is nothing to push
1117 # don't push any phase data as there is nothing to push
1118 else:
1118 else:
1119 ana = phases.analyzeremotephases(pushop.repo, cheads,
1119 ana = phases.analyzeremotephases(pushop.repo, cheads,
1120 remotephases)
1120 remotephases)
1121 pheads, droots = ana
1121 pheads, droots = ana
1122 ### Apply remote phase on local
1122 ### Apply remote phase on local
1123 if remotephases.get('publishing', False):
1123 if remotephases.get('publishing', False):
1124 _localphasemove(pushop, cheads)
1124 _localphasemove(pushop, cheads)
1125 else: # publish = False
1125 else: # publish = False
1126 _localphasemove(pushop, pheads)
1126 _localphasemove(pushop, pheads)
1127 _localphasemove(pushop, cheads, phases.draft)
1127 _localphasemove(pushop, cheads, phases.draft)
1128 ### Apply local phase on remote
1128 ### Apply local phase on remote
1129
1129
1130 if pushop.cgresult:
1130 if pushop.cgresult:
1131 if 'phases' in pushop.stepsdone:
1131 if 'phases' in pushop.stepsdone:
1132 # phases already pushed though bundle2
1132 # phases already pushed though bundle2
1133 return
1133 return
1134 outdated = pushop.outdatedphases
1134 outdated = pushop.outdatedphases
1135 else:
1135 else:
1136 outdated = pushop.fallbackoutdatedphases
1136 outdated = pushop.fallbackoutdatedphases
1137
1137
1138 pushop.stepsdone.add('phases')
1138 pushop.stepsdone.add('phases')
1139
1139
1140 # filter heads already turned public by the push
1140 # filter heads already turned public by the push
1141 outdated = [c for c in outdated if c.node() not in pheads]
1141 outdated = [c for c in outdated if c.node() not in pheads]
1142 # fallback to independent pushkey command
1142 # fallback to independent pushkey command
1143 for newremotehead in outdated:
1143 for newremotehead in outdated:
1144 r = pushop.remote.pushkey('phases',
1144 r = pushop.remote.pushkey('phases',
1145 newremotehead.hex(),
1145 newremotehead.hex(),
1146 str(phases.draft),
1146 str(phases.draft),
1147 str(phases.public))
1147 str(phases.public))
1148 if not r:
1148 if not r:
1149 pushop.ui.warn(_('updating %s to public failed!\n')
1149 pushop.ui.warn(_('updating %s to public failed!\n')
1150 % newremotehead)
1150 % newremotehead)
1151
1151
1152 def _localphasemove(pushop, nodes, phase=phases.public):
1152 def _localphasemove(pushop, nodes, phase=phases.public):
1153 """move <nodes> to <phase> in the local source repo"""
1153 """move <nodes> to <phase> in the local source repo"""
1154 if pushop.trmanager:
1154 if pushop.trmanager:
1155 phases.advanceboundary(pushop.repo,
1155 phases.advanceboundary(pushop.repo,
1156 pushop.trmanager.transaction(),
1156 pushop.trmanager.transaction(),
1157 phase,
1157 phase,
1158 nodes)
1158 nodes)
1159 else:
1159 else:
1160 # repo is not locked, do not change any phases!
1160 # repo is not locked, do not change any phases!
1161 # Informs the user that phases should have been moved when
1161 # Informs the user that phases should have been moved when
1162 # applicable.
1162 # applicable.
1163 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
1163 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
1164 phasestr = phases.phasenames[phase]
1164 phasestr = phases.phasenames[phase]
1165 if actualmoves:
1165 if actualmoves:
1166 pushop.ui.status(_('cannot lock source repo, skipping '
1166 pushop.ui.status(_('cannot lock source repo, skipping '
1167 'local %s phase update\n') % phasestr)
1167 'local %s phase update\n') % phasestr)
1168
1168
1169 def _pushobsolete(pushop):
1169 def _pushobsolete(pushop):
1170 """utility function to push obsolete markers to a remote"""
1170 """utility function to push obsolete markers to a remote"""
1171 if 'obsmarkers' in pushop.stepsdone:
1171 if 'obsmarkers' in pushop.stepsdone:
1172 return
1172 return
1173 repo = pushop.repo
1173 repo = pushop.repo
1174 remote = pushop.remote
1174 remote = pushop.remote
1175 pushop.stepsdone.add('obsmarkers')
1175 pushop.stepsdone.add('obsmarkers')
1176 if pushop.outobsmarkers:
1176 if pushop.outobsmarkers:
1177 pushop.ui.debug('try to push obsolete markers to remote\n')
1177 pushop.ui.debug('try to push obsolete markers to remote\n')
1178 rslts = []
1178 rslts = []
1179 remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers))
1179 remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers))
1180 for key in sorted(remotedata, reverse=True):
1180 for key in sorted(remotedata, reverse=True):
1181 # reverse sort to ensure we end with dump0
1181 # reverse sort to ensure we end with dump0
1182 data = remotedata[key]
1182 data = remotedata[key]
1183 rslts.append(remote.pushkey('obsolete', key, '', data))
1183 rslts.append(remote.pushkey('obsolete', key, '', data))
1184 if [r for r in rslts if not r]:
1184 if [r for r in rslts if not r]:
1185 msg = _('failed to push some obsolete markers!\n')
1185 msg = _('failed to push some obsolete markers!\n')
1186 repo.ui.warn(msg)
1186 repo.ui.warn(msg)
1187
1187
1188 def _pushbookmark(pushop):
1188 def _pushbookmark(pushop):
1189 """Update bookmark position on remote"""
1189 """Update bookmark position on remote"""
1190 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
1190 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
1191 return
1191 return
1192 pushop.stepsdone.add('bookmarks')
1192 pushop.stepsdone.add('bookmarks')
1193 ui = pushop.ui
1193 ui = pushop.ui
1194 remote = pushop.remote
1194 remote = pushop.remote
1195
1195
1196 for b, old, new in pushop.outbookmarks:
1196 for b, old, new in pushop.outbookmarks:
1197 action = 'update'
1197 action = 'update'
1198 if not old:
1198 if not old:
1199 action = 'export'
1199 action = 'export'
1200 elif not new:
1200 elif not new:
1201 action = 'delete'
1201 action = 'delete'
1202 if remote.pushkey('bookmarks', b, old, new):
1202 if remote.pushkey('bookmarks', b, old, new):
1203 ui.status(bookmsgmap[action][0] % b)
1203 ui.status(bookmsgmap[action][0] % b)
1204 else:
1204 else:
1205 ui.warn(bookmsgmap[action][1] % b)
1205 ui.warn(bookmsgmap[action][1] % b)
1206 # discovery can have set the value form invalid entry
1206 # discovery can have set the value form invalid entry
1207 if pushop.bkresult is not None:
1207 if pushop.bkresult is not None:
1208 pushop.bkresult = 1
1208 pushop.bkresult = 1
1209
1209
1210 class pulloperation(object):
1210 class pulloperation(object):
1211 """A object that represent a single pull operation
1211 """A object that represent a single pull operation
1212
1212
1213 It purpose is to carry pull related state and very common operation.
1213 It purpose is to carry pull related state and very common operation.
1214
1214
1215 A new should be created at the beginning of each pull and discarded
1215 A new should be created at the beginning of each pull and discarded
1216 afterward.
1216 afterward.
1217 """
1217 """
1218
1218
1219 def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),
1219 def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),
1220 remotebookmarks=None, streamclonerequested=None):
1220 remotebookmarks=None, streamclonerequested=None):
1221 # repo we pull into
1221 # repo we pull into
1222 self.repo = repo
1222 self.repo = repo
1223 # repo we pull from
1223 # repo we pull from
1224 self.remote = remote
1224 self.remote = remote
1225 # revision we try to pull (None is "all")
1225 # revision we try to pull (None is "all")
1226 self.heads = heads
1226 self.heads = heads
1227 # bookmark pulled explicitly
1227 # bookmark pulled explicitly
1228 self.explicitbookmarks = [repo._bookmarks.expandname(bookmark)
1228 self.explicitbookmarks = [repo._bookmarks.expandname(bookmark)
1229 for bookmark in bookmarks]
1229 for bookmark in bookmarks]
1230 # do we force pull?
1230 # do we force pull?
1231 self.force = force
1231 self.force = force
1232 # whether a streaming clone was requested
1232 # whether a streaming clone was requested
1233 self.streamclonerequested = streamclonerequested
1233 self.streamclonerequested = streamclonerequested
1234 # transaction manager
1234 # transaction manager
1235 self.trmanager = None
1235 self.trmanager = None
1236 # set of common changeset between local and remote before pull
1236 # set of common changeset between local and remote before pull
1237 self.common = None
1237 self.common = None
1238 # set of pulled head
1238 # set of pulled head
1239 self.rheads = None
1239 self.rheads = None
1240 # list of missing changeset to fetch remotely
1240 # list of missing changeset to fetch remotely
1241 self.fetch = None
1241 self.fetch = None
1242 # remote bookmarks data
1242 # remote bookmarks data
1243 self.remotebookmarks = remotebookmarks
1243 self.remotebookmarks = remotebookmarks
1244 # result of changegroup pulling (used as return code by pull)
1244 # result of changegroup pulling (used as return code by pull)
1245 self.cgresult = None
1245 self.cgresult = None
1246 # list of step already done
1246 # list of step already done
1247 self.stepsdone = set()
1247 self.stepsdone = set()
1248 # Whether we attempted a clone from pre-generated bundles.
1248 # Whether we attempted a clone from pre-generated bundles.
1249 self.clonebundleattempted = False
1249 self.clonebundleattempted = False
1250
1250
1251 @util.propertycache
1251 @util.propertycache
1252 def pulledsubset(self):
1252 def pulledsubset(self):
1253 """heads of the set of changeset target by the pull"""
1253 """heads of the set of changeset target by the pull"""
1254 # compute target subset
1254 # compute target subset
1255 if self.heads is None:
1255 if self.heads is None:
1256 # We pulled every thing possible
1256 # We pulled every thing possible
1257 # sync on everything common
1257 # sync on everything common
1258 c = set(self.common)
1258 c = set(self.common)
1259 ret = list(self.common)
1259 ret = list(self.common)
1260 for n in self.rheads:
1260 for n in self.rheads:
1261 if n not in c:
1261 if n not in c:
1262 ret.append(n)
1262 ret.append(n)
1263 return ret
1263 return ret
1264 else:
1264 else:
1265 # We pulled a specific subset
1265 # We pulled a specific subset
1266 # sync on this subset
1266 # sync on this subset
1267 return self.heads
1267 return self.heads
1268
1268
1269 @util.propertycache
1269 @util.propertycache
1270 def canusebundle2(self):
1270 def canusebundle2(self):
1271 return not _forcebundle1(self)
1271 return not _forcebundle1(self)
1272
1272
1273 @util.propertycache
1273 @util.propertycache
1274 def remotebundle2caps(self):
1274 def remotebundle2caps(self):
1275 return bundle2.bundle2caps(self.remote)
1275 return bundle2.bundle2caps(self.remote)
1276
1276
1277 def gettransaction(self):
1277 def gettransaction(self):
1278 # deprecated; talk to trmanager directly
1278 # deprecated; talk to trmanager directly
1279 return self.trmanager.transaction()
1279 return self.trmanager.transaction()
1280
1280
1281 class transactionmanager(util.transactional):
1281 class transactionmanager(util.transactional):
1282 """An object to manage the life cycle of a transaction
1282 """An object to manage the life cycle of a transaction
1283
1283
1284 It creates the transaction on demand and calls the appropriate hooks when
1284 It creates the transaction on demand and calls the appropriate hooks when
1285 closing the transaction."""
1285 closing the transaction."""
1286 def __init__(self, repo, source, url):
1286 def __init__(self, repo, source, url):
1287 self.repo = repo
1287 self.repo = repo
1288 self.source = source
1288 self.source = source
1289 self.url = url
1289 self.url = url
1290 self._tr = None
1290 self._tr = None
1291
1291
1292 def transaction(self):
1292 def transaction(self):
1293 """Return an open transaction object, constructing if necessary"""
1293 """Return an open transaction object, constructing if necessary"""
1294 if not self._tr:
1294 if not self._tr:
1295 trname = '%s\n%s' % (self.source, util.hidepassword(self.url))
1295 trname = '%s\n%s' % (self.source, util.hidepassword(self.url))
1296 self._tr = self.repo.transaction(trname)
1296 self._tr = self.repo.transaction(trname)
1297 self._tr.hookargs['source'] = self.source
1297 self._tr.hookargs['source'] = self.source
1298 self._tr.hookargs['url'] = self.url
1298 self._tr.hookargs['url'] = self.url
1299 return self._tr
1299 return self._tr
1300
1300
1301 def close(self):
1301 def close(self):
1302 """close transaction if created"""
1302 """close transaction if created"""
1303 if self._tr is not None:
1303 if self._tr is not None:
1304 self._tr.close()
1304 self._tr.close()
1305
1305
1306 def release(self):
1306 def release(self):
1307 """release transaction if created"""
1307 """release transaction if created"""
1308 if self._tr is not None:
1308 if self._tr is not None:
1309 self._tr.release()
1309 self._tr.release()
1310
1310
1311 def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,
1311 def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,
1312 streamclonerequested=None):
1312 streamclonerequested=None):
1313 """Fetch repository data from a remote.
1313 """Fetch repository data from a remote.
1314
1314
1315 This is the main function used to retrieve data from a remote repository.
1315 This is the main function used to retrieve data from a remote repository.
1316
1316
1317 ``repo`` is the local repository to clone into.
1317 ``repo`` is the local repository to clone into.
1318 ``remote`` is a peer instance.
1318 ``remote`` is a peer instance.
1319 ``heads`` is an iterable of revisions we want to pull. ``None`` (the
1319 ``heads`` is an iterable of revisions we want to pull. ``None`` (the
1320 default) means to pull everything from the remote.
1320 default) means to pull everything from the remote.
1321 ``bookmarks`` is an iterable of bookmarks requesting to be pulled. By
1321 ``bookmarks`` is an iterable of bookmarks requesting to be pulled. By
1322 default, all remote bookmarks are pulled.
1322 default, all remote bookmarks are pulled.
1323 ``opargs`` are additional keyword arguments to pass to ``pulloperation``
1323 ``opargs`` are additional keyword arguments to pass to ``pulloperation``
1324 initialization.
1324 initialization.
1325 ``streamclonerequested`` is a boolean indicating whether a "streaming
1325 ``streamclonerequested`` is a boolean indicating whether a "streaming
1326 clone" is requested. A "streaming clone" is essentially a raw file copy
1326 clone" is requested. A "streaming clone" is essentially a raw file copy
1327 of revlogs from the server. This only works when the local repository is
1327 of revlogs from the server. This only works when the local repository is
1328 empty. The default value of ``None`` means to respect the server
1328 empty. The default value of ``None`` means to respect the server
1329 configuration for preferring stream clones.
1329 configuration for preferring stream clones.
1330
1330
1331 Returns the ``pulloperation`` created for this pull.
1331 Returns the ``pulloperation`` created for this pull.
1332 """
1332 """
1333 if opargs is None:
1333 if opargs is None:
1334 opargs = {}
1334 opargs = {}
1335 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
1335 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
1336 streamclonerequested=streamclonerequested,
1336 streamclonerequested=streamclonerequested,
1337 **pycompat.strkwargs(opargs))
1337 **pycompat.strkwargs(opargs))
1338
1338
1339 peerlocal = pullop.remote.local()
1339 peerlocal = pullop.remote.local()
1340 if peerlocal:
1340 if peerlocal:
1341 missing = set(peerlocal.requirements) - pullop.repo.supported
1341 missing = set(peerlocal.requirements) - pullop.repo.supported
1342 if missing:
1342 if missing:
1343 msg = _("required features are not"
1343 msg = _("required features are not"
1344 " supported in the destination:"
1344 " supported in the destination:"
1345 " %s") % (', '.join(sorted(missing)))
1345 " %s") % (', '.join(sorted(missing)))
1346 raise error.Abort(msg)
1346 raise error.Abort(msg)
1347
1347
1348 pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
1348 pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
1349 with repo.wlock(), repo.lock(), pullop.trmanager:
1349 with repo.wlock(), repo.lock(), pullop.trmanager:
1350 # This should ideally be in _pullbundle2(). However, it needs to run
1350 # This should ideally be in _pullbundle2(). However, it needs to run
1351 # before discovery to avoid extra work.
1351 # before discovery to avoid extra work.
1352 _maybeapplyclonebundle(pullop)
1352 _maybeapplyclonebundle(pullop)
1353 streamclone.maybeperformlegacystreamclone(pullop)
1353 streamclone.maybeperformlegacystreamclone(pullop)
1354 _pulldiscovery(pullop)
1354 _pulldiscovery(pullop)
1355 if pullop.canusebundle2:
1355 if pullop.canusebundle2:
1356 _pullbundle2(pullop)
1356 _pullbundle2(pullop)
1357 _pullchangeset(pullop)
1357 _pullchangeset(pullop)
1358 _pullphase(pullop)
1358 _pullphase(pullop)
1359 _pullbookmarks(pullop)
1359 _pullbookmarks(pullop)
1360 _pullobsolete(pullop)
1360 _pullobsolete(pullop)
1361
1361
1362 # storing remotenames
1362 # storing remotenames
1363 if repo.ui.configbool('experimental', 'remotenames'):
1363 if repo.ui.configbool('experimental', 'remotenames'):
1364 logexchange.pullremotenames(repo, remote)
1364 logexchange.pullremotenames(repo, remote)
1365
1365
1366 return pullop
1366 return pullop
1367
1367
1368 # list of steps to perform discovery before pull
1368 # list of steps to perform discovery before pull
1369 pulldiscoveryorder = []
1369 pulldiscoveryorder = []
1370
1370
1371 # Mapping between step name and function
1371 # Mapping between step name and function
1372 #
1372 #
1373 # This exists to help extensions wrap steps if necessary
1373 # This exists to help extensions wrap steps if necessary
1374 pulldiscoverymapping = {}
1374 pulldiscoverymapping = {}
1375
1375
1376 def pulldiscovery(stepname):
1376 def pulldiscovery(stepname):
1377 """decorator for function performing discovery before pull
1377 """decorator for function performing discovery before pull
1378
1378
1379 The function is added to the step -> function mapping and appended to the
1379 The function is added to the step -> function mapping and appended to the
1380 list of steps. Beware that decorated function will be added in order (this
1380 list of steps. Beware that decorated function will be added in order (this
1381 may matter).
1381 may matter).
1382
1382
1383 You can only use this decorator for a new step, if you want to wrap a step
1383 You can only use this decorator for a new step, if you want to wrap a step
1384 from an extension, change the pulldiscovery dictionary directly."""
1384 from an extension, change the pulldiscovery dictionary directly."""
1385 def dec(func):
1385 def dec(func):
1386 assert stepname not in pulldiscoverymapping
1386 assert stepname not in pulldiscoverymapping
1387 pulldiscoverymapping[stepname] = func
1387 pulldiscoverymapping[stepname] = func
1388 pulldiscoveryorder.append(stepname)
1388 pulldiscoveryorder.append(stepname)
1389 return func
1389 return func
1390 return dec
1390 return dec
1391
1391
1392 def _pulldiscovery(pullop):
1392 def _pulldiscovery(pullop):
1393 """Run all discovery steps"""
1393 """Run all discovery steps"""
1394 for stepname in pulldiscoveryorder:
1394 for stepname in pulldiscoveryorder:
1395 step = pulldiscoverymapping[stepname]
1395 step = pulldiscoverymapping[stepname]
1396 step(pullop)
1396 step(pullop)
1397
1397
1398 @pulldiscovery('b1:bookmarks')
1398 @pulldiscovery('b1:bookmarks')
1399 def _pullbookmarkbundle1(pullop):
1399 def _pullbookmarkbundle1(pullop):
1400 """fetch bookmark data in bundle1 case
1400 """fetch bookmark data in bundle1 case
1401
1401
1402 If not using bundle2, we have to fetch bookmarks before changeset
1402 If not using bundle2, we have to fetch bookmarks before changeset
1403 discovery to reduce the chance and impact of race conditions."""
1403 discovery to reduce the chance and impact of race conditions."""
1404 if pullop.remotebookmarks is not None:
1404 if pullop.remotebookmarks is not None:
1405 return
1405 return
1406 if pullop.canusebundle2 and 'listkeys' in pullop.remotebundle2caps:
1406 if pullop.canusebundle2 and 'listkeys' in pullop.remotebundle2caps:
1407 # all known bundle2 servers now support listkeys, but lets be nice with
1407 # all known bundle2 servers now support listkeys, but lets be nice with
1408 # new implementation.
1408 # new implementation.
1409 return
1409 return
1410 books = pullop.remote.listkeys('bookmarks')
1410 books = pullop.remote.listkeys('bookmarks')
1411 pullop.remotebookmarks = bookmod.unhexlifybookmarks(books)
1411 pullop.remotebookmarks = bookmod.unhexlifybookmarks(books)
1412
1412
1413
1413
1414 @pulldiscovery('changegroup')
1414 @pulldiscovery('changegroup')
1415 def _pulldiscoverychangegroup(pullop):
1415 def _pulldiscoverychangegroup(pullop):
1416 """discovery phase for the pull
1416 """discovery phase for the pull
1417
1417
1418 Current handle changeset discovery only, will change handle all discovery
1418 Current handle changeset discovery only, will change handle all discovery
1419 at some point."""
1419 at some point."""
1420 tmp = discovery.findcommonincoming(pullop.repo,
1420 tmp = discovery.findcommonincoming(pullop.repo,
1421 pullop.remote,
1421 pullop.remote,
1422 heads=pullop.heads,
1422 heads=pullop.heads,
1423 force=pullop.force)
1423 force=pullop.force)
1424 common, fetch, rheads = tmp
1424 common, fetch, rheads = tmp
1425 nm = pullop.repo.unfiltered().changelog.nodemap
1425 nm = pullop.repo.unfiltered().changelog.nodemap
1426 if fetch and rheads:
1426 if fetch and rheads:
1427 # If a remote heads is filtered locally, put in back in common.
1427 # If a remote heads is filtered locally, put in back in common.
1428 #
1428 #
1429 # This is a hackish solution to catch most of "common but locally
1429 # This is a hackish solution to catch most of "common but locally
1430 # hidden situation". We do not performs discovery on unfiltered
1430 # hidden situation". We do not performs discovery on unfiltered
1431 # repository because it end up doing a pathological amount of round
1431 # repository because it end up doing a pathological amount of round
1432 # trip for w huge amount of changeset we do not care about.
1432 # trip for w huge amount of changeset we do not care about.
1433 #
1433 #
1434 # If a set of such "common but filtered" changeset exist on the server
1434 # If a set of such "common but filtered" changeset exist on the server
1435 # but are not including a remote heads, we'll not be able to detect it,
1435 # but are not including a remote heads, we'll not be able to detect it,
1436 scommon = set(common)
1436 scommon = set(common)
1437 for n in rheads:
1437 for n in rheads:
1438 if n in nm:
1438 if n in nm:
1439 if n not in scommon:
1439 if n not in scommon:
1440 common.append(n)
1440 common.append(n)
1441 if set(rheads).issubset(set(common)):
1441 if set(rheads).issubset(set(common)):
1442 fetch = []
1442 fetch = []
1443 pullop.common = common
1443 pullop.common = common
1444 pullop.fetch = fetch
1444 pullop.fetch = fetch
1445 pullop.rheads = rheads
1445 pullop.rheads = rheads
1446
1446
1447 def _pullbundle2(pullop):
1447 def _pullbundle2(pullop):
1448 """pull data using bundle2
1448 """pull data using bundle2
1449
1449
1450 For now, the only supported data are changegroup."""
1450 For now, the only supported data are changegroup."""
1451 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
1451 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
1452
1452
1453 # At the moment we don't do stream clones over bundle2. If that is
1453 # At the moment we don't do stream clones over bundle2. If that is
1454 # implemented then here's where the check for that will go.
1454 # implemented then here's where the check for that will go.
1455 streaming = False
1455 streaming = False
1456
1456
1457 # pulling changegroup
1457 # pulling changegroup
1458 pullop.stepsdone.add('changegroup')
1458 pullop.stepsdone.add('changegroup')
1459
1459
1460 kwargs['common'] = pullop.common
1460 kwargs['common'] = pullop.common
1461 kwargs['heads'] = pullop.heads or pullop.rheads
1461 kwargs['heads'] = pullop.heads or pullop.rheads
1462 kwargs['cg'] = pullop.fetch
1462 kwargs['cg'] = pullop.fetch
1463
1463
1464 ui = pullop.repo.ui
1464 ui = pullop.repo.ui
1465 legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
1465 legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
1466 hasbinaryphase = 'heads' in pullop.remotebundle2caps.get('phases', ())
1466 hasbinaryphase = 'heads' in pullop.remotebundle2caps.get('phases', ())
1467 if (not legacyphase and hasbinaryphase):
1467 if (not legacyphase and hasbinaryphase):
1468 kwargs['phases'] = True
1468 kwargs['phases'] = True
1469 pullop.stepsdone.add('phases')
1469 pullop.stepsdone.add('phases')
1470
1470
1471 bookmarksrequested = False
1471 bookmarksrequested = False
1472 legacybookmark = 'bookmarks' in ui.configlist('devel', 'legacy.exchange')
1472 legacybookmark = 'bookmarks' in ui.configlist('devel', 'legacy.exchange')
1473 hasbinarybook = 'bookmarks' in pullop.remotebundle2caps
1473 hasbinarybook = 'bookmarks' in pullop.remotebundle2caps
1474
1474
1475 if pullop.remotebookmarks is not None:
1475 if pullop.remotebookmarks is not None:
1476 pullop.stepsdone.add('request-bookmarks')
1476 pullop.stepsdone.add('request-bookmarks')
1477
1477
1478 if ('request-bookmarks' not in pullop.stepsdone
1478 if ('request-bookmarks' not in pullop.stepsdone
1479 and pullop.remotebookmarks is None
1479 and pullop.remotebookmarks is None
1480 and not legacybookmark and hasbinarybook):
1480 and not legacybookmark and hasbinarybook):
1481 kwargs['bookmarks'] = True
1481 kwargs['bookmarks'] = True
1482 bookmarksrequested = True
1482 bookmarksrequested = True
1483
1483
1484 if 'listkeys' in pullop.remotebundle2caps:
1484 if 'listkeys' in pullop.remotebundle2caps:
1485 if 'phases' not in pullop.stepsdone:
1485 if 'phases' not in pullop.stepsdone:
1486 kwargs['listkeys'] = ['phases']
1486 kwargs['listkeys'] = ['phases']
1487 if 'request-bookmarks' not in pullop.stepsdone:
1487 if 'request-bookmarks' not in pullop.stepsdone:
1488 # make sure to always includes bookmark data when migrating
1488 # make sure to always includes bookmark data when migrating
1489 # `hg incoming --bundle` to using this function.
1489 # `hg incoming --bundle` to using this function.
1490 pullop.stepsdone.add('request-bookmarks')
1490 pullop.stepsdone.add('request-bookmarks')
1491 kwargs.setdefault('listkeys', []).append('bookmarks')
1491 kwargs.setdefault('listkeys', []).append('bookmarks')
1492
1492
1493 # If this is a full pull / clone and the server supports the clone bundles
1493 # If this is a full pull / clone and the server supports the clone bundles
1494 # feature, tell the server whether we attempted a clone bundle. The
1494 # feature, tell the server whether we attempted a clone bundle. The
1495 # presence of this flag indicates the client supports clone bundles. This
1495 # presence of this flag indicates the client supports clone bundles. This
1496 # will enable the server to treat clients that support clone bundles
1496 # will enable the server to treat clients that support clone bundles
1497 # differently from those that don't.
1497 # differently from those that don't.
1498 if (pullop.remote.capable('clonebundles')
1498 if (pullop.remote.capable('clonebundles')
1499 and pullop.heads is None and list(pullop.common) == [nullid]):
1499 and pullop.heads is None and list(pullop.common) == [nullid]):
1500 kwargs['cbattempted'] = pullop.clonebundleattempted
1500 kwargs['cbattempted'] = pullop.clonebundleattempted
1501
1501
1502 if streaming:
1502 if streaming:
1503 pullop.repo.ui.status(_('streaming all changes\n'))
1503 pullop.repo.ui.status(_('streaming all changes\n'))
1504 elif not pullop.fetch:
1504 elif not pullop.fetch:
1505 pullop.repo.ui.status(_("no changes found\n"))
1505 pullop.repo.ui.status(_("no changes found\n"))
1506 pullop.cgresult = 0
1506 pullop.cgresult = 0
1507 else:
1507 else:
1508 if pullop.heads is None and list(pullop.common) == [nullid]:
1508 if pullop.heads is None and list(pullop.common) == [nullid]:
1509 pullop.repo.ui.status(_("requesting all changes\n"))
1509 pullop.repo.ui.status(_("requesting all changes\n"))
1510 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1510 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1511 remoteversions = bundle2.obsmarkersversion(pullop.remotebundle2caps)
1511 remoteversions = bundle2.obsmarkersversion(pullop.remotebundle2caps)
1512 if obsolete.commonversion(remoteversions) is not None:
1512 if obsolete.commonversion(remoteversions) is not None:
1513 kwargs['obsmarkers'] = True
1513 kwargs['obsmarkers'] = True
1514 pullop.stepsdone.add('obsmarkers')
1514 pullop.stepsdone.add('obsmarkers')
1515 _pullbundle2extraprepare(pullop, kwargs)
1515 _pullbundle2extraprepare(pullop, kwargs)
1516 bundle = pullop.remote.getbundle('pull', **pycompat.strkwargs(kwargs))
1516 bundle = pullop.remote.getbundle('pull', **pycompat.strkwargs(kwargs))
1517 try:
1517 try:
1518 op = bundle2.bundleoperation(pullop.repo, pullop.gettransaction)
1518 op = bundle2.bundleoperation(pullop.repo, pullop.gettransaction)
1519 op.modes['bookmarks'] = 'records'
1519 op.modes['bookmarks'] = 'records'
1520 bundle2.processbundle(pullop.repo, bundle, op=op)
1520 bundle2.processbundle(pullop.repo, bundle, op=op)
1521 except bundle2.AbortFromPart as exc:
1521 except bundle2.AbortFromPart as exc:
1522 pullop.repo.ui.status(_('remote: abort: %s\n') % exc)
1522 pullop.repo.ui.status(_('remote: abort: %s\n') % exc)
1523 raise error.Abort(_('pull failed on remote'), hint=exc.hint)
1523 raise error.Abort(_('pull failed on remote'), hint=exc.hint)
1524 except error.BundleValueError as exc:
1524 except error.BundleValueError as exc:
1525 raise error.Abort(_('missing support for %s') % exc)
1525 raise error.Abort(_('missing support for %s') % exc)
1526
1526
1527 if pullop.fetch:
1527 if pullop.fetch:
1528 pullop.cgresult = bundle2.combinechangegroupresults(op)
1528 pullop.cgresult = bundle2.combinechangegroupresults(op)
1529
1529
1530 # processing phases change
1530 # processing phases change
1531 for namespace, value in op.records['listkeys']:
1531 for namespace, value in op.records['listkeys']:
1532 if namespace == 'phases':
1532 if namespace == 'phases':
1533 _pullapplyphases(pullop, value)
1533 _pullapplyphases(pullop, value)
1534
1534
1535 # processing bookmark update
1535 # processing bookmark update
1536 if bookmarksrequested:
1536 if bookmarksrequested:
1537 books = {}
1537 books = {}
1538 for record in op.records['bookmarks']:
1538 for record in op.records['bookmarks']:
1539 books[record['bookmark']] = record["node"]
1539 books[record['bookmark']] = record["node"]
1540 pullop.remotebookmarks = books
1540 pullop.remotebookmarks = books
1541 else:
1541 else:
1542 for namespace, value in op.records['listkeys']:
1542 for namespace, value in op.records['listkeys']:
1543 if namespace == 'bookmarks':
1543 if namespace == 'bookmarks':
1544 pullop.remotebookmarks = bookmod.unhexlifybookmarks(value)
1544 pullop.remotebookmarks = bookmod.unhexlifybookmarks(value)
1545
1545
1546 # bookmark data were either already there or pulled in the bundle
1546 # bookmark data were either already there or pulled in the bundle
1547 if pullop.remotebookmarks is not None:
1547 if pullop.remotebookmarks is not None:
1548 _pullbookmarks(pullop)
1548 _pullbookmarks(pullop)
1549
1549
1550 def _pullbundle2extraprepare(pullop, kwargs):
1550 def _pullbundle2extraprepare(pullop, kwargs):
1551 """hook function so that extensions can extend the getbundle call"""
1551 """hook function so that extensions can extend the getbundle call"""
1552
1552
1553 def _pullchangeset(pullop):
1553 def _pullchangeset(pullop):
1554 """pull changeset from unbundle into the local repo"""
1554 """pull changeset from unbundle into the local repo"""
1555 # We delay the open of the transaction as late as possible so we
1555 # We delay the open of the transaction as late as possible so we
1556 # don't open transaction for nothing or you break future useful
1556 # don't open transaction for nothing or you break future useful
1557 # rollback call
1557 # rollback call
1558 if 'changegroup' in pullop.stepsdone:
1558 if 'changegroup' in pullop.stepsdone:
1559 return
1559 return
1560 pullop.stepsdone.add('changegroup')
1560 pullop.stepsdone.add('changegroup')
1561 if not pullop.fetch:
1561 if not pullop.fetch:
1562 pullop.repo.ui.status(_("no changes found\n"))
1562 pullop.repo.ui.status(_("no changes found\n"))
1563 pullop.cgresult = 0
1563 pullop.cgresult = 0
1564 return
1564 return
1565 tr = pullop.gettransaction()
1565 tr = pullop.gettransaction()
1566 if pullop.heads is None and list(pullop.common) == [nullid]:
1566 if pullop.heads is None and list(pullop.common) == [nullid]:
1567 pullop.repo.ui.status(_("requesting all changes\n"))
1567 pullop.repo.ui.status(_("requesting all changes\n"))
1568 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1568 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1569 # issue1320, avoid a race if remote changed after discovery
1569 # issue1320, avoid a race if remote changed after discovery
1570 pullop.heads = pullop.rheads
1570 pullop.heads = pullop.rheads
1571
1571
1572 if pullop.remote.capable('getbundle'):
1572 if pullop.remote.capable('getbundle'):
1573 # TODO: get bundlecaps from remote
1573 # TODO: get bundlecaps from remote
1574 cg = pullop.remote.getbundle('pull', common=pullop.common,
1574 cg = pullop.remote.getbundle('pull', common=pullop.common,
1575 heads=pullop.heads or pullop.rheads)
1575 heads=pullop.heads or pullop.rheads)
1576 elif pullop.heads is None:
1576 elif pullop.heads is None:
1577 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
1577 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
1578 elif not pullop.remote.capable('changegroupsubset'):
1578 elif not pullop.remote.capable('changegroupsubset'):
1579 raise error.Abort(_("partial pull cannot be done because "
1579 raise error.Abort(_("partial pull cannot be done because "
1580 "other repository doesn't support "
1580 "other repository doesn't support "
1581 "changegroupsubset."))
1581 "changegroupsubset."))
1582 else:
1582 else:
1583 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
1583 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
1584 bundleop = bundle2.applybundle(pullop.repo, cg, tr, 'pull',
1584 bundleop = bundle2.applybundle(pullop.repo, cg, tr, 'pull',
1585 pullop.remote.url())
1585 pullop.remote.url())
1586 pullop.cgresult = bundle2.combinechangegroupresults(bundleop)
1586 pullop.cgresult = bundle2.combinechangegroupresults(bundleop)
1587
1587
1588 def _pullphase(pullop):
1588 def _pullphase(pullop):
1589 # Get remote phases data from remote
1589 # Get remote phases data from remote
1590 if 'phases' in pullop.stepsdone:
1590 if 'phases' in pullop.stepsdone:
1591 return
1591 return
1592 remotephases = pullop.remote.listkeys('phases')
1592 remotephases = pullop.remote.listkeys('phases')
1593 _pullapplyphases(pullop, remotephases)
1593 _pullapplyphases(pullop, remotephases)
1594
1594
1595 def _pullapplyphases(pullop, remotephases):
1595 def _pullapplyphases(pullop, remotephases):
1596 """apply phase movement from observed remote state"""
1596 """apply phase movement from observed remote state"""
1597 if 'phases' in pullop.stepsdone:
1597 if 'phases' in pullop.stepsdone:
1598 return
1598 return
1599 pullop.stepsdone.add('phases')
1599 pullop.stepsdone.add('phases')
1600 publishing = bool(remotephases.get('publishing', False))
1600 publishing = bool(remotephases.get('publishing', False))
1601 if remotephases and not publishing:
1601 if remotephases and not publishing:
1602 # remote is new and non-publishing
1602 # remote is new and non-publishing
1603 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1603 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1604 pullop.pulledsubset,
1604 pullop.pulledsubset,
1605 remotephases)
1605 remotephases)
1606 dheads = pullop.pulledsubset
1606 dheads = pullop.pulledsubset
1607 else:
1607 else:
1608 # Remote is old or publishing all common changesets
1608 # Remote is old or publishing all common changesets
1609 # should be seen as public
1609 # should be seen as public
1610 pheads = pullop.pulledsubset
1610 pheads = pullop.pulledsubset
1611 dheads = []
1611 dheads = []
1612 unfi = pullop.repo.unfiltered()
1612 unfi = pullop.repo.unfiltered()
1613 phase = unfi._phasecache.phase
1613 phase = unfi._phasecache.phase
1614 rev = unfi.changelog.nodemap.get
1614 rev = unfi.changelog.nodemap.get
1615 public = phases.public
1615 public = phases.public
1616 draft = phases.draft
1616 draft = phases.draft
1617
1617
1618 # exclude changesets already public locally and update the others
1618 # exclude changesets already public locally and update the others
1619 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1619 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1620 if pheads:
1620 if pheads:
1621 tr = pullop.gettransaction()
1621 tr = pullop.gettransaction()
1622 phases.advanceboundary(pullop.repo, tr, public, pheads)
1622 phases.advanceboundary(pullop.repo, tr, public, pheads)
1623
1623
1624 # exclude changesets already draft locally and update the others
1624 # exclude changesets already draft locally and update the others
1625 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1625 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1626 if dheads:
1626 if dheads:
1627 tr = pullop.gettransaction()
1627 tr = pullop.gettransaction()
1628 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1628 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1629
1629
1630 def _pullbookmarks(pullop):
1630 def _pullbookmarks(pullop):
1631 """process the remote bookmark information to update the local one"""
1631 """process the remote bookmark information to update the local one"""
1632 if 'bookmarks' in pullop.stepsdone:
1632 if 'bookmarks' in pullop.stepsdone:
1633 return
1633 return
1634 pullop.stepsdone.add('bookmarks')
1634 pullop.stepsdone.add('bookmarks')
1635 repo = pullop.repo
1635 repo = pullop.repo
1636 remotebookmarks = pullop.remotebookmarks
1636 remotebookmarks = pullop.remotebookmarks
1637 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1637 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1638 pullop.remote.url(),
1638 pullop.remote.url(),
1639 pullop.gettransaction,
1639 pullop.gettransaction,
1640 explicit=pullop.explicitbookmarks)
1640 explicit=pullop.explicitbookmarks)
1641
1641
1642 def _pullobsolete(pullop):
1642 def _pullobsolete(pullop):
1643 """utility function to pull obsolete markers from a remote
1643 """utility function to pull obsolete markers from a remote
1644
1644
1645 The `gettransaction` is function that return the pull transaction, creating
1645 The `gettransaction` is function that return the pull transaction, creating
1646 one if necessary. We return the transaction to inform the calling code that
1646 one if necessary. We return the transaction to inform the calling code that
1647 a new transaction have been created (when applicable).
1647 a new transaction have been created (when applicable).
1648
1648
1649 Exists mostly to allow overriding for experimentation purpose"""
1649 Exists mostly to allow overriding for experimentation purpose"""
1650 if 'obsmarkers' in pullop.stepsdone:
1650 if 'obsmarkers' in pullop.stepsdone:
1651 return
1651 return
1652 pullop.stepsdone.add('obsmarkers')
1652 pullop.stepsdone.add('obsmarkers')
1653 tr = None
1653 tr = None
1654 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1654 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1655 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1655 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1656 remoteobs = pullop.remote.listkeys('obsolete')
1656 remoteobs = pullop.remote.listkeys('obsolete')
1657 if 'dump0' in remoteobs:
1657 if 'dump0' in remoteobs:
1658 tr = pullop.gettransaction()
1658 tr = pullop.gettransaction()
1659 markers = []
1659 markers = []
1660 for key in sorted(remoteobs, reverse=True):
1660 for key in sorted(remoteobs, reverse=True):
1661 if key.startswith('dump'):
1661 if key.startswith('dump'):
1662 data = util.b85decode(remoteobs[key])
1662 data = util.b85decode(remoteobs[key])
1663 version, newmarks = obsolete._readmarkers(data)
1663 version, newmarks = obsolete._readmarkers(data)
1664 markers += newmarks
1664 markers += newmarks
1665 if markers:
1665 if markers:
1666 pullop.repo.obsstore.add(tr, markers)
1666 pullop.repo.obsstore.add(tr, markers)
1667 pullop.repo.invalidatevolatilesets()
1667 pullop.repo.invalidatevolatilesets()
1668 return tr
1668 return tr
1669
1669
1670 def caps20to10(repo):
1670 def caps20to10(repo):
1671 """return a set with appropriate options to use bundle20 during getbundle"""
1671 """return a set with appropriate options to use bundle20 during getbundle"""
1672 caps = {'HG20'}
1672 caps = {'HG20'}
1673 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
1673 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
1674 caps.add('bundle2=' + urlreq.quote(capsblob))
1674 caps.add('bundle2=' + urlreq.quote(capsblob))
1675 return caps
1675 return caps
1676
1676
1677 # List of names of steps to perform for a bundle2 for getbundle, order matters.
1677 # List of names of steps to perform for a bundle2 for getbundle, order matters.
1678 getbundle2partsorder = []
1678 getbundle2partsorder = []
1679
1679
1680 # Mapping between step name and function
1680 # Mapping between step name and function
1681 #
1681 #
1682 # This exists to help extensions wrap steps if necessary
1682 # This exists to help extensions wrap steps if necessary
1683 getbundle2partsmapping = {}
1683 getbundle2partsmapping = {}
1684
1684
1685 def getbundle2partsgenerator(stepname, idx=None):
1685 def getbundle2partsgenerator(stepname, idx=None):
1686 """decorator for function generating bundle2 part for getbundle
1686 """decorator for function generating bundle2 part for getbundle
1687
1687
1688 The function is added to the step -> function mapping and appended to the
1688 The function is added to the step -> function mapping and appended to the
1689 list of steps. Beware that decorated functions will be added in order
1689 list of steps. Beware that decorated functions will be added in order
1690 (this may matter).
1690 (this may matter).
1691
1691
1692 You can only use this decorator for new steps, if you want to wrap a step
1692 You can only use this decorator for new steps, if you want to wrap a step
1693 from an extension, attack the getbundle2partsmapping dictionary directly."""
1693 from an extension, attack the getbundle2partsmapping dictionary directly."""
1694 def dec(func):
1694 def dec(func):
1695 assert stepname not in getbundle2partsmapping
1695 assert stepname not in getbundle2partsmapping
1696 getbundle2partsmapping[stepname] = func
1696 getbundle2partsmapping[stepname] = func
1697 if idx is None:
1697 if idx is None:
1698 getbundle2partsorder.append(stepname)
1698 getbundle2partsorder.append(stepname)
1699 else:
1699 else:
1700 getbundle2partsorder.insert(idx, stepname)
1700 getbundle2partsorder.insert(idx, stepname)
1701 return func
1701 return func
1702 return dec
1702 return dec
1703
1703
1704 def bundle2requested(bundlecaps):
1704 def bundle2requested(bundlecaps):
1705 if bundlecaps is not None:
1705 if bundlecaps is not None:
1706 return any(cap.startswith('HG2') for cap in bundlecaps)
1706 return any(cap.startswith('HG2') for cap in bundlecaps)
1707 return False
1707 return False
1708
1708
1709 def getbundlechunks(repo, source, heads=None, common=None, bundlecaps=None,
1709 def getbundlechunks(repo, source, heads=None, common=None, bundlecaps=None,
1710 **kwargs):
1710 **kwargs):
1711 """Return chunks constituting a bundle's raw data.
1711 """Return chunks constituting a bundle's raw data.
1712
1712
1713 Could be a bundle HG10 or a bundle HG20 depending on bundlecaps
1713 Could be a bundle HG10 or a bundle HG20 depending on bundlecaps
1714 passed.
1714 passed.
1715
1715
1716 Returns an iterator over raw chunks (of varying sizes).
1716 Returns an iterator over raw chunks (of varying sizes).
1717 """
1717 """
1718 kwargs = pycompat.byteskwargs(kwargs)
1718 kwargs = pycompat.byteskwargs(kwargs)
1719 usebundle2 = bundle2requested(bundlecaps)
1719 usebundle2 = bundle2requested(bundlecaps)
1720 # bundle10 case
1720 # bundle10 case
1721 if not usebundle2:
1721 if not usebundle2:
1722 if bundlecaps and not kwargs.get('cg', True):
1722 if bundlecaps and not kwargs.get('cg', True):
1723 raise ValueError(_('request for bundle10 must include changegroup'))
1723 raise ValueError(_('request for bundle10 must include changegroup'))
1724
1724
1725 if kwargs:
1725 if kwargs:
1726 raise ValueError(_('unsupported getbundle arguments: %s')
1726 raise ValueError(_('unsupported getbundle arguments: %s')
1727 % ', '.join(sorted(kwargs.keys())))
1727 % ', '.join(sorted(kwargs.keys())))
1728 outgoing = _computeoutgoing(repo, heads, common)
1728 outgoing = _computeoutgoing(repo, heads, common)
1729 return changegroup.makestream(repo, outgoing, '01', source,
1729 return changegroup.makestream(repo, outgoing, '01', source,
1730 bundlecaps=bundlecaps)
1730 bundlecaps=bundlecaps)
1731
1731
1732 # bundle20 case
1732 # bundle20 case
1733 b2caps = {}
1733 b2caps = {}
1734 for bcaps in bundlecaps:
1734 for bcaps in bundlecaps:
1735 if bcaps.startswith('bundle2='):
1735 if bcaps.startswith('bundle2='):
1736 blob = urlreq.unquote(bcaps[len('bundle2='):])
1736 blob = urlreq.unquote(bcaps[len('bundle2='):])
1737 b2caps.update(bundle2.decodecaps(blob))
1737 b2caps.update(bundle2.decodecaps(blob))
1738 bundler = bundle2.bundle20(repo.ui, b2caps)
1738 bundler = bundle2.bundle20(repo.ui, b2caps)
1739
1739
1740 kwargs['heads'] = heads
1740 kwargs['heads'] = heads
1741 kwargs['common'] = common
1741 kwargs['common'] = common
1742
1742
1743 for name in getbundle2partsorder:
1743 for name in getbundle2partsorder:
1744 func = getbundle2partsmapping[name]
1744 func = getbundle2partsmapping[name]
1745 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
1745 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
1746 **pycompat.strkwargs(kwargs))
1746 **pycompat.strkwargs(kwargs))
1747
1747
1748 return bundler.getchunks()
1748 return bundler.getchunks()
1749
1749
1750 @getbundle2partsgenerator('stream')
1751 def _getbundlestream(bundler, repo, source, bundlecaps=None,
1752 b2caps=None, heads=None, common=None, **kwargs):
1753 if not kwargs.get('stream', False):
1754 return
1755 filecount, bytecount, it = streamclone.generatev2(repo)
1756 requirements = ' '.join(repo.requirements)
1757 part = bundler.newpart('stream', data=it)
1758 part.addparam('bytecount', '%d' % bytecount, mandatory=True)
1759 part.addparam('filecount', '%d' % filecount, mandatory=True)
1760 part.addparam('requirements', requirements, mandatory=True)
1761 part.addparam('version', 'v2', mandatory=True)
1762
1750 @getbundle2partsgenerator('changegroup')
1763 @getbundle2partsgenerator('changegroup')
1751 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
1764 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
1752 b2caps=None, heads=None, common=None, **kwargs):
1765 b2caps=None, heads=None, common=None, **kwargs):
1753 """add a changegroup part to the requested bundle"""
1766 """add a changegroup part to the requested bundle"""
1754 cgstream = None
1767 cgstream = None
1755 if kwargs.get(r'cg', True):
1768 if kwargs.get(r'cg', True):
1756 # build changegroup bundle here.
1769 # build changegroup bundle here.
1757 version = '01'
1770 version = '01'
1758 cgversions = b2caps.get('changegroup')
1771 cgversions = b2caps.get('changegroup')
1759 if cgversions: # 3.1 and 3.2 ship with an empty value
1772 if cgversions: # 3.1 and 3.2 ship with an empty value
1760 cgversions = [v for v in cgversions
1773 cgversions = [v for v in cgversions
1761 if v in changegroup.supportedoutgoingversions(repo)]
1774 if v in changegroup.supportedoutgoingversions(repo)]
1762 if not cgversions:
1775 if not cgversions:
1763 raise ValueError(_('no common changegroup version'))
1776 raise ValueError(_('no common changegroup version'))
1764 version = max(cgversions)
1777 version = max(cgversions)
1765 outgoing = _computeoutgoing(repo, heads, common)
1778 outgoing = _computeoutgoing(repo, heads, common)
1766 if outgoing.missing:
1779 if outgoing.missing:
1767 cgstream = changegroup.makestream(repo, outgoing, version, source,
1780 cgstream = changegroup.makestream(repo, outgoing, version, source,
1768 bundlecaps=bundlecaps)
1781 bundlecaps=bundlecaps)
1769
1782
1770 if cgstream:
1783 if cgstream:
1771 part = bundler.newpart('changegroup', data=cgstream)
1784 part = bundler.newpart('changegroup', data=cgstream)
1772 if cgversions:
1785 if cgversions:
1773 part.addparam('version', version)
1786 part.addparam('version', version)
1774 part.addparam('nbchanges', '%d' % len(outgoing.missing),
1787 part.addparam('nbchanges', '%d' % len(outgoing.missing),
1775 mandatory=False)
1788 mandatory=False)
1776 if 'treemanifest' in repo.requirements:
1789 if 'treemanifest' in repo.requirements:
1777 part.addparam('treemanifest', '1')
1790 part.addparam('treemanifest', '1')
1778
1791
1779 @getbundle2partsgenerator('bookmarks')
1792 @getbundle2partsgenerator('bookmarks')
1780 def _getbundlebookmarkpart(bundler, repo, source, bundlecaps=None,
1793 def _getbundlebookmarkpart(bundler, repo, source, bundlecaps=None,
1781 b2caps=None, **kwargs):
1794 b2caps=None, **kwargs):
1782 """add a bookmark part to the requested bundle"""
1795 """add a bookmark part to the requested bundle"""
1783 if not kwargs.get(r'bookmarks', False):
1796 if not kwargs.get(r'bookmarks', False):
1784 return
1797 return
1785 if 'bookmarks' not in b2caps:
1798 if 'bookmarks' not in b2caps:
1786 raise ValueError(_('no common bookmarks exchange method'))
1799 raise ValueError(_('no common bookmarks exchange method'))
1787 books = bookmod.listbinbookmarks(repo)
1800 books = bookmod.listbinbookmarks(repo)
1788 data = bookmod.binaryencode(books)
1801 data = bookmod.binaryencode(books)
1789 if data:
1802 if data:
1790 bundler.newpart('bookmarks', data=data)
1803 bundler.newpart('bookmarks', data=data)
1791
1804
1792 @getbundle2partsgenerator('listkeys')
1805 @getbundle2partsgenerator('listkeys')
1793 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
1806 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
1794 b2caps=None, **kwargs):
1807 b2caps=None, **kwargs):
1795 """add parts containing listkeys namespaces to the requested bundle"""
1808 """add parts containing listkeys namespaces to the requested bundle"""
1796 listkeys = kwargs.get(r'listkeys', ())
1809 listkeys = kwargs.get(r'listkeys', ())
1797 for namespace in listkeys:
1810 for namespace in listkeys:
1798 part = bundler.newpart('listkeys')
1811 part = bundler.newpart('listkeys')
1799 part.addparam('namespace', namespace)
1812 part.addparam('namespace', namespace)
1800 keys = repo.listkeys(namespace).items()
1813 keys = repo.listkeys(namespace).items()
1801 part.data = pushkey.encodekeys(keys)
1814 part.data = pushkey.encodekeys(keys)
1802
1815
1803 @getbundle2partsgenerator('obsmarkers')
1816 @getbundle2partsgenerator('obsmarkers')
1804 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
1817 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
1805 b2caps=None, heads=None, **kwargs):
1818 b2caps=None, heads=None, **kwargs):
1806 """add an obsolescence markers part to the requested bundle"""
1819 """add an obsolescence markers part to the requested bundle"""
1807 if kwargs.get(r'obsmarkers', False):
1820 if kwargs.get(r'obsmarkers', False):
1808 if heads is None:
1821 if heads is None:
1809 heads = repo.heads()
1822 heads = repo.heads()
1810 subset = [c.node() for c in repo.set('::%ln', heads)]
1823 subset = [c.node() for c in repo.set('::%ln', heads)]
1811 markers = repo.obsstore.relevantmarkers(subset)
1824 markers = repo.obsstore.relevantmarkers(subset)
1812 markers = sorted(markers)
1825 markers = sorted(markers)
1813 bundle2.buildobsmarkerspart(bundler, markers)
1826 bundle2.buildobsmarkerspart(bundler, markers)
1814
1827
1815 @getbundle2partsgenerator('phases')
1828 @getbundle2partsgenerator('phases')
1816 def _getbundlephasespart(bundler, repo, source, bundlecaps=None,
1829 def _getbundlephasespart(bundler, repo, source, bundlecaps=None,
1817 b2caps=None, heads=None, **kwargs):
1830 b2caps=None, heads=None, **kwargs):
1818 """add phase heads part to the requested bundle"""
1831 """add phase heads part to the requested bundle"""
1819 if kwargs.get(r'phases', False):
1832 if kwargs.get(r'phases', False):
1820 if not 'heads' in b2caps.get('phases'):
1833 if not 'heads' in b2caps.get('phases'):
1821 raise ValueError(_('no common phases exchange method'))
1834 raise ValueError(_('no common phases exchange method'))
1822 if heads is None:
1835 if heads is None:
1823 heads = repo.heads()
1836 heads = repo.heads()
1824
1837
1825 headsbyphase = collections.defaultdict(set)
1838 headsbyphase = collections.defaultdict(set)
1826 if repo.publishing():
1839 if repo.publishing():
1827 headsbyphase[phases.public] = heads
1840 headsbyphase[phases.public] = heads
1828 else:
1841 else:
1829 # find the appropriate heads to move
1842 # find the appropriate heads to move
1830
1843
1831 phase = repo._phasecache.phase
1844 phase = repo._phasecache.phase
1832 node = repo.changelog.node
1845 node = repo.changelog.node
1833 rev = repo.changelog.rev
1846 rev = repo.changelog.rev
1834 for h in heads:
1847 for h in heads:
1835 headsbyphase[phase(repo, rev(h))].add(h)
1848 headsbyphase[phase(repo, rev(h))].add(h)
1836 seenphases = list(headsbyphase.keys())
1849 seenphases = list(headsbyphase.keys())
1837
1850
1838 # We do not handle anything but public and draft phase for now)
1851 # We do not handle anything but public and draft phase for now)
1839 if seenphases:
1852 if seenphases:
1840 assert max(seenphases) <= phases.draft
1853 assert max(seenphases) <= phases.draft
1841
1854
1842 # if client is pulling non-public changesets, we need to find
1855 # if client is pulling non-public changesets, we need to find
1843 # intermediate public heads.
1856 # intermediate public heads.
1844 draftheads = headsbyphase.get(phases.draft, set())
1857 draftheads = headsbyphase.get(phases.draft, set())
1845 if draftheads:
1858 if draftheads:
1846 publicheads = headsbyphase.get(phases.public, set())
1859 publicheads = headsbyphase.get(phases.public, set())
1847
1860
1848 revset = 'heads(only(%ln, %ln) and public())'
1861 revset = 'heads(only(%ln, %ln) and public())'
1849 extraheads = repo.revs(revset, draftheads, publicheads)
1862 extraheads = repo.revs(revset, draftheads, publicheads)
1850 for r in extraheads:
1863 for r in extraheads:
1851 headsbyphase[phases.public].add(node(r))
1864 headsbyphase[phases.public].add(node(r))
1852
1865
1853 # transform data in a format used by the encoding function
1866 # transform data in a format used by the encoding function
1854 phasemapping = []
1867 phasemapping = []
1855 for phase in phases.allphases:
1868 for phase in phases.allphases:
1856 phasemapping.append(sorted(headsbyphase[phase]))
1869 phasemapping.append(sorted(headsbyphase[phase]))
1857
1870
1858 # generate the actual part
1871 # generate the actual part
1859 phasedata = phases.binaryencode(phasemapping)
1872 phasedata = phases.binaryencode(phasemapping)
1860 bundler.newpart('phase-heads', data=phasedata)
1873 bundler.newpart('phase-heads', data=phasedata)
1861
1874
1862 @getbundle2partsgenerator('hgtagsfnodes')
1875 @getbundle2partsgenerator('hgtagsfnodes')
1863 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
1876 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
1864 b2caps=None, heads=None, common=None,
1877 b2caps=None, heads=None, common=None,
1865 **kwargs):
1878 **kwargs):
1866 """Transfer the .hgtags filenodes mapping.
1879 """Transfer the .hgtags filenodes mapping.
1867
1880
1868 Only values for heads in this bundle will be transferred.
1881 Only values for heads in this bundle will be transferred.
1869
1882
1870 The part data consists of pairs of 20 byte changeset node and .hgtags
1883 The part data consists of pairs of 20 byte changeset node and .hgtags
1871 filenodes raw values.
1884 filenodes raw values.
1872 """
1885 """
1873 # Don't send unless:
1886 # Don't send unless:
1874 # - changeset are being exchanged,
1887 # - changeset are being exchanged,
1875 # - the client supports it.
1888 # - the client supports it.
1876 if not (kwargs.get(r'cg', True) and 'hgtagsfnodes' in b2caps):
1889 if not (kwargs.get(r'cg', True) and 'hgtagsfnodes' in b2caps):
1877 return
1890 return
1878
1891
1879 outgoing = _computeoutgoing(repo, heads, common)
1892 outgoing = _computeoutgoing(repo, heads, common)
1880 bundle2.addparttagsfnodescache(repo, bundler, outgoing)
1893 bundle2.addparttagsfnodescache(repo, bundler, outgoing)
1881
1894
1882 def check_heads(repo, their_heads, context):
1895 def check_heads(repo, their_heads, context):
1883 """check if the heads of a repo have been modified
1896 """check if the heads of a repo have been modified
1884
1897
1885 Used by peer for unbundling.
1898 Used by peer for unbundling.
1886 """
1899 """
1887 heads = repo.heads()
1900 heads = repo.heads()
1888 heads_hash = hashlib.sha1(''.join(sorted(heads))).digest()
1901 heads_hash = hashlib.sha1(''.join(sorted(heads))).digest()
1889 if not (their_heads == ['force'] or their_heads == heads or
1902 if not (their_heads == ['force'] or their_heads == heads or
1890 their_heads == ['hashed', heads_hash]):
1903 their_heads == ['hashed', heads_hash]):
1891 # someone else committed/pushed/unbundled while we
1904 # someone else committed/pushed/unbundled while we
1892 # were transferring data
1905 # were transferring data
1893 raise error.PushRaced('repository changed while %s - '
1906 raise error.PushRaced('repository changed while %s - '
1894 'please try again' % context)
1907 'please try again' % context)
1895
1908
1896 def unbundle(repo, cg, heads, source, url):
1909 def unbundle(repo, cg, heads, source, url):
1897 """Apply a bundle to a repo.
1910 """Apply a bundle to a repo.
1898
1911
1899 this function makes sure the repo is locked during the application and have
1912 this function makes sure the repo is locked during the application and have
1900 mechanism to check that no push race occurred between the creation of the
1913 mechanism to check that no push race occurred between the creation of the
1901 bundle and its application.
1914 bundle and its application.
1902
1915
1903 If the push was raced as PushRaced exception is raised."""
1916 If the push was raced as PushRaced exception is raised."""
1904 r = 0
1917 r = 0
1905 # need a transaction when processing a bundle2 stream
1918 # need a transaction when processing a bundle2 stream
1906 # [wlock, lock, tr] - needs to be an array so nested functions can modify it
1919 # [wlock, lock, tr] - needs to be an array so nested functions can modify it
1907 lockandtr = [None, None, None]
1920 lockandtr = [None, None, None]
1908 recordout = None
1921 recordout = None
1909 # quick fix for output mismatch with bundle2 in 3.4
1922 # quick fix for output mismatch with bundle2 in 3.4
1910 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture')
1923 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture')
1911 if url.startswith('remote:http:') or url.startswith('remote:https:'):
1924 if url.startswith('remote:http:') or url.startswith('remote:https:'):
1912 captureoutput = True
1925 captureoutput = True
1913 try:
1926 try:
1914 # note: outside bundle1, 'heads' is expected to be empty and this
1927 # note: outside bundle1, 'heads' is expected to be empty and this
1915 # 'check_heads' call wil be a no-op
1928 # 'check_heads' call wil be a no-op
1916 check_heads(repo, heads, 'uploading changes')
1929 check_heads(repo, heads, 'uploading changes')
1917 # push can proceed
1930 # push can proceed
1918 if not isinstance(cg, bundle2.unbundle20):
1931 if not isinstance(cg, bundle2.unbundle20):
1919 # legacy case: bundle1 (changegroup 01)
1932 # legacy case: bundle1 (changegroup 01)
1920 txnname = "\n".join([source, util.hidepassword(url)])
1933 txnname = "\n".join([source, util.hidepassword(url)])
1921 with repo.lock(), repo.transaction(txnname) as tr:
1934 with repo.lock(), repo.transaction(txnname) as tr:
1922 op = bundle2.applybundle(repo, cg, tr, source, url)
1935 op = bundle2.applybundle(repo, cg, tr, source, url)
1923 r = bundle2.combinechangegroupresults(op)
1936 r = bundle2.combinechangegroupresults(op)
1924 else:
1937 else:
1925 r = None
1938 r = None
1926 try:
1939 try:
1927 def gettransaction():
1940 def gettransaction():
1928 if not lockandtr[2]:
1941 if not lockandtr[2]:
1929 lockandtr[0] = repo.wlock()
1942 lockandtr[0] = repo.wlock()
1930 lockandtr[1] = repo.lock()
1943 lockandtr[1] = repo.lock()
1931 lockandtr[2] = repo.transaction(source)
1944 lockandtr[2] = repo.transaction(source)
1932 lockandtr[2].hookargs['source'] = source
1945 lockandtr[2].hookargs['source'] = source
1933 lockandtr[2].hookargs['url'] = url
1946 lockandtr[2].hookargs['url'] = url
1934 lockandtr[2].hookargs['bundle2'] = '1'
1947 lockandtr[2].hookargs['bundle2'] = '1'
1935 return lockandtr[2]
1948 return lockandtr[2]
1936
1949
1937 # Do greedy locking by default until we're satisfied with lazy
1950 # Do greedy locking by default until we're satisfied with lazy
1938 # locking.
1951 # locking.
1939 if not repo.ui.configbool('experimental', 'bundle2lazylocking'):
1952 if not repo.ui.configbool('experimental', 'bundle2lazylocking'):
1940 gettransaction()
1953 gettransaction()
1941
1954
1942 op = bundle2.bundleoperation(repo, gettransaction,
1955 op = bundle2.bundleoperation(repo, gettransaction,
1943 captureoutput=captureoutput)
1956 captureoutput=captureoutput)
1944 try:
1957 try:
1945 op = bundle2.processbundle(repo, cg, op=op)
1958 op = bundle2.processbundle(repo, cg, op=op)
1946 finally:
1959 finally:
1947 r = op.reply
1960 r = op.reply
1948 if captureoutput and r is not None:
1961 if captureoutput and r is not None:
1949 repo.ui.pushbuffer(error=True, subproc=True)
1962 repo.ui.pushbuffer(error=True, subproc=True)
1950 def recordout(output):
1963 def recordout(output):
1951 r.newpart('output', data=output, mandatory=False)
1964 r.newpart('output', data=output, mandatory=False)
1952 if lockandtr[2] is not None:
1965 if lockandtr[2] is not None:
1953 lockandtr[2].close()
1966 lockandtr[2].close()
1954 except BaseException as exc:
1967 except BaseException as exc:
1955 exc.duringunbundle2 = True
1968 exc.duringunbundle2 = True
1956 if captureoutput and r is not None:
1969 if captureoutput and r is not None:
1957 parts = exc._bundle2salvagedoutput = r.salvageoutput()
1970 parts = exc._bundle2salvagedoutput = r.salvageoutput()
1958 def recordout(output):
1971 def recordout(output):
1959 part = bundle2.bundlepart('output', data=output,
1972 part = bundle2.bundlepart('output', data=output,
1960 mandatory=False)
1973 mandatory=False)
1961 parts.append(part)
1974 parts.append(part)
1962 raise
1975 raise
1963 finally:
1976 finally:
1964 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0])
1977 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0])
1965 if recordout is not None:
1978 if recordout is not None:
1966 recordout(repo.ui.popbuffer())
1979 recordout(repo.ui.popbuffer())
1967 return r
1980 return r
1968
1981
1969 def _maybeapplyclonebundle(pullop):
1982 def _maybeapplyclonebundle(pullop):
1970 """Apply a clone bundle from a remote, if possible."""
1983 """Apply a clone bundle from a remote, if possible."""
1971
1984
1972 repo = pullop.repo
1985 repo = pullop.repo
1973 remote = pullop.remote
1986 remote = pullop.remote
1974
1987
1975 if not repo.ui.configbool('ui', 'clonebundles'):
1988 if not repo.ui.configbool('ui', 'clonebundles'):
1976 return
1989 return
1977
1990
1978 # Only run if local repo is empty.
1991 # Only run if local repo is empty.
1979 if len(repo):
1992 if len(repo):
1980 return
1993 return
1981
1994
1982 if pullop.heads:
1995 if pullop.heads:
1983 return
1996 return
1984
1997
1985 if not remote.capable('clonebundles'):
1998 if not remote.capable('clonebundles'):
1986 return
1999 return
1987
2000
1988 res = remote._call('clonebundles')
2001 res = remote._call('clonebundles')
1989
2002
1990 # If we call the wire protocol command, that's good enough to record the
2003 # If we call the wire protocol command, that's good enough to record the
1991 # attempt.
2004 # attempt.
1992 pullop.clonebundleattempted = True
2005 pullop.clonebundleattempted = True
1993
2006
1994 entries = parseclonebundlesmanifest(repo, res)
2007 entries = parseclonebundlesmanifest(repo, res)
1995 if not entries:
2008 if not entries:
1996 repo.ui.note(_('no clone bundles available on remote; '
2009 repo.ui.note(_('no clone bundles available on remote; '
1997 'falling back to regular clone\n'))
2010 'falling back to regular clone\n'))
1998 return
2011 return
1999
2012
2000 entries = filterclonebundleentries(
2013 entries = filterclonebundleentries(
2001 repo, entries, streamclonerequested=pullop.streamclonerequested)
2014 repo, entries, streamclonerequested=pullop.streamclonerequested)
2002
2015
2003 if not entries:
2016 if not entries:
2004 # There is a thundering herd concern here. However, if a server
2017 # There is a thundering herd concern here. However, if a server
2005 # operator doesn't advertise bundles appropriate for its clients,
2018 # operator doesn't advertise bundles appropriate for its clients,
2006 # they deserve what's coming. Furthermore, from a client's
2019 # they deserve what's coming. Furthermore, from a client's
2007 # perspective, no automatic fallback would mean not being able to
2020 # perspective, no automatic fallback would mean not being able to
2008 # clone!
2021 # clone!
2009 repo.ui.warn(_('no compatible clone bundles available on server; '
2022 repo.ui.warn(_('no compatible clone bundles available on server; '
2010 'falling back to regular clone\n'))
2023 'falling back to regular clone\n'))
2011 repo.ui.warn(_('(you may want to report this to the server '
2024 repo.ui.warn(_('(you may want to report this to the server '
2012 'operator)\n'))
2025 'operator)\n'))
2013 return
2026 return
2014
2027
2015 entries = sortclonebundleentries(repo.ui, entries)
2028 entries = sortclonebundleentries(repo.ui, entries)
2016
2029
2017 url = entries[0]['URL']
2030 url = entries[0]['URL']
2018 repo.ui.status(_('applying clone bundle from %s\n') % url)
2031 repo.ui.status(_('applying clone bundle from %s\n') % url)
2019 if trypullbundlefromurl(repo.ui, repo, url):
2032 if trypullbundlefromurl(repo.ui, repo, url):
2020 repo.ui.status(_('finished applying clone bundle\n'))
2033 repo.ui.status(_('finished applying clone bundle\n'))
2021 # Bundle failed.
2034 # Bundle failed.
2022 #
2035 #
2023 # We abort by default to avoid the thundering herd of
2036 # We abort by default to avoid the thundering herd of
2024 # clients flooding a server that was expecting expensive
2037 # clients flooding a server that was expecting expensive
2025 # clone load to be offloaded.
2038 # clone load to be offloaded.
2026 elif repo.ui.configbool('ui', 'clonebundlefallback'):
2039 elif repo.ui.configbool('ui', 'clonebundlefallback'):
2027 repo.ui.warn(_('falling back to normal clone\n'))
2040 repo.ui.warn(_('falling back to normal clone\n'))
2028 else:
2041 else:
2029 raise error.Abort(_('error applying bundle'),
2042 raise error.Abort(_('error applying bundle'),
2030 hint=_('if this error persists, consider contacting '
2043 hint=_('if this error persists, consider contacting '
2031 'the server operator or disable clone '
2044 'the server operator or disable clone '
2032 'bundles via '
2045 'bundles via '
2033 '"--config ui.clonebundles=false"'))
2046 '"--config ui.clonebundles=false"'))
2034
2047
2035 def parseclonebundlesmanifest(repo, s):
2048 def parseclonebundlesmanifest(repo, s):
2036 """Parses the raw text of a clone bundles manifest.
2049 """Parses the raw text of a clone bundles manifest.
2037
2050
2038 Returns a list of dicts. The dicts have a ``URL`` key corresponding
2051 Returns a list of dicts. The dicts have a ``URL`` key corresponding
2039 to the URL and other keys are the attributes for the entry.
2052 to the URL and other keys are the attributes for the entry.
2040 """
2053 """
2041 m = []
2054 m = []
2042 for line in s.splitlines():
2055 for line in s.splitlines():
2043 fields = line.split()
2056 fields = line.split()
2044 if not fields:
2057 if not fields:
2045 continue
2058 continue
2046 attrs = {'URL': fields[0]}
2059 attrs = {'URL': fields[0]}
2047 for rawattr in fields[1:]:
2060 for rawattr in fields[1:]:
2048 key, value = rawattr.split('=', 1)
2061 key, value = rawattr.split('=', 1)
2049 key = urlreq.unquote(key)
2062 key = urlreq.unquote(key)
2050 value = urlreq.unquote(value)
2063 value = urlreq.unquote(value)
2051 attrs[key] = value
2064 attrs[key] = value
2052
2065
2053 # Parse BUNDLESPEC into components. This makes client-side
2066 # Parse BUNDLESPEC into components. This makes client-side
2054 # preferences easier to specify since you can prefer a single
2067 # preferences easier to specify since you can prefer a single
2055 # component of the BUNDLESPEC.
2068 # component of the BUNDLESPEC.
2056 if key == 'BUNDLESPEC':
2069 if key == 'BUNDLESPEC':
2057 try:
2070 try:
2058 comp, version, params = parsebundlespec(repo, value,
2071 comp, version, params = parsebundlespec(repo, value,
2059 externalnames=True)
2072 externalnames=True)
2060 attrs['COMPRESSION'] = comp
2073 attrs['COMPRESSION'] = comp
2061 attrs['VERSION'] = version
2074 attrs['VERSION'] = version
2062 except error.InvalidBundleSpecification:
2075 except error.InvalidBundleSpecification:
2063 pass
2076 pass
2064 except error.UnsupportedBundleSpecification:
2077 except error.UnsupportedBundleSpecification:
2065 pass
2078 pass
2066
2079
2067 m.append(attrs)
2080 m.append(attrs)
2068
2081
2069 return m
2082 return m
2070
2083
2071 def filterclonebundleentries(repo, entries, streamclonerequested=False):
2084 def filterclonebundleentries(repo, entries, streamclonerequested=False):
2072 """Remove incompatible clone bundle manifest entries.
2085 """Remove incompatible clone bundle manifest entries.
2073
2086
2074 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
2087 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
2075 and returns a new list consisting of only the entries that this client
2088 and returns a new list consisting of only the entries that this client
2076 should be able to apply.
2089 should be able to apply.
2077
2090
2078 There is no guarantee we'll be able to apply all returned entries because
2091 There is no guarantee we'll be able to apply all returned entries because
2079 the metadata we use to filter on may be missing or wrong.
2092 the metadata we use to filter on may be missing or wrong.
2080 """
2093 """
2081 newentries = []
2094 newentries = []
2082 for entry in entries:
2095 for entry in entries:
2083 spec = entry.get('BUNDLESPEC')
2096 spec = entry.get('BUNDLESPEC')
2084 if spec:
2097 if spec:
2085 try:
2098 try:
2086 comp, version, params = parsebundlespec(repo, spec, strict=True)
2099 comp, version, params = parsebundlespec(repo, spec, strict=True)
2087
2100
2088 # If a stream clone was requested, filter out non-streamclone
2101 # If a stream clone was requested, filter out non-streamclone
2089 # entries.
2102 # entries.
2090 if streamclonerequested and (comp != 'UN' or version != 's1'):
2103 if streamclonerequested and (comp != 'UN' or version != 's1'):
2091 repo.ui.debug('filtering %s because not a stream clone\n' %
2104 repo.ui.debug('filtering %s because not a stream clone\n' %
2092 entry['URL'])
2105 entry['URL'])
2093 continue
2106 continue
2094
2107
2095 except error.InvalidBundleSpecification as e:
2108 except error.InvalidBundleSpecification as e:
2096 repo.ui.debug(str(e) + '\n')
2109 repo.ui.debug(str(e) + '\n')
2097 continue
2110 continue
2098 except error.UnsupportedBundleSpecification as e:
2111 except error.UnsupportedBundleSpecification as e:
2099 repo.ui.debug('filtering %s because unsupported bundle '
2112 repo.ui.debug('filtering %s because unsupported bundle '
2100 'spec: %s\n' % (entry['URL'], str(e)))
2113 'spec: %s\n' % (entry['URL'], str(e)))
2101 continue
2114 continue
2102 # If we don't have a spec and requested a stream clone, we don't know
2115 # If we don't have a spec and requested a stream clone, we don't know
2103 # what the entry is so don't attempt to apply it.
2116 # what the entry is so don't attempt to apply it.
2104 elif streamclonerequested:
2117 elif streamclonerequested:
2105 repo.ui.debug('filtering %s because cannot determine if a stream '
2118 repo.ui.debug('filtering %s because cannot determine if a stream '
2106 'clone bundle\n' % entry['URL'])
2119 'clone bundle\n' % entry['URL'])
2107 continue
2120 continue
2108
2121
2109 if 'REQUIRESNI' in entry and not sslutil.hassni:
2122 if 'REQUIRESNI' in entry and not sslutil.hassni:
2110 repo.ui.debug('filtering %s because SNI not supported\n' %
2123 repo.ui.debug('filtering %s because SNI not supported\n' %
2111 entry['URL'])
2124 entry['URL'])
2112 continue
2125 continue
2113
2126
2114 newentries.append(entry)
2127 newentries.append(entry)
2115
2128
2116 return newentries
2129 return newentries
2117
2130
2118 class clonebundleentry(object):
2131 class clonebundleentry(object):
2119 """Represents an item in a clone bundles manifest.
2132 """Represents an item in a clone bundles manifest.
2120
2133
2121 This rich class is needed to support sorting since sorted() in Python 3
2134 This rich class is needed to support sorting since sorted() in Python 3
2122 doesn't support ``cmp`` and our comparison is complex enough that ``key=``
2135 doesn't support ``cmp`` and our comparison is complex enough that ``key=``
2123 won't work.
2136 won't work.
2124 """
2137 """
2125
2138
2126 def __init__(self, value, prefers):
2139 def __init__(self, value, prefers):
2127 self.value = value
2140 self.value = value
2128 self.prefers = prefers
2141 self.prefers = prefers
2129
2142
2130 def _cmp(self, other):
2143 def _cmp(self, other):
2131 for prefkey, prefvalue in self.prefers:
2144 for prefkey, prefvalue in self.prefers:
2132 avalue = self.value.get(prefkey)
2145 avalue = self.value.get(prefkey)
2133 bvalue = other.value.get(prefkey)
2146 bvalue = other.value.get(prefkey)
2134
2147
2135 # Special case for b missing attribute and a matches exactly.
2148 # Special case for b missing attribute and a matches exactly.
2136 if avalue is not None and bvalue is None and avalue == prefvalue:
2149 if avalue is not None and bvalue is None and avalue == prefvalue:
2137 return -1
2150 return -1
2138
2151
2139 # Special case for a missing attribute and b matches exactly.
2152 # Special case for a missing attribute and b matches exactly.
2140 if bvalue is not None and avalue is None and bvalue == prefvalue:
2153 if bvalue is not None and avalue is None and bvalue == prefvalue:
2141 return 1
2154 return 1
2142
2155
2143 # We can't compare unless attribute present on both.
2156 # We can't compare unless attribute present on both.
2144 if avalue is None or bvalue is None:
2157 if avalue is None or bvalue is None:
2145 continue
2158 continue
2146
2159
2147 # Same values should fall back to next attribute.
2160 # Same values should fall back to next attribute.
2148 if avalue == bvalue:
2161 if avalue == bvalue:
2149 continue
2162 continue
2150
2163
2151 # Exact matches come first.
2164 # Exact matches come first.
2152 if avalue == prefvalue:
2165 if avalue == prefvalue:
2153 return -1
2166 return -1
2154 if bvalue == prefvalue:
2167 if bvalue == prefvalue:
2155 return 1
2168 return 1
2156
2169
2157 # Fall back to next attribute.
2170 # Fall back to next attribute.
2158 continue
2171 continue
2159
2172
2160 # If we got here we couldn't sort by attributes and prefers. Fall
2173 # If we got here we couldn't sort by attributes and prefers. Fall
2161 # back to index order.
2174 # back to index order.
2162 return 0
2175 return 0
2163
2176
2164 def __lt__(self, other):
2177 def __lt__(self, other):
2165 return self._cmp(other) < 0
2178 return self._cmp(other) < 0
2166
2179
2167 def __gt__(self, other):
2180 def __gt__(self, other):
2168 return self._cmp(other) > 0
2181 return self._cmp(other) > 0
2169
2182
2170 def __eq__(self, other):
2183 def __eq__(self, other):
2171 return self._cmp(other) == 0
2184 return self._cmp(other) == 0
2172
2185
2173 def __le__(self, other):
2186 def __le__(self, other):
2174 return self._cmp(other) <= 0
2187 return self._cmp(other) <= 0
2175
2188
2176 def __ge__(self, other):
2189 def __ge__(self, other):
2177 return self._cmp(other) >= 0
2190 return self._cmp(other) >= 0
2178
2191
2179 def __ne__(self, other):
2192 def __ne__(self, other):
2180 return self._cmp(other) != 0
2193 return self._cmp(other) != 0
2181
2194
2182 def sortclonebundleentries(ui, entries):
2195 def sortclonebundleentries(ui, entries):
2183 prefers = ui.configlist('ui', 'clonebundleprefers')
2196 prefers = ui.configlist('ui', 'clonebundleprefers')
2184 if not prefers:
2197 if not prefers:
2185 return list(entries)
2198 return list(entries)
2186
2199
2187 prefers = [p.split('=', 1) for p in prefers]
2200 prefers = [p.split('=', 1) for p in prefers]
2188
2201
2189 items = sorted(clonebundleentry(v, prefers) for v in entries)
2202 items = sorted(clonebundleentry(v, prefers) for v in entries)
2190 return [i.value for i in items]
2203 return [i.value for i in items]
2191
2204
2192 def trypullbundlefromurl(ui, repo, url):
2205 def trypullbundlefromurl(ui, repo, url):
2193 """Attempt to apply a bundle from a URL."""
2206 """Attempt to apply a bundle from a URL."""
2194 with repo.lock(), repo.transaction('bundleurl') as tr:
2207 with repo.lock(), repo.transaction('bundleurl') as tr:
2195 try:
2208 try:
2196 fh = urlmod.open(ui, url)
2209 fh = urlmod.open(ui, url)
2197 cg = readbundle(ui, fh, 'stream')
2210 cg = readbundle(ui, fh, 'stream')
2198
2211
2199 if isinstance(cg, streamclone.streamcloneapplier):
2212 if isinstance(cg, streamclone.streamcloneapplier):
2200 cg.apply(repo)
2213 cg.apply(repo)
2201 else:
2214 else:
2202 bundle2.applybundle(repo, cg, tr, 'clonebundles', url)
2215 bundle2.applybundle(repo, cg, tr, 'clonebundles', url)
2203 return True
2216 return True
2204 except urlerr.httperror as e:
2217 except urlerr.httperror as e:
2205 ui.warn(_('HTTP error fetching bundle: %s\n') % str(e))
2218 ui.warn(_('HTTP error fetching bundle: %s\n') % str(e))
2206 except urlerr.urlerror as e:
2219 except urlerr.urlerror as e:
2207 ui.warn(_('error fetching bundle: %s\n') % e.reason)
2220 ui.warn(_('error fetching bundle: %s\n') % e.reason)
2208
2221
2209 return False
2222 return False
@@ -1,1068 +1,1070 b''
1 # wireproto.py - generic wire protocol support functions
1 # wireproto.py - generic wire protocol support functions
2 #
2 #
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import hashlib
10 import hashlib
11 import os
11 import os
12 import tempfile
12 import tempfile
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 bin,
16 bin,
17 hex,
17 hex,
18 nullid,
18 nullid,
19 )
19 )
20
20
21 from . import (
21 from . import (
22 bundle2,
22 bundle2,
23 changegroup as changegroupmod,
23 changegroup as changegroupmod,
24 discovery,
24 discovery,
25 encoding,
25 encoding,
26 error,
26 error,
27 exchange,
27 exchange,
28 peer,
28 peer,
29 pushkey as pushkeymod,
29 pushkey as pushkeymod,
30 pycompat,
30 pycompat,
31 repository,
31 repository,
32 streamclone,
32 streamclone,
33 util,
33 util,
34 )
34 )
35
35
36 urlerr = util.urlerr
36 urlerr = util.urlerr
37 urlreq = util.urlreq
37 urlreq = util.urlreq
38
38
39 bundle2requiredmain = _('incompatible Mercurial client; bundle2 required')
39 bundle2requiredmain = _('incompatible Mercurial client; bundle2 required')
40 bundle2requiredhint = _('see https://www.mercurial-scm.org/wiki/'
40 bundle2requiredhint = _('see https://www.mercurial-scm.org/wiki/'
41 'IncompatibleClient')
41 'IncompatibleClient')
42 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
42 bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint)
43
43
44 class abstractserverproto(object):
44 class abstractserverproto(object):
45 """abstract class that summarizes the protocol API
45 """abstract class that summarizes the protocol API
46
46
47 Used as reference and documentation.
47 Used as reference and documentation.
48 """
48 """
49
49
50 def getargs(self, args):
50 def getargs(self, args):
51 """return the value for arguments in <args>
51 """return the value for arguments in <args>
52
52
53 returns a list of values (same order as <args>)"""
53 returns a list of values (same order as <args>)"""
54 raise NotImplementedError()
54 raise NotImplementedError()
55
55
56 def getfile(self, fp):
56 def getfile(self, fp):
57 """write the whole content of a file into a file like object
57 """write the whole content of a file into a file like object
58
58
59 The file is in the form::
59 The file is in the form::
60
60
61 (<chunk-size>\n<chunk>)+0\n
61 (<chunk-size>\n<chunk>)+0\n
62
62
63 chunk size is the ascii version of the int.
63 chunk size is the ascii version of the int.
64 """
64 """
65 raise NotImplementedError()
65 raise NotImplementedError()
66
66
67 def redirect(self):
67 def redirect(self):
68 """may setup interception for stdout and stderr
68 """may setup interception for stdout and stderr
69
69
70 See also the `restore` method."""
70 See also the `restore` method."""
71 raise NotImplementedError()
71 raise NotImplementedError()
72
72
73 # If the `redirect` function does install interception, the `restore`
73 # If the `redirect` function does install interception, the `restore`
74 # function MUST be defined. If interception is not used, this function
74 # function MUST be defined. If interception is not used, this function
75 # MUST NOT be defined.
75 # MUST NOT be defined.
76 #
76 #
77 # left commented here on purpose
77 # left commented here on purpose
78 #
78 #
79 #def restore(self):
79 #def restore(self):
80 # """reinstall previous stdout and stderr and return intercepted stdout
80 # """reinstall previous stdout and stderr and return intercepted stdout
81 # """
81 # """
82 # raise NotImplementedError()
82 # raise NotImplementedError()
83
83
84 class remoteiterbatcher(peer.iterbatcher):
84 class remoteiterbatcher(peer.iterbatcher):
85 def __init__(self, remote):
85 def __init__(self, remote):
86 super(remoteiterbatcher, self).__init__()
86 super(remoteiterbatcher, self).__init__()
87 self._remote = remote
87 self._remote = remote
88
88
89 def __getattr__(self, name):
89 def __getattr__(self, name):
90 # Validate this method is batchable, since submit() only supports
90 # Validate this method is batchable, since submit() only supports
91 # batchable methods.
91 # batchable methods.
92 fn = getattr(self._remote, name)
92 fn = getattr(self._remote, name)
93 if not getattr(fn, 'batchable', None):
93 if not getattr(fn, 'batchable', None):
94 raise error.ProgrammingError('Attempted to batch a non-batchable '
94 raise error.ProgrammingError('Attempted to batch a non-batchable '
95 'call to %r' % name)
95 'call to %r' % name)
96
96
97 return super(remoteiterbatcher, self).__getattr__(name)
97 return super(remoteiterbatcher, self).__getattr__(name)
98
98
99 def submit(self):
99 def submit(self):
100 """Break the batch request into many patch calls and pipeline them.
100 """Break the batch request into many patch calls and pipeline them.
101
101
102 This is mostly valuable over http where request sizes can be
102 This is mostly valuable over http where request sizes can be
103 limited, but can be used in other places as well.
103 limited, but can be used in other places as well.
104 """
104 """
105 # 2-tuple of (command, arguments) that represents what will be
105 # 2-tuple of (command, arguments) that represents what will be
106 # sent over the wire.
106 # sent over the wire.
107 requests = []
107 requests = []
108
108
109 # 4-tuple of (command, final future, @batchable generator, remote
109 # 4-tuple of (command, final future, @batchable generator, remote
110 # future).
110 # future).
111 results = []
111 results = []
112
112
113 for command, args, opts, finalfuture in self.calls:
113 for command, args, opts, finalfuture in self.calls:
114 mtd = getattr(self._remote, command)
114 mtd = getattr(self._remote, command)
115 batchable = mtd.batchable(mtd.__self__, *args, **opts)
115 batchable = mtd.batchable(mtd.__self__, *args, **opts)
116
116
117 commandargs, fremote = next(batchable)
117 commandargs, fremote = next(batchable)
118 assert fremote
118 assert fremote
119 requests.append((command, commandargs))
119 requests.append((command, commandargs))
120 results.append((command, finalfuture, batchable, fremote))
120 results.append((command, finalfuture, batchable, fremote))
121
121
122 if requests:
122 if requests:
123 self._resultiter = self._remote._submitbatch(requests)
123 self._resultiter = self._remote._submitbatch(requests)
124
124
125 self._results = results
125 self._results = results
126
126
127 def results(self):
127 def results(self):
128 for command, finalfuture, batchable, remotefuture in self._results:
128 for command, finalfuture, batchable, remotefuture in self._results:
129 # Get the raw result, set it in the remote future, feed it
129 # Get the raw result, set it in the remote future, feed it
130 # back into the @batchable generator so it can be decoded, and
130 # back into the @batchable generator so it can be decoded, and
131 # set the result on the final future to this value.
131 # set the result on the final future to this value.
132 remoteresult = next(self._resultiter)
132 remoteresult = next(self._resultiter)
133 remotefuture.set(remoteresult)
133 remotefuture.set(remoteresult)
134 finalfuture.set(next(batchable))
134 finalfuture.set(next(batchable))
135
135
136 # Verify our @batchable generators only emit 2 values.
136 # Verify our @batchable generators only emit 2 values.
137 try:
137 try:
138 next(batchable)
138 next(batchable)
139 except StopIteration:
139 except StopIteration:
140 pass
140 pass
141 else:
141 else:
142 raise error.ProgrammingError('%s @batchable generator emitted '
142 raise error.ProgrammingError('%s @batchable generator emitted '
143 'unexpected value count' % command)
143 'unexpected value count' % command)
144
144
145 yield finalfuture.value
145 yield finalfuture.value
146
146
147 # Forward a couple of names from peer to make wireproto interactions
147 # Forward a couple of names from peer to make wireproto interactions
148 # slightly more sensible.
148 # slightly more sensible.
149 batchable = peer.batchable
149 batchable = peer.batchable
150 future = peer.future
150 future = peer.future
151
151
152 # list of nodes encoding / decoding
152 # list of nodes encoding / decoding
153
153
154 def decodelist(l, sep=' '):
154 def decodelist(l, sep=' '):
155 if l:
155 if l:
156 return [bin(v) for v in l.split(sep)]
156 return [bin(v) for v in l.split(sep)]
157 return []
157 return []
158
158
159 def encodelist(l, sep=' '):
159 def encodelist(l, sep=' '):
160 try:
160 try:
161 return sep.join(map(hex, l))
161 return sep.join(map(hex, l))
162 except TypeError:
162 except TypeError:
163 raise
163 raise
164
164
165 # batched call argument encoding
165 # batched call argument encoding
166
166
167 def escapearg(plain):
167 def escapearg(plain):
168 return (plain
168 return (plain
169 .replace(':', ':c')
169 .replace(':', ':c')
170 .replace(',', ':o')
170 .replace(',', ':o')
171 .replace(';', ':s')
171 .replace(';', ':s')
172 .replace('=', ':e'))
172 .replace('=', ':e'))
173
173
174 def unescapearg(escaped):
174 def unescapearg(escaped):
175 return (escaped
175 return (escaped
176 .replace(':e', '=')
176 .replace(':e', '=')
177 .replace(':s', ';')
177 .replace(':s', ';')
178 .replace(':o', ',')
178 .replace(':o', ',')
179 .replace(':c', ':'))
179 .replace(':c', ':'))
180
180
181 def encodebatchcmds(req):
181 def encodebatchcmds(req):
182 """Return a ``cmds`` argument value for the ``batch`` command."""
182 """Return a ``cmds`` argument value for the ``batch`` command."""
183 cmds = []
183 cmds = []
184 for op, argsdict in req:
184 for op, argsdict in req:
185 # Old servers didn't properly unescape argument names. So prevent
185 # Old servers didn't properly unescape argument names. So prevent
186 # the sending of argument names that may not be decoded properly by
186 # the sending of argument names that may not be decoded properly by
187 # servers.
187 # servers.
188 assert all(escapearg(k) == k for k in argsdict)
188 assert all(escapearg(k) == k for k in argsdict)
189
189
190 args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
190 args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
191 for k, v in argsdict.iteritems())
191 for k, v in argsdict.iteritems())
192 cmds.append('%s %s' % (op, args))
192 cmds.append('%s %s' % (op, args))
193
193
194 return ';'.join(cmds)
194 return ';'.join(cmds)
195
195
196 # mapping of options accepted by getbundle and their types
196 # mapping of options accepted by getbundle and their types
197 #
197 #
198 # Meant to be extended by extensions. It is extensions responsibility to ensure
198 # Meant to be extended by extensions. It is extensions responsibility to ensure
199 # such options are properly processed in exchange.getbundle.
199 # such options are properly processed in exchange.getbundle.
200 #
200 #
201 # supported types are:
201 # supported types are:
202 #
202 #
203 # :nodes: list of binary nodes
203 # :nodes: list of binary nodes
204 # :csv: list of comma-separated values
204 # :csv: list of comma-separated values
205 # :scsv: list of comma-separated values return as set
205 # :scsv: list of comma-separated values return as set
206 # :plain: string with no transformation needed.
206 # :plain: string with no transformation needed.
207 gboptsmap = {'heads': 'nodes',
207 gboptsmap = {'heads': 'nodes',
208 'bookmarks': 'boolean',
208 'bookmarks': 'boolean',
209 'common': 'nodes',
209 'common': 'nodes',
210 'obsmarkers': 'boolean',
210 'obsmarkers': 'boolean',
211 'phases': 'boolean',
211 'phases': 'boolean',
212 'bundlecaps': 'scsv',
212 'bundlecaps': 'scsv',
213 'listkeys': 'csv',
213 'listkeys': 'csv',
214 'cg': 'boolean',
214 'cg': 'boolean',
215 'cbattempted': 'boolean'}
215 'cbattempted': 'boolean',
216 'stream': 'boolean',
217 }
216
218
217 # client side
219 # client side
218
220
219 class wirepeer(repository.legacypeer):
221 class wirepeer(repository.legacypeer):
220 """Client-side interface for communicating with a peer repository.
222 """Client-side interface for communicating with a peer repository.
221
223
222 Methods commonly call wire protocol commands of the same name.
224 Methods commonly call wire protocol commands of the same name.
223
225
224 See also httppeer.py and sshpeer.py for protocol-specific
226 See also httppeer.py and sshpeer.py for protocol-specific
225 implementations of this interface.
227 implementations of this interface.
226 """
228 """
227 # Begin of basewirepeer interface.
229 # Begin of basewirepeer interface.
228
230
229 def iterbatch(self):
231 def iterbatch(self):
230 return remoteiterbatcher(self)
232 return remoteiterbatcher(self)
231
233
232 @batchable
234 @batchable
233 def lookup(self, key):
235 def lookup(self, key):
234 self.requirecap('lookup', _('look up remote revision'))
236 self.requirecap('lookup', _('look up remote revision'))
235 f = future()
237 f = future()
236 yield {'key': encoding.fromlocal(key)}, f
238 yield {'key': encoding.fromlocal(key)}, f
237 d = f.value
239 d = f.value
238 success, data = d[:-1].split(" ", 1)
240 success, data = d[:-1].split(" ", 1)
239 if int(success):
241 if int(success):
240 yield bin(data)
242 yield bin(data)
241 else:
243 else:
242 self._abort(error.RepoError(data))
244 self._abort(error.RepoError(data))
243
245
244 @batchable
246 @batchable
245 def heads(self):
247 def heads(self):
246 f = future()
248 f = future()
247 yield {}, f
249 yield {}, f
248 d = f.value
250 d = f.value
249 try:
251 try:
250 yield decodelist(d[:-1])
252 yield decodelist(d[:-1])
251 except ValueError:
253 except ValueError:
252 self._abort(error.ResponseError(_("unexpected response:"), d))
254 self._abort(error.ResponseError(_("unexpected response:"), d))
253
255
254 @batchable
256 @batchable
255 def known(self, nodes):
257 def known(self, nodes):
256 f = future()
258 f = future()
257 yield {'nodes': encodelist(nodes)}, f
259 yield {'nodes': encodelist(nodes)}, f
258 d = f.value
260 d = f.value
259 try:
261 try:
260 yield [bool(int(b)) for b in d]
262 yield [bool(int(b)) for b in d]
261 except ValueError:
263 except ValueError:
262 self._abort(error.ResponseError(_("unexpected response:"), d))
264 self._abort(error.ResponseError(_("unexpected response:"), d))
263
265
264 @batchable
266 @batchable
265 def branchmap(self):
267 def branchmap(self):
266 f = future()
268 f = future()
267 yield {}, f
269 yield {}, f
268 d = f.value
270 d = f.value
269 try:
271 try:
270 branchmap = {}
272 branchmap = {}
271 for branchpart in d.splitlines():
273 for branchpart in d.splitlines():
272 branchname, branchheads = branchpart.split(' ', 1)
274 branchname, branchheads = branchpart.split(' ', 1)
273 branchname = encoding.tolocal(urlreq.unquote(branchname))
275 branchname = encoding.tolocal(urlreq.unquote(branchname))
274 branchheads = decodelist(branchheads)
276 branchheads = decodelist(branchheads)
275 branchmap[branchname] = branchheads
277 branchmap[branchname] = branchheads
276 yield branchmap
278 yield branchmap
277 except TypeError:
279 except TypeError:
278 self._abort(error.ResponseError(_("unexpected response:"), d))
280 self._abort(error.ResponseError(_("unexpected response:"), d))
279
281
280 @batchable
282 @batchable
281 def listkeys(self, namespace):
283 def listkeys(self, namespace):
282 if not self.capable('pushkey'):
284 if not self.capable('pushkey'):
283 yield {}, None
285 yield {}, None
284 f = future()
286 f = future()
285 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
287 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
286 yield {'namespace': encoding.fromlocal(namespace)}, f
288 yield {'namespace': encoding.fromlocal(namespace)}, f
287 d = f.value
289 d = f.value
288 self.ui.debug('received listkey for "%s": %i bytes\n'
290 self.ui.debug('received listkey for "%s": %i bytes\n'
289 % (namespace, len(d)))
291 % (namespace, len(d)))
290 yield pushkeymod.decodekeys(d)
292 yield pushkeymod.decodekeys(d)
291
293
292 @batchable
294 @batchable
293 def pushkey(self, namespace, key, old, new):
295 def pushkey(self, namespace, key, old, new):
294 if not self.capable('pushkey'):
296 if not self.capable('pushkey'):
295 yield False, None
297 yield False, None
296 f = future()
298 f = future()
297 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
299 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
298 yield {'namespace': encoding.fromlocal(namespace),
300 yield {'namespace': encoding.fromlocal(namespace),
299 'key': encoding.fromlocal(key),
301 'key': encoding.fromlocal(key),
300 'old': encoding.fromlocal(old),
302 'old': encoding.fromlocal(old),
301 'new': encoding.fromlocal(new)}, f
303 'new': encoding.fromlocal(new)}, f
302 d = f.value
304 d = f.value
303 d, output = d.split('\n', 1)
305 d, output = d.split('\n', 1)
304 try:
306 try:
305 d = bool(int(d))
307 d = bool(int(d))
306 except ValueError:
308 except ValueError:
307 raise error.ResponseError(
309 raise error.ResponseError(
308 _('push failed (unexpected response):'), d)
310 _('push failed (unexpected response):'), d)
309 for l in output.splitlines(True):
311 for l in output.splitlines(True):
310 self.ui.status(_('remote: '), l)
312 self.ui.status(_('remote: '), l)
311 yield d
313 yield d
312
314
313 def stream_out(self):
315 def stream_out(self):
314 return self._callstream('stream_out')
316 return self._callstream('stream_out')
315
317
316 def getbundle(self, source, **kwargs):
318 def getbundle(self, source, **kwargs):
317 kwargs = pycompat.byteskwargs(kwargs)
319 kwargs = pycompat.byteskwargs(kwargs)
318 self.requirecap('getbundle', _('look up remote changes'))
320 self.requirecap('getbundle', _('look up remote changes'))
319 opts = {}
321 opts = {}
320 bundlecaps = kwargs.get('bundlecaps')
322 bundlecaps = kwargs.get('bundlecaps')
321 if bundlecaps is not None:
323 if bundlecaps is not None:
322 kwargs['bundlecaps'] = sorted(bundlecaps)
324 kwargs['bundlecaps'] = sorted(bundlecaps)
323 else:
325 else:
324 bundlecaps = () # kwargs could have it to None
326 bundlecaps = () # kwargs could have it to None
325 for key, value in kwargs.iteritems():
327 for key, value in kwargs.iteritems():
326 if value is None:
328 if value is None:
327 continue
329 continue
328 keytype = gboptsmap.get(key)
330 keytype = gboptsmap.get(key)
329 if keytype is None:
331 if keytype is None:
330 raise error.ProgrammingError(
332 raise error.ProgrammingError(
331 'Unexpectedly None keytype for key %s' % key)
333 'Unexpectedly None keytype for key %s' % key)
332 elif keytype == 'nodes':
334 elif keytype == 'nodes':
333 value = encodelist(value)
335 value = encodelist(value)
334 elif keytype in ('csv', 'scsv'):
336 elif keytype in ('csv', 'scsv'):
335 value = ','.join(value)
337 value = ','.join(value)
336 elif keytype == 'boolean':
338 elif keytype == 'boolean':
337 value = '%i' % bool(value)
339 value = '%i' % bool(value)
338 elif keytype != 'plain':
340 elif keytype != 'plain':
339 raise KeyError('unknown getbundle option type %s'
341 raise KeyError('unknown getbundle option type %s'
340 % keytype)
342 % keytype)
341 opts[key] = value
343 opts[key] = value
342 f = self._callcompressable("getbundle", **pycompat.strkwargs(opts))
344 f = self._callcompressable("getbundle", **pycompat.strkwargs(opts))
343 if any((cap.startswith('HG2') for cap in bundlecaps)):
345 if any((cap.startswith('HG2') for cap in bundlecaps)):
344 return bundle2.getunbundler(self.ui, f)
346 return bundle2.getunbundler(self.ui, f)
345 else:
347 else:
346 return changegroupmod.cg1unpacker(f, 'UN')
348 return changegroupmod.cg1unpacker(f, 'UN')
347
349
348 def unbundle(self, cg, heads, url):
350 def unbundle(self, cg, heads, url):
349 '''Send cg (a readable file-like object representing the
351 '''Send cg (a readable file-like object representing the
350 changegroup to push, typically a chunkbuffer object) to the
352 changegroup to push, typically a chunkbuffer object) to the
351 remote server as a bundle.
353 remote server as a bundle.
352
354
353 When pushing a bundle10 stream, return an integer indicating the
355 When pushing a bundle10 stream, return an integer indicating the
354 result of the push (see changegroup.apply()).
356 result of the push (see changegroup.apply()).
355
357
356 When pushing a bundle20 stream, return a bundle20 stream.
358 When pushing a bundle20 stream, return a bundle20 stream.
357
359
358 `url` is the url the client thinks it's pushing to, which is
360 `url` is the url the client thinks it's pushing to, which is
359 visible to hooks.
361 visible to hooks.
360 '''
362 '''
361
363
362 if heads != ['force'] and self.capable('unbundlehash'):
364 if heads != ['force'] and self.capable('unbundlehash'):
363 heads = encodelist(['hashed',
365 heads = encodelist(['hashed',
364 hashlib.sha1(''.join(sorted(heads))).digest()])
366 hashlib.sha1(''.join(sorted(heads))).digest()])
365 else:
367 else:
366 heads = encodelist(heads)
368 heads = encodelist(heads)
367
369
368 if util.safehasattr(cg, 'deltaheader'):
370 if util.safehasattr(cg, 'deltaheader'):
369 # this a bundle10, do the old style call sequence
371 # this a bundle10, do the old style call sequence
370 ret, output = self._callpush("unbundle", cg, heads=heads)
372 ret, output = self._callpush("unbundle", cg, heads=heads)
371 if ret == "":
373 if ret == "":
372 raise error.ResponseError(
374 raise error.ResponseError(
373 _('push failed:'), output)
375 _('push failed:'), output)
374 try:
376 try:
375 ret = int(ret)
377 ret = int(ret)
376 except ValueError:
378 except ValueError:
377 raise error.ResponseError(
379 raise error.ResponseError(
378 _('push failed (unexpected response):'), ret)
380 _('push failed (unexpected response):'), ret)
379
381
380 for l in output.splitlines(True):
382 for l in output.splitlines(True):
381 self.ui.status(_('remote: '), l)
383 self.ui.status(_('remote: '), l)
382 else:
384 else:
383 # bundle2 push. Send a stream, fetch a stream.
385 # bundle2 push. Send a stream, fetch a stream.
384 stream = self._calltwowaystream('unbundle', cg, heads=heads)
386 stream = self._calltwowaystream('unbundle', cg, heads=heads)
385 ret = bundle2.getunbundler(self.ui, stream)
387 ret = bundle2.getunbundler(self.ui, stream)
386 return ret
388 return ret
387
389
388 # End of basewirepeer interface.
390 # End of basewirepeer interface.
389
391
390 # Begin of baselegacywirepeer interface.
392 # Begin of baselegacywirepeer interface.
391
393
392 def branches(self, nodes):
394 def branches(self, nodes):
393 n = encodelist(nodes)
395 n = encodelist(nodes)
394 d = self._call("branches", nodes=n)
396 d = self._call("branches", nodes=n)
395 try:
397 try:
396 br = [tuple(decodelist(b)) for b in d.splitlines()]
398 br = [tuple(decodelist(b)) for b in d.splitlines()]
397 return br
399 return br
398 except ValueError:
400 except ValueError:
399 self._abort(error.ResponseError(_("unexpected response:"), d))
401 self._abort(error.ResponseError(_("unexpected response:"), d))
400
402
401 def between(self, pairs):
403 def between(self, pairs):
402 batch = 8 # avoid giant requests
404 batch = 8 # avoid giant requests
403 r = []
405 r = []
404 for i in xrange(0, len(pairs), batch):
406 for i in xrange(0, len(pairs), batch):
405 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
407 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
406 d = self._call("between", pairs=n)
408 d = self._call("between", pairs=n)
407 try:
409 try:
408 r.extend(l and decodelist(l) or [] for l in d.splitlines())
410 r.extend(l and decodelist(l) or [] for l in d.splitlines())
409 except ValueError:
411 except ValueError:
410 self._abort(error.ResponseError(_("unexpected response:"), d))
412 self._abort(error.ResponseError(_("unexpected response:"), d))
411 return r
413 return r
412
414
413 def changegroup(self, nodes, kind):
415 def changegroup(self, nodes, kind):
414 n = encodelist(nodes)
416 n = encodelist(nodes)
415 f = self._callcompressable("changegroup", roots=n)
417 f = self._callcompressable("changegroup", roots=n)
416 return changegroupmod.cg1unpacker(f, 'UN')
418 return changegroupmod.cg1unpacker(f, 'UN')
417
419
418 def changegroupsubset(self, bases, heads, kind):
420 def changegroupsubset(self, bases, heads, kind):
419 self.requirecap('changegroupsubset', _('look up remote changes'))
421 self.requirecap('changegroupsubset', _('look up remote changes'))
420 bases = encodelist(bases)
422 bases = encodelist(bases)
421 heads = encodelist(heads)
423 heads = encodelist(heads)
422 f = self._callcompressable("changegroupsubset",
424 f = self._callcompressable("changegroupsubset",
423 bases=bases, heads=heads)
425 bases=bases, heads=heads)
424 return changegroupmod.cg1unpacker(f, 'UN')
426 return changegroupmod.cg1unpacker(f, 'UN')
425
427
426 # End of baselegacywirepeer interface.
428 # End of baselegacywirepeer interface.
427
429
428 def _submitbatch(self, req):
430 def _submitbatch(self, req):
429 """run batch request <req> on the server
431 """run batch request <req> on the server
430
432
431 Returns an iterator of the raw responses from the server.
433 Returns an iterator of the raw responses from the server.
432 """
434 """
433 rsp = self._callstream("batch", cmds=encodebatchcmds(req))
435 rsp = self._callstream("batch", cmds=encodebatchcmds(req))
434 chunk = rsp.read(1024)
436 chunk = rsp.read(1024)
435 work = [chunk]
437 work = [chunk]
436 while chunk:
438 while chunk:
437 while ';' not in chunk and chunk:
439 while ';' not in chunk and chunk:
438 chunk = rsp.read(1024)
440 chunk = rsp.read(1024)
439 work.append(chunk)
441 work.append(chunk)
440 merged = ''.join(work)
442 merged = ''.join(work)
441 while ';' in merged:
443 while ';' in merged:
442 one, merged = merged.split(';', 1)
444 one, merged = merged.split(';', 1)
443 yield unescapearg(one)
445 yield unescapearg(one)
444 chunk = rsp.read(1024)
446 chunk = rsp.read(1024)
445 work = [merged, chunk]
447 work = [merged, chunk]
446 yield unescapearg(''.join(work))
448 yield unescapearg(''.join(work))
447
449
448 def _submitone(self, op, args):
450 def _submitone(self, op, args):
449 return self._call(op, **pycompat.strkwargs(args))
451 return self._call(op, **pycompat.strkwargs(args))
450
452
451 def debugwireargs(self, one, two, three=None, four=None, five=None):
453 def debugwireargs(self, one, two, three=None, four=None, five=None):
452 # don't pass optional arguments left at their default value
454 # don't pass optional arguments left at their default value
453 opts = {}
455 opts = {}
454 if three is not None:
456 if three is not None:
455 opts[r'three'] = three
457 opts[r'three'] = three
456 if four is not None:
458 if four is not None:
457 opts[r'four'] = four
459 opts[r'four'] = four
458 return self._call('debugwireargs', one=one, two=two, **opts)
460 return self._call('debugwireargs', one=one, two=two, **opts)
459
461
460 def _call(self, cmd, **args):
462 def _call(self, cmd, **args):
461 """execute <cmd> on the server
463 """execute <cmd> on the server
462
464
463 The command is expected to return a simple string.
465 The command is expected to return a simple string.
464
466
465 returns the server reply as a string."""
467 returns the server reply as a string."""
466 raise NotImplementedError()
468 raise NotImplementedError()
467
469
468 def _callstream(self, cmd, **args):
470 def _callstream(self, cmd, **args):
469 """execute <cmd> on the server
471 """execute <cmd> on the server
470
472
471 The command is expected to return a stream. Note that if the
473 The command is expected to return a stream. Note that if the
472 command doesn't return a stream, _callstream behaves
474 command doesn't return a stream, _callstream behaves
473 differently for ssh and http peers.
475 differently for ssh and http peers.
474
476
475 returns the server reply as a file like object.
477 returns the server reply as a file like object.
476 """
478 """
477 raise NotImplementedError()
479 raise NotImplementedError()
478
480
479 def _callcompressable(self, cmd, **args):
481 def _callcompressable(self, cmd, **args):
480 """execute <cmd> on the server
482 """execute <cmd> on the server
481
483
482 The command is expected to return a stream.
484 The command is expected to return a stream.
483
485
484 The stream may have been compressed in some implementations. This
486 The stream may have been compressed in some implementations. This
485 function takes care of the decompression. This is the only difference
487 function takes care of the decompression. This is the only difference
486 with _callstream.
488 with _callstream.
487
489
488 returns the server reply as a file like object.
490 returns the server reply as a file like object.
489 """
491 """
490 raise NotImplementedError()
492 raise NotImplementedError()
491
493
492 def _callpush(self, cmd, fp, **args):
494 def _callpush(self, cmd, fp, **args):
493 """execute a <cmd> on server
495 """execute a <cmd> on server
494
496
495 The command is expected to be related to a push. Push has a special
497 The command is expected to be related to a push. Push has a special
496 return method.
498 return method.
497
499
498 returns the server reply as a (ret, output) tuple. ret is either
500 returns the server reply as a (ret, output) tuple. ret is either
499 empty (error) or a stringified int.
501 empty (error) or a stringified int.
500 """
502 """
501 raise NotImplementedError()
503 raise NotImplementedError()
502
504
503 def _calltwowaystream(self, cmd, fp, **args):
505 def _calltwowaystream(self, cmd, fp, **args):
504 """execute <cmd> on server
506 """execute <cmd> on server
505
507
506 The command will send a stream to the server and get a stream in reply.
508 The command will send a stream to the server and get a stream in reply.
507 """
509 """
508 raise NotImplementedError()
510 raise NotImplementedError()
509
511
510 def _abort(self, exception):
512 def _abort(self, exception):
511 """clearly abort the wire protocol connection and raise the exception
513 """clearly abort the wire protocol connection and raise the exception
512 """
514 """
513 raise NotImplementedError()
515 raise NotImplementedError()
514
516
515 # server side
517 # server side
516
518
517 # wire protocol command can either return a string or one of these classes.
519 # wire protocol command can either return a string or one of these classes.
518 class streamres(object):
520 class streamres(object):
519 """wireproto reply: binary stream
521 """wireproto reply: binary stream
520
522
521 The call was successful and the result is a stream.
523 The call was successful and the result is a stream.
522
524
523 Accepts a generator containing chunks of data to be sent to the client.
525 Accepts a generator containing chunks of data to be sent to the client.
524
526
525 ``prefer_uncompressed`` indicates that the data is expected to be
527 ``prefer_uncompressed`` indicates that the data is expected to be
526 uncompressable and that the stream should therefore use the ``none``
528 uncompressable and that the stream should therefore use the ``none``
527 engine.
529 engine.
528 """
530 """
529 def __init__(self, gen=None, prefer_uncompressed=False):
531 def __init__(self, gen=None, prefer_uncompressed=False):
530 self.gen = gen
532 self.gen = gen
531 self.prefer_uncompressed = prefer_uncompressed
533 self.prefer_uncompressed = prefer_uncompressed
532
534
533 class streamres_legacy(object):
535 class streamres_legacy(object):
534 """wireproto reply: uncompressed binary stream
536 """wireproto reply: uncompressed binary stream
535
537
536 The call was successful and the result is a stream.
538 The call was successful and the result is a stream.
537
539
538 Accepts a generator containing chunks of data to be sent to the client.
540 Accepts a generator containing chunks of data to be sent to the client.
539
541
540 Like ``streamres``, but sends an uncompressed data for "version 1" clients
542 Like ``streamres``, but sends an uncompressed data for "version 1" clients
541 using the application/mercurial-0.1 media type.
543 using the application/mercurial-0.1 media type.
542 """
544 """
543 def __init__(self, gen=None):
545 def __init__(self, gen=None):
544 self.gen = gen
546 self.gen = gen
545
547
546 class pushres(object):
548 class pushres(object):
547 """wireproto reply: success with simple integer return
549 """wireproto reply: success with simple integer return
548
550
549 The call was successful and returned an integer contained in `self.res`.
551 The call was successful and returned an integer contained in `self.res`.
550 """
552 """
551 def __init__(self, res):
553 def __init__(self, res):
552 self.res = res
554 self.res = res
553
555
554 class pusherr(object):
556 class pusherr(object):
555 """wireproto reply: failure
557 """wireproto reply: failure
556
558
557 The call failed. The `self.res` attribute contains the error message.
559 The call failed. The `self.res` attribute contains the error message.
558 """
560 """
559 def __init__(self, res):
561 def __init__(self, res):
560 self.res = res
562 self.res = res
561
563
562 class ooberror(object):
564 class ooberror(object):
563 """wireproto reply: failure of a batch of operation
565 """wireproto reply: failure of a batch of operation
564
566
565 Something failed during a batch call. The error message is stored in
567 Something failed during a batch call. The error message is stored in
566 `self.message`.
568 `self.message`.
567 """
569 """
568 def __init__(self, message):
570 def __init__(self, message):
569 self.message = message
571 self.message = message
570
572
571 def getdispatchrepo(repo, proto, command):
573 def getdispatchrepo(repo, proto, command):
572 """Obtain the repo used for processing wire protocol commands.
574 """Obtain the repo used for processing wire protocol commands.
573
575
574 The intent of this function is to serve as a monkeypatch point for
576 The intent of this function is to serve as a monkeypatch point for
575 extensions that need commands to operate on different repo views under
577 extensions that need commands to operate on different repo views under
576 specialized circumstances.
578 specialized circumstances.
577 """
579 """
578 return repo.filtered('served')
580 return repo.filtered('served')
579
581
580 def dispatch(repo, proto, command):
582 def dispatch(repo, proto, command):
581 repo = getdispatchrepo(repo, proto, command)
583 repo = getdispatchrepo(repo, proto, command)
582 func, spec = commands[command]
584 func, spec = commands[command]
583 args = proto.getargs(spec)
585 args = proto.getargs(spec)
584 return func(repo, proto, *args)
586 return func(repo, proto, *args)
585
587
586 def options(cmd, keys, others):
588 def options(cmd, keys, others):
587 opts = {}
589 opts = {}
588 for k in keys:
590 for k in keys:
589 if k in others:
591 if k in others:
590 opts[k] = others[k]
592 opts[k] = others[k]
591 del others[k]
593 del others[k]
592 if others:
594 if others:
593 util.stderr.write("warning: %s ignored unexpected arguments %s\n"
595 util.stderr.write("warning: %s ignored unexpected arguments %s\n"
594 % (cmd, ",".join(others)))
596 % (cmd, ",".join(others)))
595 return opts
597 return opts
596
598
597 def bundle1allowed(repo, action):
599 def bundle1allowed(repo, action):
598 """Whether a bundle1 operation is allowed from the server.
600 """Whether a bundle1 operation is allowed from the server.
599
601
600 Priority is:
602 Priority is:
601
603
602 1. server.bundle1gd.<action> (if generaldelta active)
604 1. server.bundle1gd.<action> (if generaldelta active)
603 2. server.bundle1.<action>
605 2. server.bundle1.<action>
604 3. server.bundle1gd (if generaldelta active)
606 3. server.bundle1gd (if generaldelta active)
605 4. server.bundle1
607 4. server.bundle1
606 """
608 """
607 ui = repo.ui
609 ui = repo.ui
608 gd = 'generaldelta' in repo.requirements
610 gd = 'generaldelta' in repo.requirements
609
611
610 if gd:
612 if gd:
611 v = ui.configbool('server', 'bundle1gd.%s' % action)
613 v = ui.configbool('server', 'bundle1gd.%s' % action)
612 if v is not None:
614 if v is not None:
613 return v
615 return v
614
616
615 v = ui.configbool('server', 'bundle1.%s' % action)
617 v = ui.configbool('server', 'bundle1.%s' % action)
616 if v is not None:
618 if v is not None:
617 return v
619 return v
618
620
619 if gd:
621 if gd:
620 v = ui.configbool('server', 'bundle1gd')
622 v = ui.configbool('server', 'bundle1gd')
621 if v is not None:
623 if v is not None:
622 return v
624 return v
623
625
624 return ui.configbool('server', 'bundle1')
626 return ui.configbool('server', 'bundle1')
625
627
626 def supportedcompengines(ui, proto, role):
628 def supportedcompengines(ui, proto, role):
627 """Obtain the list of supported compression engines for a request."""
629 """Obtain the list of supported compression engines for a request."""
628 assert role in (util.CLIENTROLE, util.SERVERROLE)
630 assert role in (util.CLIENTROLE, util.SERVERROLE)
629
631
630 compengines = util.compengines.supportedwireengines(role)
632 compengines = util.compengines.supportedwireengines(role)
631
633
632 # Allow config to override default list and ordering.
634 # Allow config to override default list and ordering.
633 if role == util.SERVERROLE:
635 if role == util.SERVERROLE:
634 configengines = ui.configlist('server', 'compressionengines')
636 configengines = ui.configlist('server', 'compressionengines')
635 config = 'server.compressionengines'
637 config = 'server.compressionengines'
636 else:
638 else:
637 # This is currently implemented mainly to facilitate testing. In most
639 # This is currently implemented mainly to facilitate testing. In most
638 # cases, the server should be in charge of choosing a compression engine
640 # cases, the server should be in charge of choosing a compression engine
639 # because a server has the most to lose from a sub-optimal choice. (e.g.
641 # because a server has the most to lose from a sub-optimal choice. (e.g.
640 # CPU DoS due to an expensive engine or a network DoS due to poor
642 # CPU DoS due to an expensive engine or a network DoS due to poor
641 # compression ratio).
643 # compression ratio).
642 configengines = ui.configlist('experimental',
644 configengines = ui.configlist('experimental',
643 'clientcompressionengines')
645 'clientcompressionengines')
644 config = 'experimental.clientcompressionengines'
646 config = 'experimental.clientcompressionengines'
645
647
646 # No explicit config. Filter out the ones that aren't supposed to be
648 # No explicit config. Filter out the ones that aren't supposed to be
647 # advertised and return default ordering.
649 # advertised and return default ordering.
648 if not configengines:
650 if not configengines:
649 attr = 'serverpriority' if role == util.SERVERROLE else 'clientpriority'
651 attr = 'serverpriority' if role == util.SERVERROLE else 'clientpriority'
650 return [e for e in compengines
652 return [e for e in compengines
651 if getattr(e.wireprotosupport(), attr) > 0]
653 if getattr(e.wireprotosupport(), attr) > 0]
652
654
653 # If compression engines are listed in the config, assume there is a good
655 # If compression engines are listed in the config, assume there is a good
654 # reason for it (like server operators wanting to achieve specific
656 # reason for it (like server operators wanting to achieve specific
655 # performance characteristics). So fail fast if the config references
657 # performance characteristics). So fail fast if the config references
656 # unusable compression engines.
658 # unusable compression engines.
657 validnames = set(e.name() for e in compengines)
659 validnames = set(e.name() for e in compengines)
658 invalidnames = set(e for e in configengines if e not in validnames)
660 invalidnames = set(e for e in configengines if e not in validnames)
659 if invalidnames:
661 if invalidnames:
660 raise error.Abort(_('invalid compression engine defined in %s: %s') %
662 raise error.Abort(_('invalid compression engine defined in %s: %s') %
661 (config, ', '.join(sorted(invalidnames))))
663 (config, ', '.join(sorted(invalidnames))))
662
664
663 compengines = [e for e in compengines if e.name() in configengines]
665 compengines = [e for e in compengines if e.name() in configengines]
664 compengines = sorted(compengines,
666 compengines = sorted(compengines,
665 key=lambda e: configengines.index(e.name()))
667 key=lambda e: configengines.index(e.name()))
666
668
667 if not compengines:
669 if not compengines:
668 raise error.Abort(_('%s config option does not specify any known '
670 raise error.Abort(_('%s config option does not specify any known '
669 'compression engines') % config,
671 'compression engines') % config,
670 hint=_('usable compression engines: %s') %
672 hint=_('usable compression engines: %s') %
671 ', '.sorted(validnames))
673 ', '.sorted(validnames))
672
674
673 return compengines
675 return compengines
674
676
675 # list of commands
677 # list of commands
676 commands = {}
678 commands = {}
677
679
678 def wireprotocommand(name, args=''):
680 def wireprotocommand(name, args=''):
679 """decorator for wire protocol command"""
681 """decorator for wire protocol command"""
680 def register(func):
682 def register(func):
681 commands[name] = (func, args)
683 commands[name] = (func, args)
682 return func
684 return func
683 return register
685 return register
684
686
685 @wireprotocommand('batch', 'cmds *')
687 @wireprotocommand('batch', 'cmds *')
686 def batch(repo, proto, cmds, others):
688 def batch(repo, proto, cmds, others):
687 repo = repo.filtered("served")
689 repo = repo.filtered("served")
688 res = []
690 res = []
689 for pair in cmds.split(';'):
691 for pair in cmds.split(';'):
690 op, args = pair.split(' ', 1)
692 op, args = pair.split(' ', 1)
691 vals = {}
693 vals = {}
692 for a in args.split(','):
694 for a in args.split(','):
693 if a:
695 if a:
694 n, v = a.split('=')
696 n, v = a.split('=')
695 vals[unescapearg(n)] = unescapearg(v)
697 vals[unescapearg(n)] = unescapearg(v)
696 func, spec = commands[op]
698 func, spec = commands[op]
697 if spec:
699 if spec:
698 keys = spec.split()
700 keys = spec.split()
699 data = {}
701 data = {}
700 for k in keys:
702 for k in keys:
701 if k == '*':
703 if k == '*':
702 star = {}
704 star = {}
703 for key in vals.keys():
705 for key in vals.keys():
704 if key not in keys:
706 if key not in keys:
705 star[key] = vals[key]
707 star[key] = vals[key]
706 data['*'] = star
708 data['*'] = star
707 else:
709 else:
708 data[k] = vals[k]
710 data[k] = vals[k]
709 result = func(repo, proto, *[data[k] for k in keys])
711 result = func(repo, proto, *[data[k] for k in keys])
710 else:
712 else:
711 result = func(repo, proto)
713 result = func(repo, proto)
712 if isinstance(result, ooberror):
714 if isinstance(result, ooberror):
713 return result
715 return result
714 res.append(escapearg(result))
716 res.append(escapearg(result))
715 return ';'.join(res)
717 return ';'.join(res)
716
718
717 @wireprotocommand('between', 'pairs')
719 @wireprotocommand('between', 'pairs')
718 def between(repo, proto, pairs):
720 def between(repo, proto, pairs):
719 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
721 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
720 r = []
722 r = []
721 for b in repo.between(pairs):
723 for b in repo.between(pairs):
722 r.append(encodelist(b) + "\n")
724 r.append(encodelist(b) + "\n")
723 return "".join(r)
725 return "".join(r)
724
726
725 @wireprotocommand('branchmap')
727 @wireprotocommand('branchmap')
726 def branchmap(repo, proto):
728 def branchmap(repo, proto):
727 branchmap = repo.branchmap()
729 branchmap = repo.branchmap()
728 heads = []
730 heads = []
729 for branch, nodes in branchmap.iteritems():
731 for branch, nodes in branchmap.iteritems():
730 branchname = urlreq.quote(encoding.fromlocal(branch))
732 branchname = urlreq.quote(encoding.fromlocal(branch))
731 branchnodes = encodelist(nodes)
733 branchnodes = encodelist(nodes)
732 heads.append('%s %s' % (branchname, branchnodes))
734 heads.append('%s %s' % (branchname, branchnodes))
733 return '\n'.join(heads)
735 return '\n'.join(heads)
734
736
735 @wireprotocommand('branches', 'nodes')
737 @wireprotocommand('branches', 'nodes')
736 def branches(repo, proto, nodes):
738 def branches(repo, proto, nodes):
737 nodes = decodelist(nodes)
739 nodes = decodelist(nodes)
738 r = []
740 r = []
739 for b in repo.branches(nodes):
741 for b in repo.branches(nodes):
740 r.append(encodelist(b) + "\n")
742 r.append(encodelist(b) + "\n")
741 return "".join(r)
743 return "".join(r)
742
744
743 @wireprotocommand('clonebundles', '')
745 @wireprotocommand('clonebundles', '')
744 def clonebundles(repo, proto):
746 def clonebundles(repo, proto):
745 """Server command for returning info for available bundles to seed clones.
747 """Server command for returning info for available bundles to seed clones.
746
748
747 Clients will parse this response and determine what bundle to fetch.
749 Clients will parse this response and determine what bundle to fetch.
748
750
749 Extensions may wrap this command to filter or dynamically emit data
751 Extensions may wrap this command to filter or dynamically emit data
750 depending on the request. e.g. you could advertise URLs for the closest
752 depending on the request. e.g. you could advertise URLs for the closest
751 data center given the client's IP address.
753 data center given the client's IP address.
752 """
754 """
753 return repo.vfs.tryread('clonebundles.manifest')
755 return repo.vfs.tryread('clonebundles.manifest')
754
756
755 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
757 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
756 'known', 'getbundle', 'unbundlehash', 'batch']
758 'known', 'getbundle', 'unbundlehash', 'batch']
757
759
758 def _capabilities(repo, proto):
760 def _capabilities(repo, proto):
759 """return a list of capabilities for a repo
761 """return a list of capabilities for a repo
760
762
761 This function exists to allow extensions to easily wrap capabilities
763 This function exists to allow extensions to easily wrap capabilities
762 computation
764 computation
763
765
764 - returns a lists: easy to alter
766 - returns a lists: easy to alter
765 - change done here will be propagated to both `capabilities` and `hello`
767 - change done here will be propagated to both `capabilities` and `hello`
766 command without any other action needed.
768 command without any other action needed.
767 """
769 """
768 # copy to prevent modification of the global list
770 # copy to prevent modification of the global list
769 caps = list(wireprotocaps)
771 caps = list(wireprotocaps)
770 if streamclone.allowservergeneration(repo):
772 if streamclone.allowservergeneration(repo):
771 if repo.ui.configbool('server', 'preferuncompressed'):
773 if repo.ui.configbool('server', 'preferuncompressed'):
772 caps.append('stream-preferred')
774 caps.append('stream-preferred')
773 requiredformats = repo.requirements & repo.supportedformats
775 requiredformats = repo.requirements & repo.supportedformats
774 # if our local revlogs are just revlogv1, add 'stream' cap
776 # if our local revlogs are just revlogv1, add 'stream' cap
775 if not requiredformats - {'revlogv1'}:
777 if not requiredformats - {'revlogv1'}:
776 caps.append('stream')
778 caps.append('stream')
777 # otherwise, add 'streamreqs' detailing our local revlog format
779 # otherwise, add 'streamreqs' detailing our local revlog format
778 else:
780 else:
779 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
781 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
780 if repo.ui.configbool('experimental', 'bundle2-advertise'):
782 if repo.ui.configbool('experimental', 'bundle2-advertise'):
781 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
783 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
782 caps.append('bundle2=' + urlreq.quote(capsblob))
784 caps.append('bundle2=' + urlreq.quote(capsblob))
783 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
785 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
784
786
785 if proto.name == 'http':
787 if proto.name == 'http':
786 caps.append('httpheader=%d' %
788 caps.append('httpheader=%d' %
787 repo.ui.configint('server', 'maxhttpheaderlen'))
789 repo.ui.configint('server', 'maxhttpheaderlen'))
788 if repo.ui.configbool('experimental', 'httppostargs'):
790 if repo.ui.configbool('experimental', 'httppostargs'):
789 caps.append('httppostargs')
791 caps.append('httppostargs')
790
792
791 # FUTURE advertise 0.2rx once support is implemented
793 # FUTURE advertise 0.2rx once support is implemented
792 # FUTURE advertise minrx and mintx after consulting config option
794 # FUTURE advertise minrx and mintx after consulting config option
793 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
795 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
794
796
795 compengines = supportedcompengines(repo.ui, proto, util.SERVERROLE)
797 compengines = supportedcompengines(repo.ui, proto, util.SERVERROLE)
796 if compengines:
798 if compengines:
797 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
799 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
798 for e in compengines)
800 for e in compengines)
799 caps.append('compression=%s' % comptypes)
801 caps.append('compression=%s' % comptypes)
800
802
801 return caps
803 return caps
802
804
803 # If you are writing an extension and consider wrapping this function. Wrap
805 # If you are writing an extension and consider wrapping this function. Wrap
804 # `_capabilities` instead.
806 # `_capabilities` instead.
805 @wireprotocommand('capabilities')
807 @wireprotocommand('capabilities')
806 def capabilities(repo, proto):
808 def capabilities(repo, proto):
807 return ' '.join(_capabilities(repo, proto))
809 return ' '.join(_capabilities(repo, proto))
808
810
809 @wireprotocommand('changegroup', 'roots')
811 @wireprotocommand('changegroup', 'roots')
810 def changegroup(repo, proto, roots):
812 def changegroup(repo, proto, roots):
811 nodes = decodelist(roots)
813 nodes = decodelist(roots)
812 outgoing = discovery.outgoing(repo, missingroots=nodes,
814 outgoing = discovery.outgoing(repo, missingroots=nodes,
813 missingheads=repo.heads())
815 missingheads=repo.heads())
814 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
816 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
815 gen = iter(lambda: cg.read(32768), '')
817 gen = iter(lambda: cg.read(32768), '')
816 return streamres(gen=gen)
818 return streamres(gen=gen)
817
819
818 @wireprotocommand('changegroupsubset', 'bases heads')
820 @wireprotocommand('changegroupsubset', 'bases heads')
819 def changegroupsubset(repo, proto, bases, heads):
821 def changegroupsubset(repo, proto, bases, heads):
820 bases = decodelist(bases)
822 bases = decodelist(bases)
821 heads = decodelist(heads)
823 heads = decodelist(heads)
822 outgoing = discovery.outgoing(repo, missingroots=bases,
824 outgoing = discovery.outgoing(repo, missingroots=bases,
823 missingheads=heads)
825 missingheads=heads)
824 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
826 cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
825 gen = iter(lambda: cg.read(32768), '')
827 gen = iter(lambda: cg.read(32768), '')
826 return streamres(gen=gen)
828 return streamres(gen=gen)
827
829
828 @wireprotocommand('debugwireargs', 'one two *')
830 @wireprotocommand('debugwireargs', 'one two *')
829 def debugwireargs(repo, proto, one, two, others):
831 def debugwireargs(repo, proto, one, two, others):
830 # only accept optional args from the known set
832 # only accept optional args from the known set
831 opts = options('debugwireargs', ['three', 'four'], others)
833 opts = options('debugwireargs', ['three', 'four'], others)
832 return repo.debugwireargs(one, two, **pycompat.strkwargs(opts))
834 return repo.debugwireargs(one, two, **pycompat.strkwargs(opts))
833
835
834 @wireprotocommand('getbundle', '*')
836 @wireprotocommand('getbundle', '*')
835 def getbundle(repo, proto, others):
837 def getbundle(repo, proto, others):
836 opts = options('getbundle', gboptsmap.keys(), others)
838 opts = options('getbundle', gboptsmap.keys(), others)
837 for k, v in opts.iteritems():
839 for k, v in opts.iteritems():
838 keytype = gboptsmap[k]
840 keytype = gboptsmap[k]
839 if keytype == 'nodes':
841 if keytype == 'nodes':
840 opts[k] = decodelist(v)
842 opts[k] = decodelist(v)
841 elif keytype == 'csv':
843 elif keytype == 'csv':
842 opts[k] = list(v.split(','))
844 opts[k] = list(v.split(','))
843 elif keytype == 'scsv':
845 elif keytype == 'scsv':
844 opts[k] = set(v.split(','))
846 opts[k] = set(v.split(','))
845 elif keytype == 'boolean':
847 elif keytype == 'boolean':
846 # Client should serialize False as '0', which is a non-empty string
848 # Client should serialize False as '0', which is a non-empty string
847 # so it evaluates as a True bool.
849 # so it evaluates as a True bool.
848 if v == '0':
850 if v == '0':
849 opts[k] = False
851 opts[k] = False
850 else:
852 else:
851 opts[k] = bool(v)
853 opts[k] = bool(v)
852 elif keytype != 'plain':
854 elif keytype != 'plain':
853 raise KeyError('unknown getbundle option type %s'
855 raise KeyError('unknown getbundle option type %s'
854 % keytype)
856 % keytype)
855
857
856 if not bundle1allowed(repo, 'pull'):
858 if not bundle1allowed(repo, 'pull'):
857 if not exchange.bundle2requested(opts.get('bundlecaps')):
859 if not exchange.bundle2requested(opts.get('bundlecaps')):
858 if proto.name == 'http':
860 if proto.name == 'http':
859 return ooberror(bundle2required)
861 return ooberror(bundle2required)
860 raise error.Abort(bundle2requiredmain,
862 raise error.Abort(bundle2requiredmain,
861 hint=bundle2requiredhint)
863 hint=bundle2requiredhint)
862
864
863 try:
865 try:
864 if repo.ui.configbool('server', 'disablefullbundle'):
866 if repo.ui.configbool('server', 'disablefullbundle'):
865 # Check to see if this is a full clone.
867 # Check to see if this is a full clone.
866 clheads = set(repo.changelog.heads())
868 clheads = set(repo.changelog.heads())
867 heads = set(opts.get('heads', set()))
869 heads = set(opts.get('heads', set()))
868 common = set(opts.get('common', set()))
870 common = set(opts.get('common', set()))
869 common.discard(nullid)
871 common.discard(nullid)
870 if not common and clheads == heads:
872 if not common and clheads == heads:
871 raise error.Abort(
873 raise error.Abort(
872 _('server has pull-based clones disabled'),
874 _('server has pull-based clones disabled'),
873 hint=_('remove --pull if specified or upgrade Mercurial'))
875 hint=_('remove --pull if specified or upgrade Mercurial'))
874
876
875 chunks = exchange.getbundlechunks(repo, 'serve',
877 chunks = exchange.getbundlechunks(repo, 'serve',
876 **pycompat.strkwargs(opts))
878 **pycompat.strkwargs(opts))
877 except error.Abort as exc:
879 except error.Abort as exc:
878 # cleanly forward Abort error to the client
880 # cleanly forward Abort error to the client
879 if not exchange.bundle2requested(opts.get('bundlecaps')):
881 if not exchange.bundle2requested(opts.get('bundlecaps')):
880 if proto.name == 'http':
882 if proto.name == 'http':
881 return ooberror(str(exc) + '\n')
883 return ooberror(str(exc) + '\n')
882 raise # cannot do better for bundle1 + ssh
884 raise # cannot do better for bundle1 + ssh
883 # bundle2 request expect a bundle2 reply
885 # bundle2 request expect a bundle2 reply
884 bundler = bundle2.bundle20(repo.ui)
886 bundler = bundle2.bundle20(repo.ui)
885 manargs = [('message', str(exc))]
887 manargs = [('message', str(exc))]
886 advargs = []
888 advargs = []
887 if exc.hint is not None:
889 if exc.hint is not None:
888 advargs.append(('hint', exc.hint))
890 advargs.append(('hint', exc.hint))
889 bundler.addpart(bundle2.bundlepart('error:abort',
891 bundler.addpart(bundle2.bundlepart('error:abort',
890 manargs, advargs))
892 manargs, advargs))
891 return streamres(gen=bundler.getchunks())
893 return streamres(gen=bundler.getchunks())
892 return streamres(gen=chunks)
894 return streamres(gen=chunks)
893
895
894 @wireprotocommand('heads')
896 @wireprotocommand('heads')
895 def heads(repo, proto):
897 def heads(repo, proto):
896 h = repo.heads()
898 h = repo.heads()
897 return encodelist(h) + "\n"
899 return encodelist(h) + "\n"
898
900
899 @wireprotocommand('hello')
901 @wireprotocommand('hello')
900 def hello(repo, proto):
902 def hello(repo, proto):
901 '''the hello command returns a set of lines describing various
903 '''the hello command returns a set of lines describing various
902 interesting things about the server, in an RFC822-like format.
904 interesting things about the server, in an RFC822-like format.
903 Currently the only one defined is "capabilities", which
905 Currently the only one defined is "capabilities", which
904 consists of a line in the form:
906 consists of a line in the form:
905
907
906 capabilities: space separated list of tokens
908 capabilities: space separated list of tokens
907 '''
909 '''
908 return "capabilities: %s\n" % (capabilities(repo, proto))
910 return "capabilities: %s\n" % (capabilities(repo, proto))
909
911
910 @wireprotocommand('listkeys', 'namespace')
912 @wireprotocommand('listkeys', 'namespace')
911 def listkeys(repo, proto, namespace):
913 def listkeys(repo, proto, namespace):
912 d = repo.listkeys(encoding.tolocal(namespace)).items()
914 d = repo.listkeys(encoding.tolocal(namespace)).items()
913 return pushkeymod.encodekeys(d)
915 return pushkeymod.encodekeys(d)
914
916
915 @wireprotocommand('lookup', 'key')
917 @wireprotocommand('lookup', 'key')
916 def lookup(repo, proto, key):
918 def lookup(repo, proto, key):
917 try:
919 try:
918 k = encoding.tolocal(key)
920 k = encoding.tolocal(key)
919 c = repo[k]
921 c = repo[k]
920 r = c.hex()
922 r = c.hex()
921 success = 1
923 success = 1
922 except Exception as inst:
924 except Exception as inst:
923 r = str(inst)
925 r = str(inst)
924 success = 0
926 success = 0
925 return "%d %s\n" % (success, r)
927 return "%d %s\n" % (success, r)
926
928
927 @wireprotocommand('known', 'nodes *')
929 @wireprotocommand('known', 'nodes *')
928 def known(repo, proto, nodes, others):
930 def known(repo, proto, nodes, others):
929 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
931 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
930
932
931 @wireprotocommand('pushkey', 'namespace key old new')
933 @wireprotocommand('pushkey', 'namespace key old new')
932 def pushkey(repo, proto, namespace, key, old, new):
934 def pushkey(repo, proto, namespace, key, old, new):
933 # compatibility with pre-1.8 clients which were accidentally
935 # compatibility with pre-1.8 clients which were accidentally
934 # sending raw binary nodes rather than utf-8-encoded hex
936 # sending raw binary nodes rather than utf-8-encoded hex
935 if len(new) == 20 and util.escapestr(new) != new:
937 if len(new) == 20 and util.escapestr(new) != new:
936 # looks like it could be a binary node
938 # looks like it could be a binary node
937 try:
939 try:
938 new.decode('utf-8')
940 new.decode('utf-8')
939 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
941 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
940 except UnicodeDecodeError:
942 except UnicodeDecodeError:
941 pass # binary, leave unmodified
943 pass # binary, leave unmodified
942 else:
944 else:
943 new = encoding.tolocal(new) # normal path
945 new = encoding.tolocal(new) # normal path
944
946
945 if util.safehasattr(proto, 'restore'):
947 if util.safehasattr(proto, 'restore'):
946
948
947 proto.redirect()
949 proto.redirect()
948
950
949 try:
951 try:
950 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
952 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
951 encoding.tolocal(old), new) or False
953 encoding.tolocal(old), new) or False
952 except error.Abort:
954 except error.Abort:
953 r = False
955 r = False
954
956
955 output = proto.restore()
957 output = proto.restore()
956
958
957 return '%s\n%s' % (int(r), output)
959 return '%s\n%s' % (int(r), output)
958
960
959 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
961 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
960 encoding.tolocal(old), new)
962 encoding.tolocal(old), new)
961 return '%s\n' % int(r)
963 return '%s\n' % int(r)
962
964
963 @wireprotocommand('stream_out')
965 @wireprotocommand('stream_out')
964 def stream(repo, proto):
966 def stream(repo, proto):
965 '''If the server supports streaming clone, it advertises the "stream"
967 '''If the server supports streaming clone, it advertises the "stream"
966 capability with a value representing the version and flags of the repo
968 capability with a value representing the version and flags of the repo
967 it is serving. Client checks to see if it understands the format.
969 it is serving. Client checks to see if it understands the format.
968 '''
970 '''
969 return streamres_legacy(streamclone.generatev1wireproto(repo))
971 return streamres_legacy(streamclone.generatev1wireproto(repo))
970
972
971 @wireprotocommand('unbundle', 'heads')
973 @wireprotocommand('unbundle', 'heads')
972 def unbundle(repo, proto, heads):
974 def unbundle(repo, proto, heads):
973 their_heads = decodelist(heads)
975 their_heads = decodelist(heads)
974
976
975 try:
977 try:
976 proto.redirect()
978 proto.redirect()
977
979
978 exchange.check_heads(repo, their_heads, 'preparing changes')
980 exchange.check_heads(repo, their_heads, 'preparing changes')
979
981
980 # write bundle data to temporary file because it can be big
982 # write bundle data to temporary file because it can be big
981 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
983 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
982 fp = os.fdopen(fd, pycompat.sysstr('wb+'))
984 fp = os.fdopen(fd, pycompat.sysstr('wb+'))
983 r = 0
985 r = 0
984 try:
986 try:
985 proto.getfile(fp)
987 proto.getfile(fp)
986 fp.seek(0)
988 fp.seek(0)
987 gen = exchange.readbundle(repo.ui, fp, None)
989 gen = exchange.readbundle(repo.ui, fp, None)
988 if (isinstance(gen, changegroupmod.cg1unpacker)
990 if (isinstance(gen, changegroupmod.cg1unpacker)
989 and not bundle1allowed(repo, 'push')):
991 and not bundle1allowed(repo, 'push')):
990 if proto.name == 'http':
992 if proto.name == 'http':
991 # need to special case http because stderr do not get to
993 # need to special case http because stderr do not get to
992 # the http client on failed push so we need to abuse some
994 # the http client on failed push so we need to abuse some
993 # other error type to make sure the message get to the
995 # other error type to make sure the message get to the
994 # user.
996 # user.
995 return ooberror(bundle2required)
997 return ooberror(bundle2required)
996 raise error.Abort(bundle2requiredmain,
998 raise error.Abort(bundle2requiredmain,
997 hint=bundle2requiredhint)
999 hint=bundle2requiredhint)
998
1000
999 r = exchange.unbundle(repo, gen, their_heads, 'serve',
1001 r = exchange.unbundle(repo, gen, their_heads, 'serve',
1000 proto._client())
1002 proto._client())
1001 if util.safehasattr(r, 'addpart'):
1003 if util.safehasattr(r, 'addpart'):
1002 # The return looks streamable, we are in the bundle2 case and
1004 # The return looks streamable, we are in the bundle2 case and
1003 # should return a stream.
1005 # should return a stream.
1004 return streamres_legacy(gen=r.getchunks())
1006 return streamres_legacy(gen=r.getchunks())
1005 return pushres(r)
1007 return pushres(r)
1006
1008
1007 finally:
1009 finally:
1008 fp.close()
1010 fp.close()
1009 os.unlink(tempname)
1011 os.unlink(tempname)
1010
1012
1011 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
1013 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
1012 # handle non-bundle2 case first
1014 # handle non-bundle2 case first
1013 if not getattr(exc, 'duringunbundle2', False):
1015 if not getattr(exc, 'duringunbundle2', False):
1014 try:
1016 try:
1015 raise
1017 raise
1016 except error.Abort:
1018 except error.Abort:
1017 # The old code we moved used util.stderr directly.
1019 # The old code we moved used util.stderr directly.
1018 # We did not change it to minimise code change.
1020 # We did not change it to minimise code change.
1019 # This need to be moved to something proper.
1021 # This need to be moved to something proper.
1020 # Feel free to do it.
1022 # Feel free to do it.
1021 util.stderr.write("abort: %s\n" % exc)
1023 util.stderr.write("abort: %s\n" % exc)
1022 if exc.hint is not None:
1024 if exc.hint is not None:
1023 util.stderr.write("(%s)\n" % exc.hint)
1025 util.stderr.write("(%s)\n" % exc.hint)
1024 return pushres(0)
1026 return pushres(0)
1025 except error.PushRaced:
1027 except error.PushRaced:
1026 return pusherr(str(exc))
1028 return pusherr(str(exc))
1027
1029
1028 bundler = bundle2.bundle20(repo.ui)
1030 bundler = bundle2.bundle20(repo.ui)
1029 for out in getattr(exc, '_bundle2salvagedoutput', ()):
1031 for out in getattr(exc, '_bundle2salvagedoutput', ()):
1030 bundler.addpart(out)
1032 bundler.addpart(out)
1031 try:
1033 try:
1032 try:
1034 try:
1033 raise
1035 raise
1034 except error.PushkeyFailed as exc:
1036 except error.PushkeyFailed as exc:
1035 # check client caps
1037 # check client caps
1036 remotecaps = getattr(exc, '_replycaps', None)
1038 remotecaps = getattr(exc, '_replycaps', None)
1037 if (remotecaps is not None
1039 if (remotecaps is not None
1038 and 'pushkey' not in remotecaps.get('error', ())):
1040 and 'pushkey' not in remotecaps.get('error', ())):
1039 # no support remote side, fallback to Abort handler.
1041 # no support remote side, fallback to Abort handler.
1040 raise
1042 raise
1041 part = bundler.newpart('error:pushkey')
1043 part = bundler.newpart('error:pushkey')
1042 part.addparam('in-reply-to', exc.partid)
1044 part.addparam('in-reply-to', exc.partid)
1043 if exc.namespace is not None:
1045 if exc.namespace is not None:
1044 part.addparam('namespace', exc.namespace, mandatory=False)
1046 part.addparam('namespace', exc.namespace, mandatory=False)
1045 if exc.key is not None:
1047 if exc.key is not None:
1046 part.addparam('key', exc.key, mandatory=False)
1048 part.addparam('key', exc.key, mandatory=False)
1047 if exc.new is not None:
1049 if exc.new is not None:
1048 part.addparam('new', exc.new, mandatory=False)
1050 part.addparam('new', exc.new, mandatory=False)
1049 if exc.old is not None:
1051 if exc.old is not None:
1050 part.addparam('old', exc.old, mandatory=False)
1052 part.addparam('old', exc.old, mandatory=False)
1051 if exc.ret is not None:
1053 if exc.ret is not None:
1052 part.addparam('ret', exc.ret, mandatory=False)
1054 part.addparam('ret', exc.ret, mandatory=False)
1053 except error.BundleValueError as exc:
1055 except error.BundleValueError as exc:
1054 errpart = bundler.newpart('error:unsupportedcontent')
1056 errpart = bundler.newpart('error:unsupportedcontent')
1055 if exc.parttype is not None:
1057 if exc.parttype is not None:
1056 errpart.addparam('parttype', exc.parttype)
1058 errpart.addparam('parttype', exc.parttype)
1057 if exc.params:
1059 if exc.params:
1058 errpart.addparam('params', '\0'.join(exc.params))
1060 errpart.addparam('params', '\0'.join(exc.params))
1059 except error.Abort as exc:
1061 except error.Abort as exc:
1060 manargs = [('message', str(exc))]
1062 manargs = [('message', str(exc))]
1061 advargs = []
1063 advargs = []
1062 if exc.hint is not None:
1064 if exc.hint is not None:
1063 advargs.append(('hint', exc.hint))
1065 advargs.append(('hint', exc.hint))
1064 bundler.addpart(bundle2.bundlepart('error:abort',
1066 bundler.addpart(bundle2.bundlepart('error:abort',
1065 manargs, advargs))
1067 manargs, advargs))
1066 except error.PushRaced as exc:
1068 except error.PushRaced as exc:
1067 bundler.newpart('error:pushraced', [('message', str(exc))])
1069 bundler.newpart('error:pushraced', [('message', str(exc))])
1068 return streamres_legacy(gen=bundler.getchunks())
1070 return streamres_legacy(gen=bundler.getchunks())
General Comments 0
You need to be logged in to leave comments. Login now