##// END OF EJS Templates
getbundle: cleanly handle remote abort during getbundle...
Pierre-Yves David -
r30913:d70971a3 stable
parent child Browse files
Show More
@@ -1,2008 +1,2011 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 errno
10 import errno
11 import hashlib
11 import hashlib
12
12
13 from .i18n import _
13 from .i18n import _
14 from .node import (
14 from .node import (
15 hex,
15 hex,
16 nullid,
16 nullid,
17 )
17 )
18 from . import (
18 from . import (
19 base85,
19 base85,
20 bookmarks as bookmod,
20 bookmarks as bookmod,
21 bundle2,
21 bundle2,
22 changegroup,
22 changegroup,
23 discovery,
23 discovery,
24 error,
24 error,
25 lock as lockmod,
25 lock as lockmod,
26 obsolete,
26 obsolete,
27 phases,
27 phases,
28 pushkey,
28 pushkey,
29 scmutil,
29 scmutil,
30 sslutil,
30 sslutil,
31 streamclone,
31 streamclone,
32 tags,
32 tags,
33 url as urlmod,
33 url as urlmod,
34 util,
34 util,
35 )
35 )
36
36
37 urlerr = util.urlerr
37 urlerr = util.urlerr
38 urlreq = util.urlreq
38 urlreq = util.urlreq
39
39
40 # Maps bundle version human names to changegroup versions.
40 # Maps bundle version human names to changegroup versions.
41 _bundlespeccgversions = {'v1': '01',
41 _bundlespeccgversions = {'v1': '01',
42 'v2': '02',
42 'v2': '02',
43 'packed1': 's1',
43 'packed1': 's1',
44 'bundle2': '02', #legacy
44 'bundle2': '02', #legacy
45 }
45 }
46
46
47 def parsebundlespec(repo, spec, strict=True, externalnames=False):
47 def parsebundlespec(repo, spec, strict=True, externalnames=False):
48 """Parse a bundle string specification into parts.
48 """Parse a bundle string specification into parts.
49
49
50 Bundle specifications denote a well-defined bundle/exchange format.
50 Bundle specifications denote a well-defined bundle/exchange format.
51 The content of a given specification should not change over time in
51 The content of a given specification should not change over time in
52 order to ensure that bundles produced by a newer version of Mercurial are
52 order to ensure that bundles produced by a newer version of Mercurial are
53 readable from an older version.
53 readable from an older version.
54
54
55 The string currently has the form:
55 The string currently has the form:
56
56
57 <compression>-<type>[;<parameter0>[;<parameter1>]]
57 <compression>-<type>[;<parameter0>[;<parameter1>]]
58
58
59 Where <compression> is one of the supported compression formats
59 Where <compression> is one of the supported compression formats
60 and <type> is (currently) a version string. A ";" can follow the type and
60 and <type> is (currently) a version string. A ";" can follow the type and
61 all text afterwards is interpreted as URI encoded, ";" delimited key=value
61 all text afterwards is interpreted as URI encoded, ";" delimited key=value
62 pairs.
62 pairs.
63
63
64 If ``strict`` is True (the default) <compression> is required. Otherwise,
64 If ``strict`` is True (the default) <compression> is required. Otherwise,
65 it is optional.
65 it is optional.
66
66
67 If ``externalnames`` is False (the default), the human-centric names will
67 If ``externalnames`` is False (the default), the human-centric names will
68 be converted to their internal representation.
68 be converted to their internal representation.
69
69
70 Returns a 3-tuple of (compression, version, parameters). Compression will
70 Returns a 3-tuple of (compression, version, parameters). Compression will
71 be ``None`` if not in strict mode and a compression isn't defined.
71 be ``None`` if not in strict mode and a compression isn't defined.
72
72
73 An ``InvalidBundleSpecification`` is raised when the specification is
73 An ``InvalidBundleSpecification`` is raised when the specification is
74 not syntactically well formed.
74 not syntactically well formed.
75
75
76 An ``UnsupportedBundleSpecification`` is raised when the compression or
76 An ``UnsupportedBundleSpecification`` is raised when the compression or
77 bundle type/version is not recognized.
77 bundle type/version is not recognized.
78
78
79 Note: this function will likely eventually return a more complex data
79 Note: this function will likely eventually return a more complex data
80 structure, including bundle2 part information.
80 structure, including bundle2 part information.
81 """
81 """
82 def parseparams(s):
82 def parseparams(s):
83 if ';' not in s:
83 if ';' not in s:
84 return s, {}
84 return s, {}
85
85
86 params = {}
86 params = {}
87 version, paramstr = s.split(';', 1)
87 version, paramstr = s.split(';', 1)
88
88
89 for p in paramstr.split(';'):
89 for p in paramstr.split(';'):
90 if '=' not in p:
90 if '=' not in p:
91 raise error.InvalidBundleSpecification(
91 raise error.InvalidBundleSpecification(
92 _('invalid bundle specification: '
92 _('invalid bundle specification: '
93 'missing "=" in parameter: %s') % p)
93 'missing "=" in parameter: %s') % p)
94
94
95 key, value = p.split('=', 1)
95 key, value = p.split('=', 1)
96 key = urlreq.unquote(key)
96 key = urlreq.unquote(key)
97 value = urlreq.unquote(value)
97 value = urlreq.unquote(value)
98 params[key] = value
98 params[key] = value
99
99
100 return version, params
100 return version, params
101
101
102
102
103 if strict and '-' not in spec:
103 if strict and '-' not in spec:
104 raise error.InvalidBundleSpecification(
104 raise error.InvalidBundleSpecification(
105 _('invalid bundle specification; '
105 _('invalid bundle specification; '
106 'must be prefixed with compression: %s') % spec)
106 'must be prefixed with compression: %s') % spec)
107
107
108 if '-' in spec:
108 if '-' in spec:
109 compression, version = spec.split('-', 1)
109 compression, version = spec.split('-', 1)
110
110
111 if compression not in util.compengines.supportedbundlenames:
111 if compression not in util.compengines.supportedbundlenames:
112 raise error.UnsupportedBundleSpecification(
112 raise error.UnsupportedBundleSpecification(
113 _('%s compression is not supported') % compression)
113 _('%s compression is not supported') % compression)
114
114
115 version, params = parseparams(version)
115 version, params = parseparams(version)
116
116
117 if version not in _bundlespeccgversions:
117 if version not in _bundlespeccgversions:
118 raise error.UnsupportedBundleSpecification(
118 raise error.UnsupportedBundleSpecification(
119 _('%s is not a recognized bundle version') % version)
119 _('%s is not a recognized bundle version') % version)
120 else:
120 else:
121 # Value could be just the compression or just the version, in which
121 # Value could be just the compression or just the version, in which
122 # case some defaults are assumed (but only when not in strict mode).
122 # case some defaults are assumed (but only when not in strict mode).
123 assert not strict
123 assert not strict
124
124
125 spec, params = parseparams(spec)
125 spec, params = parseparams(spec)
126
126
127 if spec in util.compengines.supportedbundlenames:
127 if spec in util.compengines.supportedbundlenames:
128 compression = spec
128 compression = spec
129 version = 'v1'
129 version = 'v1'
130 if 'generaldelta' in repo.requirements:
130 if 'generaldelta' in repo.requirements:
131 version = 'v2'
131 version = 'v2'
132 elif spec in _bundlespeccgversions:
132 elif spec in _bundlespeccgversions:
133 if spec == 'packed1':
133 if spec == 'packed1':
134 compression = 'none'
134 compression = 'none'
135 else:
135 else:
136 compression = 'bzip2'
136 compression = 'bzip2'
137 version = spec
137 version = spec
138 else:
138 else:
139 raise error.UnsupportedBundleSpecification(
139 raise error.UnsupportedBundleSpecification(
140 _('%s is not a recognized bundle specification') % spec)
140 _('%s is not a recognized bundle specification') % spec)
141
141
142 # The specification for packed1 can optionally declare the data formats
142 # The specification for packed1 can optionally declare the data formats
143 # required to apply it. If we see this metadata, compare against what the
143 # required to apply it. If we see this metadata, compare against what the
144 # repo supports and error if the bundle isn't compatible.
144 # repo supports and error if the bundle isn't compatible.
145 if version == 'packed1' and 'requirements' in params:
145 if version == 'packed1' and 'requirements' in params:
146 requirements = set(params['requirements'].split(','))
146 requirements = set(params['requirements'].split(','))
147 missingreqs = requirements - repo.supportedformats
147 missingreqs = requirements - repo.supportedformats
148 if missingreqs:
148 if missingreqs:
149 raise error.UnsupportedBundleSpecification(
149 raise error.UnsupportedBundleSpecification(
150 _('missing support for repository features: %s') %
150 _('missing support for repository features: %s') %
151 ', '.join(sorted(missingreqs)))
151 ', '.join(sorted(missingreqs)))
152
152
153 if not externalnames:
153 if not externalnames:
154 engine = util.compengines.forbundlename(compression)
154 engine = util.compengines.forbundlename(compression)
155 compression = engine.bundletype()[1]
155 compression = engine.bundletype()[1]
156 version = _bundlespeccgversions[version]
156 version = _bundlespeccgversions[version]
157 return compression, version, params
157 return compression, version, params
158
158
159 def readbundle(ui, fh, fname, vfs=None):
159 def readbundle(ui, fh, fname, vfs=None):
160 header = changegroup.readexactly(fh, 4)
160 header = changegroup.readexactly(fh, 4)
161
161
162 alg = None
162 alg = None
163 if not fname:
163 if not fname:
164 fname = "stream"
164 fname = "stream"
165 if not header.startswith('HG') and header.startswith('\0'):
165 if not header.startswith('HG') and header.startswith('\0'):
166 fh = changegroup.headerlessfixup(fh, header)
166 fh = changegroup.headerlessfixup(fh, header)
167 header = "HG10"
167 header = "HG10"
168 alg = 'UN'
168 alg = 'UN'
169 elif vfs:
169 elif vfs:
170 fname = vfs.join(fname)
170 fname = vfs.join(fname)
171
171
172 magic, version = header[0:2], header[2:4]
172 magic, version = header[0:2], header[2:4]
173
173
174 if magic != 'HG':
174 if magic != 'HG':
175 raise error.Abort(_('%s: not a Mercurial bundle') % fname)
175 raise error.Abort(_('%s: not a Mercurial bundle') % fname)
176 if version == '10':
176 if version == '10':
177 if alg is None:
177 if alg is None:
178 alg = changegroup.readexactly(fh, 2)
178 alg = changegroup.readexactly(fh, 2)
179 return changegroup.cg1unpacker(fh, alg)
179 return changegroup.cg1unpacker(fh, alg)
180 elif version.startswith('2'):
180 elif version.startswith('2'):
181 return bundle2.getunbundler(ui, fh, magicstring=magic + version)
181 return bundle2.getunbundler(ui, fh, magicstring=magic + version)
182 elif version == 'S1':
182 elif version == 'S1':
183 return streamclone.streamcloneapplier(fh)
183 return streamclone.streamcloneapplier(fh)
184 else:
184 else:
185 raise error.Abort(_('%s: unknown bundle version %s') % (fname, version))
185 raise error.Abort(_('%s: unknown bundle version %s') % (fname, version))
186
186
187 def getbundlespec(ui, fh):
187 def getbundlespec(ui, fh):
188 """Infer the bundlespec from a bundle file handle.
188 """Infer the bundlespec from a bundle file handle.
189
189
190 The input file handle is seeked and the original seek position is not
190 The input file handle is seeked and the original seek position is not
191 restored.
191 restored.
192 """
192 """
193 def speccompression(alg):
193 def speccompression(alg):
194 try:
194 try:
195 return util.compengines.forbundletype(alg).bundletype()[0]
195 return util.compengines.forbundletype(alg).bundletype()[0]
196 except KeyError:
196 except KeyError:
197 return None
197 return None
198
198
199 b = readbundle(ui, fh, None)
199 b = readbundle(ui, fh, None)
200 if isinstance(b, changegroup.cg1unpacker):
200 if isinstance(b, changegroup.cg1unpacker):
201 alg = b._type
201 alg = b._type
202 if alg == '_truncatedBZ':
202 if alg == '_truncatedBZ':
203 alg = 'BZ'
203 alg = 'BZ'
204 comp = speccompression(alg)
204 comp = speccompression(alg)
205 if not comp:
205 if not comp:
206 raise error.Abort(_('unknown compression algorithm: %s') % alg)
206 raise error.Abort(_('unknown compression algorithm: %s') % alg)
207 return '%s-v1' % comp
207 return '%s-v1' % comp
208 elif isinstance(b, bundle2.unbundle20):
208 elif isinstance(b, bundle2.unbundle20):
209 if 'Compression' in b.params:
209 if 'Compression' in b.params:
210 comp = speccompression(b.params['Compression'])
210 comp = speccompression(b.params['Compression'])
211 if not comp:
211 if not comp:
212 raise error.Abort(_('unknown compression algorithm: %s') % comp)
212 raise error.Abort(_('unknown compression algorithm: %s') % comp)
213 else:
213 else:
214 comp = 'none'
214 comp = 'none'
215
215
216 version = None
216 version = None
217 for part in b.iterparts():
217 for part in b.iterparts():
218 if part.type == 'changegroup':
218 if part.type == 'changegroup':
219 version = part.params['version']
219 version = part.params['version']
220 if version in ('01', '02'):
220 if version in ('01', '02'):
221 version = 'v2'
221 version = 'v2'
222 else:
222 else:
223 raise error.Abort(_('changegroup version %s does not have '
223 raise error.Abort(_('changegroup version %s does not have '
224 'a known bundlespec') % version,
224 'a known bundlespec') % version,
225 hint=_('try upgrading your Mercurial '
225 hint=_('try upgrading your Mercurial '
226 'client'))
226 'client'))
227
227
228 if not version:
228 if not version:
229 raise error.Abort(_('could not identify changegroup version in '
229 raise error.Abort(_('could not identify changegroup version in '
230 'bundle'))
230 'bundle'))
231
231
232 return '%s-%s' % (comp, version)
232 return '%s-%s' % (comp, version)
233 elif isinstance(b, streamclone.streamcloneapplier):
233 elif isinstance(b, streamclone.streamcloneapplier):
234 requirements = streamclone.readbundle1header(fh)[2]
234 requirements = streamclone.readbundle1header(fh)[2]
235 params = 'requirements=%s' % ','.join(sorted(requirements))
235 params = 'requirements=%s' % ','.join(sorted(requirements))
236 return 'none-packed1;%s' % urlreq.quote(params)
236 return 'none-packed1;%s' % urlreq.quote(params)
237 else:
237 else:
238 raise error.Abort(_('unknown bundle type: %s') % b)
238 raise error.Abort(_('unknown bundle type: %s') % b)
239
239
240 def buildobsmarkerspart(bundler, markers):
240 def buildobsmarkerspart(bundler, markers):
241 """add an obsmarker part to the bundler with <markers>
241 """add an obsmarker part to the bundler with <markers>
242
242
243 No part is created if markers is empty.
243 No part is created if markers is empty.
244 Raises ValueError if the bundler doesn't support any known obsmarker format.
244 Raises ValueError if the bundler doesn't support any known obsmarker format.
245 """
245 """
246 if markers:
246 if markers:
247 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
247 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
248 version = obsolete.commonversion(remoteversions)
248 version = obsolete.commonversion(remoteversions)
249 if version is None:
249 if version is None:
250 raise ValueError('bundler does not support common obsmarker format')
250 raise ValueError('bundler does not support common obsmarker format')
251 stream = obsolete.encodemarkers(markers, True, version=version)
251 stream = obsolete.encodemarkers(markers, True, version=version)
252 return bundler.newpart('obsmarkers', data=stream)
252 return bundler.newpart('obsmarkers', data=stream)
253 return None
253 return None
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=()):
300 bookmarks=()):
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 # did a local lock get acquired?
314 # did a local lock get acquired?
315 self.locallocked = None
315 self.locallocked = None
316 # step already performed
316 # step already performed
317 # (used to check what steps have been already performed through bundle2)
317 # (used to check what steps have been already performed through bundle2)
318 self.stepsdone = set()
318 self.stepsdone = set()
319 # Integer version of the changegroup push result
319 # Integer version of the changegroup push result
320 # - None means nothing to push
320 # - None means nothing to push
321 # - 0 means HTTP error
321 # - 0 means HTTP error
322 # - 1 means we pushed and remote head count is unchanged *or*
322 # - 1 means we pushed and remote head count is unchanged *or*
323 # we have outgoing changesets but refused to push
323 # we have outgoing changesets but refused to push
324 # - other values as described by addchangegroup()
324 # - other values as described by addchangegroup()
325 self.cgresult = None
325 self.cgresult = None
326 # Boolean value for the bookmark push
326 # Boolean value for the bookmark push
327 self.bkresult = None
327 self.bkresult = None
328 # discover.outgoing object (contains common and outgoing data)
328 # discover.outgoing object (contains common and outgoing data)
329 self.outgoing = None
329 self.outgoing = None
330 # all remote heads before the push
330 # all remote heads before the push
331 self.remoteheads = None
331 self.remoteheads = None
332 # testable as a boolean indicating if any nodes are missing locally.
332 # testable as a boolean indicating if any nodes are missing locally.
333 self.incoming = None
333 self.incoming = None
334 # phases changes that must be pushed along side the changesets
334 # phases changes that must be pushed along side the changesets
335 self.outdatedphases = None
335 self.outdatedphases = None
336 # phases changes that must be pushed if changeset push fails
336 # phases changes that must be pushed if changeset push fails
337 self.fallbackoutdatedphases = None
337 self.fallbackoutdatedphases = None
338 # outgoing obsmarkers
338 # outgoing obsmarkers
339 self.outobsmarkers = set()
339 self.outobsmarkers = set()
340 # outgoing bookmarks
340 # outgoing bookmarks
341 self.outbookmarks = []
341 self.outbookmarks = []
342 # transaction manager
342 # transaction manager
343 self.trmanager = None
343 self.trmanager = None
344 # map { pushkey partid -> callback handling failure}
344 # map { pushkey partid -> callback handling failure}
345 # used to handle exception from mandatory pushkey part failure
345 # used to handle exception from mandatory pushkey part failure
346 self.pkfailcb = {}
346 self.pkfailcb = {}
347
347
348 @util.propertycache
348 @util.propertycache
349 def futureheads(self):
349 def futureheads(self):
350 """future remote heads if the changeset push succeeds"""
350 """future remote heads if the changeset push succeeds"""
351 return self.outgoing.missingheads
351 return self.outgoing.missingheads
352
352
353 @util.propertycache
353 @util.propertycache
354 def fallbackheads(self):
354 def fallbackheads(self):
355 """future remote heads if the changeset push fails"""
355 """future remote heads if the changeset push fails"""
356 if self.revs is None:
356 if self.revs is None:
357 # not target to push, all common are relevant
357 # not target to push, all common are relevant
358 return self.outgoing.commonheads
358 return self.outgoing.commonheads
359 unfi = self.repo.unfiltered()
359 unfi = self.repo.unfiltered()
360 # I want cheads = heads(::missingheads and ::commonheads)
360 # I want cheads = heads(::missingheads and ::commonheads)
361 # (missingheads is revs with secret changeset filtered out)
361 # (missingheads is revs with secret changeset filtered out)
362 #
362 #
363 # This can be expressed as:
363 # This can be expressed as:
364 # cheads = ( (missingheads and ::commonheads)
364 # cheads = ( (missingheads and ::commonheads)
365 # + (commonheads and ::missingheads))"
365 # + (commonheads and ::missingheads))"
366 # )
366 # )
367 #
367 #
368 # while trying to push we already computed the following:
368 # while trying to push we already computed the following:
369 # common = (::commonheads)
369 # common = (::commonheads)
370 # missing = ((commonheads::missingheads) - commonheads)
370 # missing = ((commonheads::missingheads) - commonheads)
371 #
371 #
372 # We can pick:
372 # We can pick:
373 # * missingheads part of common (::commonheads)
373 # * missingheads part of common (::commonheads)
374 common = self.outgoing.common
374 common = self.outgoing.common
375 nm = self.repo.changelog.nodemap
375 nm = self.repo.changelog.nodemap
376 cheads = [node for node in self.revs if nm[node] in common]
376 cheads = [node for node in self.revs if nm[node] in common]
377 # and
377 # and
378 # * commonheads parents on missing
378 # * commonheads parents on missing
379 revset = unfi.set('%ln and parents(roots(%ln))',
379 revset = unfi.set('%ln and parents(roots(%ln))',
380 self.outgoing.commonheads,
380 self.outgoing.commonheads,
381 self.outgoing.missing)
381 self.outgoing.missing)
382 cheads.extend(c.node() for c in revset)
382 cheads.extend(c.node() for c in revset)
383 return cheads
383 return cheads
384
384
385 @property
385 @property
386 def commonheads(self):
386 def commonheads(self):
387 """set of all common heads after changeset bundle push"""
387 """set of all common heads after changeset bundle push"""
388 if self.cgresult:
388 if self.cgresult:
389 return self.futureheads
389 return self.futureheads
390 else:
390 else:
391 return self.fallbackheads
391 return self.fallbackheads
392
392
393 # mapping of message used when pushing bookmark
393 # mapping of message used when pushing bookmark
394 bookmsgmap = {'update': (_("updating bookmark %s\n"),
394 bookmsgmap = {'update': (_("updating bookmark %s\n"),
395 _('updating bookmark %s failed!\n')),
395 _('updating bookmark %s failed!\n')),
396 'export': (_("exporting bookmark %s\n"),
396 'export': (_("exporting bookmark %s\n"),
397 _('exporting bookmark %s failed!\n')),
397 _('exporting bookmark %s failed!\n')),
398 'delete': (_("deleting remote bookmark %s\n"),
398 'delete': (_("deleting remote bookmark %s\n"),
399 _('deleting remote bookmark %s failed!\n')),
399 _('deleting remote bookmark %s failed!\n')),
400 }
400 }
401
401
402
402
403 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=(),
403 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=(),
404 opargs=None):
404 opargs=None):
405 '''Push outgoing changesets (limited by revs) from a local
405 '''Push outgoing changesets (limited by revs) from a local
406 repository to remote. Return an integer:
406 repository to remote. Return an integer:
407 - None means nothing to push
407 - None means nothing to push
408 - 0 means HTTP error
408 - 0 means HTTP error
409 - 1 means we pushed and remote head count is unchanged *or*
409 - 1 means we pushed and remote head count is unchanged *or*
410 we have outgoing changesets but refused to push
410 we have outgoing changesets but refused to push
411 - other values as described by addchangegroup()
411 - other values as described by addchangegroup()
412 '''
412 '''
413 if opargs is None:
413 if opargs is None:
414 opargs = {}
414 opargs = {}
415 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
415 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
416 **opargs)
416 **opargs)
417 if pushop.remote.local():
417 if pushop.remote.local():
418 missing = (set(pushop.repo.requirements)
418 missing = (set(pushop.repo.requirements)
419 - pushop.remote.local().supported)
419 - pushop.remote.local().supported)
420 if missing:
420 if missing:
421 msg = _("required features are not"
421 msg = _("required features are not"
422 " supported in the destination:"
422 " supported in the destination:"
423 " %s") % (', '.join(sorted(missing)))
423 " %s") % (', '.join(sorted(missing)))
424 raise error.Abort(msg)
424 raise error.Abort(msg)
425
425
426 # there are two ways to push to remote repo:
426 # there are two ways to push to remote repo:
427 #
427 #
428 # addchangegroup assumes local user can lock remote
428 # addchangegroup assumes local user can lock remote
429 # repo (local filesystem, old ssh servers).
429 # repo (local filesystem, old ssh servers).
430 #
430 #
431 # unbundle assumes local user cannot lock remote repo (new ssh
431 # unbundle assumes local user cannot lock remote repo (new ssh
432 # servers, http servers).
432 # servers, http servers).
433
433
434 if not pushop.remote.canpush():
434 if not pushop.remote.canpush():
435 raise error.Abort(_("destination does not support push"))
435 raise error.Abort(_("destination does not support push"))
436 # get local lock as we might write phase data
436 # get local lock as we might write phase data
437 localwlock = locallock = None
437 localwlock = locallock = None
438 try:
438 try:
439 # bundle2 push may receive a reply bundle touching bookmarks or other
439 # bundle2 push may receive a reply bundle touching bookmarks or other
440 # things requiring the wlock. Take it now to ensure proper ordering.
440 # things requiring the wlock. Take it now to ensure proper ordering.
441 maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
441 maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
442 if (not _forcebundle1(pushop)) and maypushback:
442 if (not _forcebundle1(pushop)) and maypushback:
443 localwlock = pushop.repo.wlock()
443 localwlock = pushop.repo.wlock()
444 locallock = pushop.repo.lock()
444 locallock = pushop.repo.lock()
445 pushop.locallocked = True
445 pushop.locallocked = True
446 except IOError as err:
446 except IOError as err:
447 pushop.locallocked = False
447 pushop.locallocked = False
448 if err.errno != errno.EACCES:
448 if err.errno != errno.EACCES:
449 raise
449 raise
450 # source repo cannot be locked.
450 # source repo cannot be locked.
451 # We do not abort the push, but just disable the local phase
451 # We do not abort the push, but just disable the local phase
452 # synchronisation.
452 # synchronisation.
453 msg = 'cannot lock source repository: %s\n' % err
453 msg = 'cannot lock source repository: %s\n' % err
454 pushop.ui.debug(msg)
454 pushop.ui.debug(msg)
455 try:
455 try:
456 if pushop.locallocked:
456 if pushop.locallocked:
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 pushop.repo.checkpush(pushop)
460 pushop.repo.checkpush(pushop)
461 lock = None
461 lock = None
462 unbundle = pushop.remote.capable('unbundle')
462 unbundle = pushop.remote.capable('unbundle')
463 if not unbundle:
463 if not unbundle:
464 lock = pushop.remote.lock()
464 lock = pushop.remote.lock()
465 try:
465 try:
466 _pushdiscovery(pushop)
466 _pushdiscovery(pushop)
467 if not _forcebundle1(pushop):
467 if not _forcebundle1(pushop):
468 _pushbundle2(pushop)
468 _pushbundle2(pushop)
469 _pushchangeset(pushop)
469 _pushchangeset(pushop)
470 _pushsyncphase(pushop)
470 _pushsyncphase(pushop)
471 _pushobsolete(pushop)
471 _pushobsolete(pushop)
472 _pushbookmark(pushop)
472 _pushbookmark(pushop)
473 finally:
473 finally:
474 if lock is not None:
474 if lock is not None:
475 lock.release()
475 lock.release()
476 if pushop.trmanager:
476 if pushop.trmanager:
477 pushop.trmanager.close()
477 pushop.trmanager.close()
478 finally:
478 finally:
479 if pushop.trmanager:
479 if pushop.trmanager:
480 pushop.trmanager.release()
480 pushop.trmanager.release()
481 if locallock is not None:
481 if locallock is not None:
482 locallock.release()
482 locallock.release()
483 if localwlock is not None:
483 if localwlock is not None:
484 localwlock.release()
484 localwlock.release()
485
485
486 return pushop
486 return pushop
487
487
488 # list of steps to perform discovery before push
488 # list of steps to perform discovery before push
489 pushdiscoveryorder = []
489 pushdiscoveryorder = []
490
490
491 # Mapping between step name and function
491 # Mapping between step name and function
492 #
492 #
493 # This exists to help extensions wrap steps if necessary
493 # This exists to help extensions wrap steps if necessary
494 pushdiscoverymapping = {}
494 pushdiscoverymapping = {}
495
495
496 def pushdiscovery(stepname):
496 def pushdiscovery(stepname):
497 """decorator for function performing discovery before push
497 """decorator for function performing discovery before push
498
498
499 The function is added to the step -> function mapping and appended to the
499 The function is added to the step -> function mapping and appended to the
500 list of steps. Beware that decorated function will be added in order (this
500 list of steps. Beware that decorated function will be added in order (this
501 may matter).
501 may matter).
502
502
503 You can only use this decorator for a new step, if you want to wrap a step
503 You can only use this decorator for a new step, if you want to wrap a step
504 from an extension, change the pushdiscovery dictionary directly."""
504 from an extension, change the pushdiscovery dictionary directly."""
505 def dec(func):
505 def dec(func):
506 assert stepname not in pushdiscoverymapping
506 assert stepname not in pushdiscoverymapping
507 pushdiscoverymapping[stepname] = func
507 pushdiscoverymapping[stepname] = func
508 pushdiscoveryorder.append(stepname)
508 pushdiscoveryorder.append(stepname)
509 return func
509 return func
510 return dec
510 return dec
511
511
512 def _pushdiscovery(pushop):
512 def _pushdiscovery(pushop):
513 """Run all discovery steps"""
513 """Run all discovery steps"""
514 for stepname in pushdiscoveryorder:
514 for stepname in pushdiscoveryorder:
515 step = pushdiscoverymapping[stepname]
515 step = pushdiscoverymapping[stepname]
516 step(pushop)
516 step(pushop)
517
517
518 @pushdiscovery('changeset')
518 @pushdiscovery('changeset')
519 def _pushdiscoverychangeset(pushop):
519 def _pushdiscoverychangeset(pushop):
520 """discover the changeset that need to be pushed"""
520 """discover the changeset that need to be pushed"""
521 fci = discovery.findcommonincoming
521 fci = discovery.findcommonincoming
522 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force)
522 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force)
523 common, inc, remoteheads = commoninc
523 common, inc, remoteheads = commoninc
524 fco = discovery.findcommonoutgoing
524 fco = discovery.findcommonoutgoing
525 outgoing = fco(pushop.repo, pushop.remote, onlyheads=pushop.revs,
525 outgoing = fco(pushop.repo, pushop.remote, onlyheads=pushop.revs,
526 commoninc=commoninc, force=pushop.force)
526 commoninc=commoninc, force=pushop.force)
527 pushop.outgoing = outgoing
527 pushop.outgoing = outgoing
528 pushop.remoteheads = remoteheads
528 pushop.remoteheads = remoteheads
529 pushop.incoming = inc
529 pushop.incoming = inc
530
530
531 @pushdiscovery('phase')
531 @pushdiscovery('phase')
532 def _pushdiscoveryphase(pushop):
532 def _pushdiscoveryphase(pushop):
533 """discover the phase that needs to be pushed
533 """discover the phase that needs to be pushed
534
534
535 (computed for both success and failure case for changesets push)"""
535 (computed for both success and failure case for changesets push)"""
536 outgoing = pushop.outgoing
536 outgoing = pushop.outgoing
537 unfi = pushop.repo.unfiltered()
537 unfi = pushop.repo.unfiltered()
538 remotephases = pushop.remote.listkeys('phases')
538 remotephases = pushop.remote.listkeys('phases')
539 publishing = remotephases.get('publishing', False)
539 publishing = remotephases.get('publishing', False)
540 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
540 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
541 and remotephases # server supports phases
541 and remotephases # server supports phases
542 and not pushop.outgoing.missing # no changesets to be pushed
542 and not pushop.outgoing.missing # no changesets to be pushed
543 and publishing):
543 and publishing):
544 # When:
544 # When:
545 # - this is a subrepo push
545 # - this is a subrepo push
546 # - and remote support phase
546 # - and remote support phase
547 # - and no changeset are to be pushed
547 # - and no changeset are to be pushed
548 # - and remote is publishing
548 # - and remote is publishing
549 # We may be in issue 3871 case!
549 # We may be in issue 3871 case!
550 # We drop the possible phase synchronisation done by
550 # We drop the possible phase synchronisation done by
551 # courtesy to publish changesets possibly locally draft
551 # courtesy to publish changesets possibly locally draft
552 # on the remote.
552 # on the remote.
553 remotephases = {'publishing': 'True'}
553 remotephases = {'publishing': 'True'}
554 ana = phases.analyzeremotephases(pushop.repo,
554 ana = phases.analyzeremotephases(pushop.repo,
555 pushop.fallbackheads,
555 pushop.fallbackheads,
556 remotephases)
556 remotephases)
557 pheads, droots = ana
557 pheads, droots = ana
558 extracond = ''
558 extracond = ''
559 if not publishing:
559 if not publishing:
560 extracond = ' and public()'
560 extracond = ' and public()'
561 revset = 'heads((%%ln::%%ln) %s)' % extracond
561 revset = 'heads((%%ln::%%ln) %s)' % extracond
562 # Get the list of all revs draft on remote by public here.
562 # Get the list of all revs draft on remote by public here.
563 # XXX Beware that revset break if droots is not strictly
563 # XXX Beware that revset break if droots is not strictly
564 # XXX root we may want to ensure it is but it is costly
564 # XXX root we may want to ensure it is but it is costly
565 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
565 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
566 if not outgoing.missing:
566 if not outgoing.missing:
567 future = fallback
567 future = fallback
568 else:
568 else:
569 # adds changeset we are going to push as draft
569 # adds changeset we are going to push as draft
570 #
570 #
571 # should not be necessary for publishing server, but because of an
571 # should not be necessary for publishing server, but because of an
572 # issue fixed in xxxxx we have to do it anyway.
572 # issue fixed in xxxxx we have to do it anyway.
573 fdroots = list(unfi.set('roots(%ln + %ln::)',
573 fdroots = list(unfi.set('roots(%ln + %ln::)',
574 outgoing.missing, droots))
574 outgoing.missing, droots))
575 fdroots = [f.node() for f in fdroots]
575 fdroots = [f.node() for f in fdroots]
576 future = list(unfi.set(revset, fdroots, pushop.futureheads))
576 future = list(unfi.set(revset, fdroots, pushop.futureheads))
577 pushop.outdatedphases = future
577 pushop.outdatedphases = future
578 pushop.fallbackoutdatedphases = fallback
578 pushop.fallbackoutdatedphases = fallback
579
579
580 @pushdiscovery('obsmarker')
580 @pushdiscovery('obsmarker')
581 def _pushdiscoveryobsmarkers(pushop):
581 def _pushdiscoveryobsmarkers(pushop):
582 if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
582 if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
583 and pushop.repo.obsstore
583 and pushop.repo.obsstore
584 and 'obsolete' in pushop.remote.listkeys('namespaces')):
584 and 'obsolete' in pushop.remote.listkeys('namespaces')):
585 repo = pushop.repo
585 repo = pushop.repo
586 # very naive computation, that can be quite expensive on big repo.
586 # very naive computation, that can be quite expensive on big repo.
587 # However: evolution is currently slow on them anyway.
587 # However: evolution is currently slow on them anyway.
588 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
588 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
589 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
589 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
590
590
591 @pushdiscovery('bookmarks')
591 @pushdiscovery('bookmarks')
592 def _pushdiscoverybookmarks(pushop):
592 def _pushdiscoverybookmarks(pushop):
593 ui = pushop.ui
593 ui = pushop.ui
594 repo = pushop.repo.unfiltered()
594 repo = pushop.repo.unfiltered()
595 remote = pushop.remote
595 remote = pushop.remote
596 ui.debug("checking for updated bookmarks\n")
596 ui.debug("checking for updated bookmarks\n")
597 ancestors = ()
597 ancestors = ()
598 if pushop.revs:
598 if pushop.revs:
599 revnums = map(repo.changelog.rev, pushop.revs)
599 revnums = map(repo.changelog.rev, pushop.revs)
600 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
600 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
601 remotebookmark = remote.listkeys('bookmarks')
601 remotebookmark = remote.listkeys('bookmarks')
602
602
603 explicit = set([repo._bookmarks.expandname(bookmark)
603 explicit = set([repo._bookmarks.expandname(bookmark)
604 for bookmark in pushop.bookmarks])
604 for bookmark in pushop.bookmarks])
605
605
606 remotebookmark = bookmod.unhexlifybookmarks(remotebookmark)
606 remotebookmark = bookmod.unhexlifybookmarks(remotebookmark)
607 comp = bookmod.comparebookmarks(repo, repo._bookmarks, remotebookmark)
607 comp = bookmod.comparebookmarks(repo, repo._bookmarks, remotebookmark)
608
608
609 def safehex(x):
609 def safehex(x):
610 if x is None:
610 if x is None:
611 return x
611 return x
612 return hex(x)
612 return hex(x)
613
613
614 def hexifycompbookmarks(bookmarks):
614 def hexifycompbookmarks(bookmarks):
615 for b, scid, dcid in bookmarks:
615 for b, scid, dcid in bookmarks:
616 yield b, safehex(scid), safehex(dcid)
616 yield b, safehex(scid), safehex(dcid)
617
617
618 comp = [hexifycompbookmarks(marks) for marks in comp]
618 comp = [hexifycompbookmarks(marks) for marks in comp]
619 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
619 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
620
620
621 for b, scid, dcid in advsrc:
621 for b, scid, dcid in advsrc:
622 if b in explicit:
622 if b in explicit:
623 explicit.remove(b)
623 explicit.remove(b)
624 if not ancestors or repo[scid].rev() in ancestors:
624 if not ancestors or repo[scid].rev() in ancestors:
625 pushop.outbookmarks.append((b, dcid, scid))
625 pushop.outbookmarks.append((b, dcid, scid))
626 # search added bookmark
626 # search added bookmark
627 for b, scid, dcid in addsrc:
627 for b, scid, dcid in addsrc:
628 if b in explicit:
628 if b in explicit:
629 explicit.remove(b)
629 explicit.remove(b)
630 pushop.outbookmarks.append((b, '', scid))
630 pushop.outbookmarks.append((b, '', scid))
631 # search for overwritten bookmark
631 # search for overwritten bookmark
632 for b, scid, dcid in list(advdst) + list(diverge) + list(differ):
632 for b, scid, dcid in list(advdst) + list(diverge) + list(differ):
633 if b in explicit:
633 if b in explicit:
634 explicit.remove(b)
634 explicit.remove(b)
635 pushop.outbookmarks.append((b, dcid, scid))
635 pushop.outbookmarks.append((b, dcid, scid))
636 # search for bookmark to delete
636 # search for bookmark to delete
637 for b, scid, dcid in adddst:
637 for b, scid, dcid in adddst:
638 if b in explicit:
638 if b in explicit:
639 explicit.remove(b)
639 explicit.remove(b)
640 # treat as "deleted locally"
640 # treat as "deleted locally"
641 pushop.outbookmarks.append((b, dcid, ''))
641 pushop.outbookmarks.append((b, dcid, ''))
642 # identical bookmarks shouldn't get reported
642 # identical bookmarks shouldn't get reported
643 for b, scid, dcid in same:
643 for b, scid, dcid in same:
644 if b in explicit:
644 if b in explicit:
645 explicit.remove(b)
645 explicit.remove(b)
646
646
647 if explicit:
647 if explicit:
648 explicit = sorted(explicit)
648 explicit = sorted(explicit)
649 # we should probably list all of them
649 # we should probably list all of them
650 ui.warn(_('bookmark %s does not exist on the local '
650 ui.warn(_('bookmark %s does not exist on the local '
651 'or remote repository!\n') % explicit[0])
651 'or remote repository!\n') % explicit[0])
652 pushop.bkresult = 2
652 pushop.bkresult = 2
653
653
654 pushop.outbookmarks.sort()
654 pushop.outbookmarks.sort()
655
655
656 def _pushcheckoutgoing(pushop):
656 def _pushcheckoutgoing(pushop):
657 outgoing = pushop.outgoing
657 outgoing = pushop.outgoing
658 unfi = pushop.repo.unfiltered()
658 unfi = pushop.repo.unfiltered()
659 if not outgoing.missing:
659 if not outgoing.missing:
660 # nothing to push
660 # nothing to push
661 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
661 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
662 return False
662 return False
663 # something to push
663 # something to push
664 if not pushop.force:
664 if not pushop.force:
665 # if repo.obsstore == False --> no obsolete
665 # if repo.obsstore == False --> no obsolete
666 # then, save the iteration
666 # then, save the iteration
667 if unfi.obsstore:
667 if unfi.obsstore:
668 # this message are here for 80 char limit reason
668 # this message are here for 80 char limit reason
669 mso = _("push includes obsolete changeset: %s!")
669 mso = _("push includes obsolete changeset: %s!")
670 mst = {"unstable": _("push includes unstable changeset: %s!"),
670 mst = {"unstable": _("push includes unstable changeset: %s!"),
671 "bumped": _("push includes bumped changeset: %s!"),
671 "bumped": _("push includes bumped changeset: %s!"),
672 "divergent": _("push includes divergent changeset: %s!")}
672 "divergent": _("push includes divergent changeset: %s!")}
673 # If we are to push if there is at least one
673 # If we are to push if there is at least one
674 # obsolete or unstable changeset in missing, at
674 # obsolete or unstable changeset in missing, at
675 # least one of the missinghead will be obsolete or
675 # least one of the missinghead will be obsolete or
676 # unstable. So checking heads only is ok
676 # unstable. So checking heads only is ok
677 for node in outgoing.missingheads:
677 for node in outgoing.missingheads:
678 ctx = unfi[node]
678 ctx = unfi[node]
679 if ctx.obsolete():
679 if ctx.obsolete():
680 raise error.Abort(mso % ctx)
680 raise error.Abort(mso % ctx)
681 elif ctx.troubled():
681 elif ctx.troubled():
682 raise error.Abort(mst[ctx.troubles()[0]] % ctx)
682 raise error.Abort(mst[ctx.troubles()[0]] % ctx)
683
683
684 discovery.checkheads(pushop)
684 discovery.checkheads(pushop)
685 return True
685 return True
686
686
687 # List of names of steps to perform for an outgoing bundle2, order matters.
687 # List of names of steps to perform for an outgoing bundle2, order matters.
688 b2partsgenorder = []
688 b2partsgenorder = []
689
689
690 # Mapping between step name and function
690 # Mapping between step name and function
691 #
691 #
692 # This exists to help extensions wrap steps if necessary
692 # This exists to help extensions wrap steps if necessary
693 b2partsgenmapping = {}
693 b2partsgenmapping = {}
694
694
695 def b2partsgenerator(stepname, idx=None):
695 def b2partsgenerator(stepname, idx=None):
696 """decorator for function generating bundle2 part
696 """decorator for function generating bundle2 part
697
697
698 The function is added to the step -> function mapping and appended to the
698 The function is added to the step -> function mapping and appended to the
699 list of steps. Beware that decorated functions will be added in order
699 list of steps. Beware that decorated functions will be added in order
700 (this may matter).
700 (this may matter).
701
701
702 You can only use this decorator for new steps, if you want to wrap a step
702 You can only use this decorator for new steps, if you want to wrap a step
703 from an extension, attack the b2partsgenmapping dictionary directly."""
703 from an extension, attack the b2partsgenmapping dictionary directly."""
704 def dec(func):
704 def dec(func):
705 assert stepname not in b2partsgenmapping
705 assert stepname not in b2partsgenmapping
706 b2partsgenmapping[stepname] = func
706 b2partsgenmapping[stepname] = func
707 if idx is None:
707 if idx is None:
708 b2partsgenorder.append(stepname)
708 b2partsgenorder.append(stepname)
709 else:
709 else:
710 b2partsgenorder.insert(idx, stepname)
710 b2partsgenorder.insert(idx, stepname)
711 return func
711 return func
712 return dec
712 return dec
713
713
714 def _pushb2ctxcheckheads(pushop, bundler):
714 def _pushb2ctxcheckheads(pushop, bundler):
715 """Generate race condition checking parts
715 """Generate race condition checking parts
716
716
717 Exists as an independent function to aid extensions
717 Exists as an independent function to aid extensions
718 """
718 """
719 if not pushop.force:
719 if not pushop.force:
720 bundler.newpart('check:heads', data=iter(pushop.remoteheads))
720 bundler.newpart('check:heads', data=iter(pushop.remoteheads))
721
721
722 @b2partsgenerator('changeset')
722 @b2partsgenerator('changeset')
723 def _pushb2ctx(pushop, bundler):
723 def _pushb2ctx(pushop, bundler):
724 """handle changegroup push through bundle2
724 """handle changegroup push through bundle2
725
725
726 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
726 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
727 """
727 """
728 if 'changesets' in pushop.stepsdone:
728 if 'changesets' in pushop.stepsdone:
729 return
729 return
730 pushop.stepsdone.add('changesets')
730 pushop.stepsdone.add('changesets')
731 # Send known heads to the server for race detection.
731 # Send known heads to the server for race detection.
732 if not _pushcheckoutgoing(pushop):
732 if not _pushcheckoutgoing(pushop):
733 return
733 return
734 pushop.repo.prepushoutgoinghooks(pushop)
734 pushop.repo.prepushoutgoinghooks(pushop)
735
735
736 _pushb2ctxcheckheads(pushop, bundler)
736 _pushb2ctxcheckheads(pushop, bundler)
737
737
738 b2caps = bundle2.bundle2caps(pushop.remote)
738 b2caps = bundle2.bundle2caps(pushop.remote)
739 version = '01'
739 version = '01'
740 cgversions = b2caps.get('changegroup')
740 cgversions = b2caps.get('changegroup')
741 if cgversions: # 3.1 and 3.2 ship with an empty value
741 if cgversions: # 3.1 and 3.2 ship with an empty value
742 cgversions = [v for v in cgversions
742 cgversions = [v for v in cgversions
743 if v in changegroup.supportedoutgoingversions(
743 if v in changegroup.supportedoutgoingversions(
744 pushop.repo)]
744 pushop.repo)]
745 if not cgversions:
745 if not cgversions:
746 raise ValueError(_('no common changegroup version'))
746 raise ValueError(_('no common changegroup version'))
747 version = max(cgversions)
747 version = max(cgversions)
748 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
748 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
749 pushop.outgoing,
749 pushop.outgoing,
750 version=version)
750 version=version)
751 cgpart = bundler.newpart('changegroup', data=cg)
751 cgpart = bundler.newpart('changegroup', data=cg)
752 if cgversions:
752 if cgversions:
753 cgpart.addparam('version', version)
753 cgpart.addparam('version', version)
754 if 'treemanifest' in pushop.repo.requirements:
754 if 'treemanifest' in pushop.repo.requirements:
755 cgpart.addparam('treemanifest', '1')
755 cgpart.addparam('treemanifest', '1')
756 def handlereply(op):
756 def handlereply(op):
757 """extract addchangegroup returns from server reply"""
757 """extract addchangegroup returns from server reply"""
758 cgreplies = op.records.getreplies(cgpart.id)
758 cgreplies = op.records.getreplies(cgpart.id)
759 assert len(cgreplies['changegroup']) == 1
759 assert len(cgreplies['changegroup']) == 1
760 pushop.cgresult = cgreplies['changegroup'][0]['return']
760 pushop.cgresult = cgreplies['changegroup'][0]['return']
761 return handlereply
761 return handlereply
762
762
763 @b2partsgenerator('phase')
763 @b2partsgenerator('phase')
764 def _pushb2phases(pushop, bundler):
764 def _pushb2phases(pushop, bundler):
765 """handle phase push through bundle2"""
765 """handle phase push through bundle2"""
766 if 'phases' in pushop.stepsdone:
766 if 'phases' in pushop.stepsdone:
767 return
767 return
768 b2caps = bundle2.bundle2caps(pushop.remote)
768 b2caps = bundle2.bundle2caps(pushop.remote)
769 if not 'pushkey' in b2caps:
769 if not 'pushkey' in b2caps:
770 return
770 return
771 pushop.stepsdone.add('phases')
771 pushop.stepsdone.add('phases')
772 part2node = []
772 part2node = []
773
773
774 def handlefailure(pushop, exc):
774 def handlefailure(pushop, exc):
775 targetid = int(exc.partid)
775 targetid = int(exc.partid)
776 for partid, node in part2node:
776 for partid, node in part2node:
777 if partid == targetid:
777 if partid == targetid:
778 raise error.Abort(_('updating %s to public failed') % node)
778 raise error.Abort(_('updating %s to public failed') % node)
779
779
780 enc = pushkey.encode
780 enc = pushkey.encode
781 for newremotehead in pushop.outdatedphases:
781 for newremotehead in pushop.outdatedphases:
782 part = bundler.newpart('pushkey')
782 part = bundler.newpart('pushkey')
783 part.addparam('namespace', enc('phases'))
783 part.addparam('namespace', enc('phases'))
784 part.addparam('key', enc(newremotehead.hex()))
784 part.addparam('key', enc(newremotehead.hex()))
785 part.addparam('old', enc(str(phases.draft)))
785 part.addparam('old', enc(str(phases.draft)))
786 part.addparam('new', enc(str(phases.public)))
786 part.addparam('new', enc(str(phases.public)))
787 part2node.append((part.id, newremotehead))
787 part2node.append((part.id, newremotehead))
788 pushop.pkfailcb[part.id] = handlefailure
788 pushop.pkfailcb[part.id] = handlefailure
789
789
790 def handlereply(op):
790 def handlereply(op):
791 for partid, node in part2node:
791 for partid, node in part2node:
792 partrep = op.records.getreplies(partid)
792 partrep = op.records.getreplies(partid)
793 results = partrep['pushkey']
793 results = partrep['pushkey']
794 assert len(results) <= 1
794 assert len(results) <= 1
795 msg = None
795 msg = None
796 if not results:
796 if not results:
797 msg = _('server ignored update of %s to public!\n') % node
797 msg = _('server ignored update of %s to public!\n') % node
798 elif not int(results[0]['return']):
798 elif not int(results[0]['return']):
799 msg = _('updating %s to public failed!\n') % node
799 msg = _('updating %s to public failed!\n') % node
800 if msg is not None:
800 if msg is not None:
801 pushop.ui.warn(msg)
801 pushop.ui.warn(msg)
802 return handlereply
802 return handlereply
803
803
804 @b2partsgenerator('obsmarkers')
804 @b2partsgenerator('obsmarkers')
805 def _pushb2obsmarkers(pushop, bundler):
805 def _pushb2obsmarkers(pushop, bundler):
806 if 'obsmarkers' in pushop.stepsdone:
806 if 'obsmarkers' in pushop.stepsdone:
807 return
807 return
808 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
808 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
809 if obsolete.commonversion(remoteversions) is None:
809 if obsolete.commonversion(remoteversions) is None:
810 return
810 return
811 pushop.stepsdone.add('obsmarkers')
811 pushop.stepsdone.add('obsmarkers')
812 if pushop.outobsmarkers:
812 if pushop.outobsmarkers:
813 markers = sorted(pushop.outobsmarkers)
813 markers = sorted(pushop.outobsmarkers)
814 buildobsmarkerspart(bundler, markers)
814 buildobsmarkerspart(bundler, markers)
815
815
816 @b2partsgenerator('bookmarks')
816 @b2partsgenerator('bookmarks')
817 def _pushb2bookmarks(pushop, bundler):
817 def _pushb2bookmarks(pushop, bundler):
818 """handle bookmark push through bundle2"""
818 """handle bookmark push through bundle2"""
819 if 'bookmarks' in pushop.stepsdone:
819 if 'bookmarks' in pushop.stepsdone:
820 return
820 return
821 b2caps = bundle2.bundle2caps(pushop.remote)
821 b2caps = bundle2.bundle2caps(pushop.remote)
822 if 'pushkey' not in b2caps:
822 if 'pushkey' not in b2caps:
823 return
823 return
824 pushop.stepsdone.add('bookmarks')
824 pushop.stepsdone.add('bookmarks')
825 part2book = []
825 part2book = []
826 enc = pushkey.encode
826 enc = pushkey.encode
827
827
828 def handlefailure(pushop, exc):
828 def handlefailure(pushop, exc):
829 targetid = int(exc.partid)
829 targetid = int(exc.partid)
830 for partid, book, action in part2book:
830 for partid, book, action in part2book:
831 if partid == targetid:
831 if partid == targetid:
832 raise error.Abort(bookmsgmap[action][1].rstrip() % book)
832 raise error.Abort(bookmsgmap[action][1].rstrip() % book)
833 # we should not be called for part we did not generated
833 # we should not be called for part we did not generated
834 assert False
834 assert False
835
835
836 for book, old, new in pushop.outbookmarks:
836 for book, old, new in pushop.outbookmarks:
837 part = bundler.newpart('pushkey')
837 part = bundler.newpart('pushkey')
838 part.addparam('namespace', enc('bookmarks'))
838 part.addparam('namespace', enc('bookmarks'))
839 part.addparam('key', enc(book))
839 part.addparam('key', enc(book))
840 part.addparam('old', enc(old))
840 part.addparam('old', enc(old))
841 part.addparam('new', enc(new))
841 part.addparam('new', enc(new))
842 action = 'update'
842 action = 'update'
843 if not old:
843 if not old:
844 action = 'export'
844 action = 'export'
845 elif not new:
845 elif not new:
846 action = 'delete'
846 action = 'delete'
847 part2book.append((part.id, book, action))
847 part2book.append((part.id, book, action))
848 pushop.pkfailcb[part.id] = handlefailure
848 pushop.pkfailcb[part.id] = handlefailure
849
849
850 def handlereply(op):
850 def handlereply(op):
851 ui = pushop.ui
851 ui = pushop.ui
852 for partid, book, action in part2book:
852 for partid, book, action in part2book:
853 partrep = op.records.getreplies(partid)
853 partrep = op.records.getreplies(partid)
854 results = partrep['pushkey']
854 results = partrep['pushkey']
855 assert len(results) <= 1
855 assert len(results) <= 1
856 if not results:
856 if not results:
857 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
857 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
858 else:
858 else:
859 ret = int(results[0]['return'])
859 ret = int(results[0]['return'])
860 if ret:
860 if ret:
861 ui.status(bookmsgmap[action][0] % book)
861 ui.status(bookmsgmap[action][0] % book)
862 else:
862 else:
863 ui.warn(bookmsgmap[action][1] % book)
863 ui.warn(bookmsgmap[action][1] % book)
864 if pushop.bkresult is not None:
864 if pushop.bkresult is not None:
865 pushop.bkresult = 1
865 pushop.bkresult = 1
866 return handlereply
866 return handlereply
867
867
868
868
869 def _pushbundle2(pushop):
869 def _pushbundle2(pushop):
870 """push data to the remote using bundle2
870 """push data to the remote using bundle2
871
871
872 The only currently supported type of data is changegroup but this will
872 The only currently supported type of data is changegroup but this will
873 evolve in the future."""
873 evolve in the future."""
874 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
874 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
875 pushback = (pushop.trmanager
875 pushback = (pushop.trmanager
876 and pushop.ui.configbool('experimental', 'bundle2.pushback'))
876 and pushop.ui.configbool('experimental', 'bundle2.pushback'))
877
877
878 # create reply capability
878 # create reply capability
879 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo,
879 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo,
880 allowpushback=pushback))
880 allowpushback=pushback))
881 bundler.newpart('replycaps', data=capsblob)
881 bundler.newpart('replycaps', data=capsblob)
882 replyhandlers = []
882 replyhandlers = []
883 for partgenname in b2partsgenorder:
883 for partgenname in b2partsgenorder:
884 partgen = b2partsgenmapping[partgenname]
884 partgen = b2partsgenmapping[partgenname]
885 ret = partgen(pushop, bundler)
885 ret = partgen(pushop, bundler)
886 if callable(ret):
886 if callable(ret):
887 replyhandlers.append(ret)
887 replyhandlers.append(ret)
888 # do not push if nothing to push
888 # do not push if nothing to push
889 if bundler.nbparts <= 1:
889 if bundler.nbparts <= 1:
890 return
890 return
891 stream = util.chunkbuffer(bundler.getchunks())
891 stream = util.chunkbuffer(bundler.getchunks())
892 try:
892 try:
893 try:
893 try:
894 reply = pushop.remote.unbundle(
894 reply = pushop.remote.unbundle(
895 stream, ['force'], pushop.remote.url())
895 stream, ['force'], pushop.remote.url())
896 except error.BundleValueError as exc:
896 except error.BundleValueError as exc:
897 raise error.Abort(_('missing support for %s') % exc)
897 raise error.Abort(_('missing support for %s') % exc)
898 try:
898 try:
899 trgetter = None
899 trgetter = None
900 if pushback:
900 if pushback:
901 trgetter = pushop.trmanager.transaction
901 trgetter = pushop.trmanager.transaction
902 op = bundle2.processbundle(pushop.repo, reply, trgetter)
902 op = bundle2.processbundle(pushop.repo, reply, trgetter)
903 except error.BundleValueError as exc:
903 except error.BundleValueError as exc:
904 raise error.Abort(_('missing support for %s') % exc)
904 raise error.Abort(_('missing support for %s') % exc)
905 except bundle2.AbortFromPart as exc:
905 except bundle2.AbortFromPart as exc:
906 pushop.ui.status(_('remote: %s\n') % exc)
906 pushop.ui.status(_('remote: %s\n') % exc)
907 if exc.hint is not None:
907 if exc.hint is not None:
908 pushop.ui.status(_('remote: %s\n') % ('(%s)' % exc.hint))
908 pushop.ui.status(_('remote: %s\n') % ('(%s)' % exc.hint))
909 raise error.Abort(_('push failed on remote'))
909 raise error.Abort(_('push failed on remote'))
910 except error.PushkeyFailed as exc:
910 except error.PushkeyFailed as exc:
911 partid = int(exc.partid)
911 partid = int(exc.partid)
912 if partid not in pushop.pkfailcb:
912 if partid not in pushop.pkfailcb:
913 raise
913 raise
914 pushop.pkfailcb[partid](pushop, exc)
914 pushop.pkfailcb[partid](pushop, exc)
915 for rephand in replyhandlers:
915 for rephand in replyhandlers:
916 rephand(op)
916 rephand(op)
917
917
918 def _pushchangeset(pushop):
918 def _pushchangeset(pushop):
919 """Make the actual push of changeset bundle to remote repo"""
919 """Make the actual push of changeset bundle to remote repo"""
920 if 'changesets' in pushop.stepsdone:
920 if 'changesets' in pushop.stepsdone:
921 return
921 return
922 pushop.stepsdone.add('changesets')
922 pushop.stepsdone.add('changesets')
923 if not _pushcheckoutgoing(pushop):
923 if not _pushcheckoutgoing(pushop):
924 return
924 return
925 pushop.repo.prepushoutgoinghooks(pushop)
925 pushop.repo.prepushoutgoinghooks(pushop)
926 outgoing = pushop.outgoing
926 outgoing = pushop.outgoing
927 unbundle = pushop.remote.capable('unbundle')
927 unbundle = pushop.remote.capable('unbundle')
928 # TODO: get bundlecaps from remote
928 # TODO: get bundlecaps from remote
929 bundlecaps = None
929 bundlecaps = None
930 # create a changegroup from local
930 # create a changegroup from local
931 if pushop.revs is None and not (outgoing.excluded
931 if pushop.revs is None and not (outgoing.excluded
932 or pushop.repo.changelog.filteredrevs):
932 or pushop.repo.changelog.filteredrevs):
933 # push everything,
933 # push everything,
934 # use the fast path, no race possible on push
934 # use the fast path, no race possible on push
935 bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
935 bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
936 cg = changegroup.getsubset(pushop.repo,
936 cg = changegroup.getsubset(pushop.repo,
937 outgoing,
937 outgoing,
938 bundler,
938 bundler,
939 'push',
939 'push',
940 fastpath=True)
940 fastpath=True)
941 else:
941 else:
942 cg = changegroup.getlocalchangegroup(pushop.repo, 'push', outgoing,
942 cg = changegroup.getlocalchangegroup(pushop.repo, 'push', outgoing,
943 bundlecaps)
943 bundlecaps)
944
944
945 # apply changegroup to remote
945 # apply changegroup to remote
946 if unbundle:
946 if unbundle:
947 # local repo finds heads on server, finds out what
947 # local repo finds heads on server, finds out what
948 # revs it must push. once revs transferred, if server
948 # revs it must push. once revs transferred, if server
949 # finds it has different heads (someone else won
949 # finds it has different heads (someone else won
950 # commit/push race), server aborts.
950 # commit/push race), server aborts.
951 if pushop.force:
951 if pushop.force:
952 remoteheads = ['force']
952 remoteheads = ['force']
953 else:
953 else:
954 remoteheads = pushop.remoteheads
954 remoteheads = pushop.remoteheads
955 # ssh: return remote's addchangegroup()
955 # ssh: return remote's addchangegroup()
956 # http: return remote's addchangegroup() or 0 for error
956 # http: return remote's addchangegroup() or 0 for error
957 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
957 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
958 pushop.repo.url())
958 pushop.repo.url())
959 else:
959 else:
960 # we return an integer indicating remote head count
960 # we return an integer indicating remote head count
961 # change
961 # change
962 pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
962 pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
963 pushop.repo.url())
963 pushop.repo.url())
964
964
965 def _pushsyncphase(pushop):
965 def _pushsyncphase(pushop):
966 """synchronise phase information locally and remotely"""
966 """synchronise phase information locally and remotely"""
967 cheads = pushop.commonheads
967 cheads = pushop.commonheads
968 # even when we don't push, exchanging phase data is useful
968 # even when we don't push, exchanging phase data is useful
969 remotephases = pushop.remote.listkeys('phases')
969 remotephases = pushop.remote.listkeys('phases')
970 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
970 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
971 and remotephases # server supports phases
971 and remotephases # server supports phases
972 and pushop.cgresult is None # nothing was pushed
972 and pushop.cgresult is None # nothing was pushed
973 and remotephases.get('publishing', False)):
973 and remotephases.get('publishing', False)):
974 # When:
974 # When:
975 # - this is a subrepo push
975 # - this is a subrepo push
976 # - and remote support phase
976 # - and remote support phase
977 # - and no changeset was pushed
977 # - and no changeset was pushed
978 # - and remote is publishing
978 # - and remote is publishing
979 # We may be in issue 3871 case!
979 # We may be in issue 3871 case!
980 # We drop the possible phase synchronisation done by
980 # We drop the possible phase synchronisation done by
981 # courtesy to publish changesets possibly locally draft
981 # courtesy to publish changesets possibly locally draft
982 # on the remote.
982 # on the remote.
983 remotephases = {'publishing': 'True'}
983 remotephases = {'publishing': 'True'}
984 if not remotephases: # old server or public only reply from non-publishing
984 if not remotephases: # old server or public only reply from non-publishing
985 _localphasemove(pushop, cheads)
985 _localphasemove(pushop, cheads)
986 # don't push any phase data as there is nothing to push
986 # don't push any phase data as there is nothing to push
987 else:
987 else:
988 ana = phases.analyzeremotephases(pushop.repo, cheads,
988 ana = phases.analyzeremotephases(pushop.repo, cheads,
989 remotephases)
989 remotephases)
990 pheads, droots = ana
990 pheads, droots = ana
991 ### Apply remote phase on local
991 ### Apply remote phase on local
992 if remotephases.get('publishing', False):
992 if remotephases.get('publishing', False):
993 _localphasemove(pushop, cheads)
993 _localphasemove(pushop, cheads)
994 else: # publish = False
994 else: # publish = False
995 _localphasemove(pushop, pheads)
995 _localphasemove(pushop, pheads)
996 _localphasemove(pushop, cheads, phases.draft)
996 _localphasemove(pushop, cheads, phases.draft)
997 ### Apply local phase on remote
997 ### Apply local phase on remote
998
998
999 if pushop.cgresult:
999 if pushop.cgresult:
1000 if 'phases' in pushop.stepsdone:
1000 if 'phases' in pushop.stepsdone:
1001 # phases already pushed though bundle2
1001 # phases already pushed though bundle2
1002 return
1002 return
1003 outdated = pushop.outdatedphases
1003 outdated = pushop.outdatedphases
1004 else:
1004 else:
1005 outdated = pushop.fallbackoutdatedphases
1005 outdated = pushop.fallbackoutdatedphases
1006
1006
1007 pushop.stepsdone.add('phases')
1007 pushop.stepsdone.add('phases')
1008
1008
1009 # filter heads already turned public by the push
1009 # filter heads already turned public by the push
1010 outdated = [c for c in outdated if c.node() not in pheads]
1010 outdated = [c for c in outdated if c.node() not in pheads]
1011 # fallback to independent pushkey command
1011 # fallback to independent pushkey command
1012 for newremotehead in outdated:
1012 for newremotehead in outdated:
1013 r = pushop.remote.pushkey('phases',
1013 r = pushop.remote.pushkey('phases',
1014 newremotehead.hex(),
1014 newremotehead.hex(),
1015 str(phases.draft),
1015 str(phases.draft),
1016 str(phases.public))
1016 str(phases.public))
1017 if not r:
1017 if not r:
1018 pushop.ui.warn(_('updating %s to public failed!\n')
1018 pushop.ui.warn(_('updating %s to public failed!\n')
1019 % newremotehead)
1019 % newremotehead)
1020
1020
1021 def _localphasemove(pushop, nodes, phase=phases.public):
1021 def _localphasemove(pushop, nodes, phase=phases.public):
1022 """move <nodes> to <phase> in the local source repo"""
1022 """move <nodes> to <phase> in the local source repo"""
1023 if pushop.trmanager:
1023 if pushop.trmanager:
1024 phases.advanceboundary(pushop.repo,
1024 phases.advanceboundary(pushop.repo,
1025 pushop.trmanager.transaction(),
1025 pushop.trmanager.transaction(),
1026 phase,
1026 phase,
1027 nodes)
1027 nodes)
1028 else:
1028 else:
1029 # repo is not locked, do not change any phases!
1029 # repo is not locked, do not change any phases!
1030 # Informs the user that phases should have been moved when
1030 # Informs the user that phases should have been moved when
1031 # applicable.
1031 # applicable.
1032 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
1032 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
1033 phasestr = phases.phasenames[phase]
1033 phasestr = phases.phasenames[phase]
1034 if actualmoves:
1034 if actualmoves:
1035 pushop.ui.status(_('cannot lock source repo, skipping '
1035 pushop.ui.status(_('cannot lock source repo, skipping '
1036 'local %s phase update\n') % phasestr)
1036 'local %s phase update\n') % phasestr)
1037
1037
1038 def _pushobsolete(pushop):
1038 def _pushobsolete(pushop):
1039 """utility function to push obsolete markers to a remote"""
1039 """utility function to push obsolete markers to a remote"""
1040 if 'obsmarkers' in pushop.stepsdone:
1040 if 'obsmarkers' in pushop.stepsdone:
1041 return
1041 return
1042 repo = pushop.repo
1042 repo = pushop.repo
1043 remote = pushop.remote
1043 remote = pushop.remote
1044 pushop.stepsdone.add('obsmarkers')
1044 pushop.stepsdone.add('obsmarkers')
1045 if pushop.outobsmarkers:
1045 if pushop.outobsmarkers:
1046 pushop.ui.debug('try to push obsolete markers to remote\n')
1046 pushop.ui.debug('try to push obsolete markers to remote\n')
1047 rslts = []
1047 rslts = []
1048 remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers))
1048 remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers))
1049 for key in sorted(remotedata, reverse=True):
1049 for key in sorted(remotedata, reverse=True):
1050 # reverse sort to ensure we end with dump0
1050 # reverse sort to ensure we end with dump0
1051 data = remotedata[key]
1051 data = remotedata[key]
1052 rslts.append(remote.pushkey('obsolete', key, '', data))
1052 rslts.append(remote.pushkey('obsolete', key, '', data))
1053 if [r for r in rslts if not r]:
1053 if [r for r in rslts if not r]:
1054 msg = _('failed to push some obsolete markers!\n')
1054 msg = _('failed to push some obsolete markers!\n')
1055 repo.ui.warn(msg)
1055 repo.ui.warn(msg)
1056
1056
1057 def _pushbookmark(pushop):
1057 def _pushbookmark(pushop):
1058 """Update bookmark position on remote"""
1058 """Update bookmark position on remote"""
1059 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
1059 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
1060 return
1060 return
1061 pushop.stepsdone.add('bookmarks')
1061 pushop.stepsdone.add('bookmarks')
1062 ui = pushop.ui
1062 ui = pushop.ui
1063 remote = pushop.remote
1063 remote = pushop.remote
1064
1064
1065 for b, old, new in pushop.outbookmarks:
1065 for b, old, new in pushop.outbookmarks:
1066 action = 'update'
1066 action = 'update'
1067 if not old:
1067 if not old:
1068 action = 'export'
1068 action = 'export'
1069 elif not new:
1069 elif not new:
1070 action = 'delete'
1070 action = 'delete'
1071 if remote.pushkey('bookmarks', b, old, new):
1071 if remote.pushkey('bookmarks', b, old, new):
1072 ui.status(bookmsgmap[action][0] % b)
1072 ui.status(bookmsgmap[action][0] % b)
1073 else:
1073 else:
1074 ui.warn(bookmsgmap[action][1] % b)
1074 ui.warn(bookmsgmap[action][1] % b)
1075 # discovery can have set the value form invalid entry
1075 # discovery can have set the value form invalid entry
1076 if pushop.bkresult is not None:
1076 if pushop.bkresult is not None:
1077 pushop.bkresult = 1
1077 pushop.bkresult = 1
1078
1078
1079 class pulloperation(object):
1079 class pulloperation(object):
1080 """A object that represent a single pull operation
1080 """A object that represent a single pull operation
1081
1081
1082 It purpose is to carry pull related state and very common operation.
1082 It purpose is to carry pull related state and very common operation.
1083
1083
1084 A new should be created at the beginning of each pull and discarded
1084 A new should be created at the beginning of each pull and discarded
1085 afterward.
1085 afterward.
1086 """
1086 """
1087
1087
1088 def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),
1088 def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),
1089 remotebookmarks=None, streamclonerequested=None):
1089 remotebookmarks=None, streamclonerequested=None):
1090 # repo we pull into
1090 # repo we pull into
1091 self.repo = repo
1091 self.repo = repo
1092 # repo we pull from
1092 # repo we pull from
1093 self.remote = remote
1093 self.remote = remote
1094 # revision we try to pull (None is "all")
1094 # revision we try to pull (None is "all")
1095 self.heads = heads
1095 self.heads = heads
1096 # bookmark pulled explicitly
1096 # bookmark pulled explicitly
1097 self.explicitbookmarks = [repo._bookmarks.expandname(bookmark)
1097 self.explicitbookmarks = [repo._bookmarks.expandname(bookmark)
1098 for bookmark in bookmarks]
1098 for bookmark in bookmarks]
1099 # do we force pull?
1099 # do we force pull?
1100 self.force = force
1100 self.force = force
1101 # whether a streaming clone was requested
1101 # whether a streaming clone was requested
1102 self.streamclonerequested = streamclonerequested
1102 self.streamclonerequested = streamclonerequested
1103 # transaction manager
1103 # transaction manager
1104 self.trmanager = None
1104 self.trmanager = None
1105 # set of common changeset between local and remote before pull
1105 # set of common changeset between local and remote before pull
1106 self.common = None
1106 self.common = None
1107 # set of pulled head
1107 # set of pulled head
1108 self.rheads = None
1108 self.rheads = None
1109 # list of missing changeset to fetch remotely
1109 # list of missing changeset to fetch remotely
1110 self.fetch = None
1110 self.fetch = None
1111 # remote bookmarks data
1111 # remote bookmarks data
1112 self.remotebookmarks = remotebookmarks
1112 self.remotebookmarks = remotebookmarks
1113 # result of changegroup pulling (used as return code by pull)
1113 # result of changegroup pulling (used as return code by pull)
1114 self.cgresult = None
1114 self.cgresult = None
1115 # list of step already done
1115 # list of step already done
1116 self.stepsdone = set()
1116 self.stepsdone = set()
1117 # Whether we attempted a clone from pre-generated bundles.
1117 # Whether we attempted a clone from pre-generated bundles.
1118 self.clonebundleattempted = False
1118 self.clonebundleattempted = False
1119
1119
1120 @util.propertycache
1120 @util.propertycache
1121 def pulledsubset(self):
1121 def pulledsubset(self):
1122 """heads of the set of changeset target by the pull"""
1122 """heads of the set of changeset target by the pull"""
1123 # compute target subset
1123 # compute target subset
1124 if self.heads is None:
1124 if self.heads is None:
1125 # We pulled every thing possible
1125 # We pulled every thing possible
1126 # sync on everything common
1126 # sync on everything common
1127 c = set(self.common)
1127 c = set(self.common)
1128 ret = list(self.common)
1128 ret = list(self.common)
1129 for n in self.rheads:
1129 for n in self.rheads:
1130 if n not in c:
1130 if n not in c:
1131 ret.append(n)
1131 ret.append(n)
1132 return ret
1132 return ret
1133 else:
1133 else:
1134 # We pulled a specific subset
1134 # We pulled a specific subset
1135 # sync on this subset
1135 # sync on this subset
1136 return self.heads
1136 return self.heads
1137
1137
1138 @util.propertycache
1138 @util.propertycache
1139 def canusebundle2(self):
1139 def canusebundle2(self):
1140 return not _forcebundle1(self)
1140 return not _forcebundle1(self)
1141
1141
1142 @util.propertycache
1142 @util.propertycache
1143 def remotebundle2caps(self):
1143 def remotebundle2caps(self):
1144 return bundle2.bundle2caps(self.remote)
1144 return bundle2.bundle2caps(self.remote)
1145
1145
1146 def gettransaction(self):
1146 def gettransaction(self):
1147 # deprecated; talk to trmanager directly
1147 # deprecated; talk to trmanager directly
1148 return self.trmanager.transaction()
1148 return self.trmanager.transaction()
1149
1149
1150 class transactionmanager(object):
1150 class transactionmanager(object):
1151 """An object to manage the life cycle of a transaction
1151 """An object to manage the life cycle of a transaction
1152
1152
1153 It creates the transaction on demand and calls the appropriate hooks when
1153 It creates the transaction on demand and calls the appropriate hooks when
1154 closing the transaction."""
1154 closing the transaction."""
1155 def __init__(self, repo, source, url):
1155 def __init__(self, repo, source, url):
1156 self.repo = repo
1156 self.repo = repo
1157 self.source = source
1157 self.source = source
1158 self.url = url
1158 self.url = url
1159 self._tr = None
1159 self._tr = None
1160
1160
1161 def transaction(self):
1161 def transaction(self):
1162 """Return an open transaction object, constructing if necessary"""
1162 """Return an open transaction object, constructing if necessary"""
1163 if not self._tr:
1163 if not self._tr:
1164 trname = '%s\n%s' % (self.source, util.hidepassword(self.url))
1164 trname = '%s\n%s' % (self.source, util.hidepassword(self.url))
1165 self._tr = self.repo.transaction(trname)
1165 self._tr = self.repo.transaction(trname)
1166 self._tr.hookargs['source'] = self.source
1166 self._tr.hookargs['source'] = self.source
1167 self._tr.hookargs['url'] = self.url
1167 self._tr.hookargs['url'] = self.url
1168 return self._tr
1168 return self._tr
1169
1169
1170 def close(self):
1170 def close(self):
1171 """close transaction if created"""
1171 """close transaction if created"""
1172 if self._tr is not None:
1172 if self._tr is not None:
1173 self._tr.close()
1173 self._tr.close()
1174
1174
1175 def release(self):
1175 def release(self):
1176 """release transaction if created"""
1176 """release transaction if created"""
1177 if self._tr is not None:
1177 if self._tr is not None:
1178 self._tr.release()
1178 self._tr.release()
1179
1179
1180 def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,
1180 def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,
1181 streamclonerequested=None):
1181 streamclonerequested=None):
1182 """Fetch repository data from a remote.
1182 """Fetch repository data from a remote.
1183
1183
1184 This is the main function used to retrieve data from a remote repository.
1184 This is the main function used to retrieve data from a remote repository.
1185
1185
1186 ``repo`` is the local repository to clone into.
1186 ``repo`` is the local repository to clone into.
1187 ``remote`` is a peer instance.
1187 ``remote`` is a peer instance.
1188 ``heads`` is an iterable of revisions we want to pull. ``None`` (the
1188 ``heads`` is an iterable of revisions we want to pull. ``None`` (the
1189 default) means to pull everything from the remote.
1189 default) means to pull everything from the remote.
1190 ``bookmarks`` is an iterable of bookmarks requesting to be pulled. By
1190 ``bookmarks`` is an iterable of bookmarks requesting to be pulled. By
1191 default, all remote bookmarks are pulled.
1191 default, all remote bookmarks are pulled.
1192 ``opargs`` are additional keyword arguments to pass to ``pulloperation``
1192 ``opargs`` are additional keyword arguments to pass to ``pulloperation``
1193 initialization.
1193 initialization.
1194 ``streamclonerequested`` is a boolean indicating whether a "streaming
1194 ``streamclonerequested`` is a boolean indicating whether a "streaming
1195 clone" is requested. A "streaming clone" is essentially a raw file copy
1195 clone" is requested. A "streaming clone" is essentially a raw file copy
1196 of revlogs from the server. This only works when the local repository is
1196 of revlogs from the server. This only works when the local repository is
1197 empty. The default value of ``None`` means to respect the server
1197 empty. The default value of ``None`` means to respect the server
1198 configuration for preferring stream clones.
1198 configuration for preferring stream clones.
1199
1199
1200 Returns the ``pulloperation`` created for this pull.
1200 Returns the ``pulloperation`` created for this pull.
1201 """
1201 """
1202 if opargs is None:
1202 if opargs is None:
1203 opargs = {}
1203 opargs = {}
1204 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
1204 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
1205 streamclonerequested=streamclonerequested, **opargs)
1205 streamclonerequested=streamclonerequested, **opargs)
1206 if pullop.remote.local():
1206 if pullop.remote.local():
1207 missing = set(pullop.remote.requirements) - pullop.repo.supported
1207 missing = set(pullop.remote.requirements) - pullop.repo.supported
1208 if missing:
1208 if missing:
1209 msg = _("required features are not"
1209 msg = _("required features are not"
1210 " supported in the destination:"
1210 " supported in the destination:"
1211 " %s") % (', '.join(sorted(missing)))
1211 " %s") % (', '.join(sorted(missing)))
1212 raise error.Abort(msg)
1212 raise error.Abort(msg)
1213
1213
1214 wlock = lock = None
1214 wlock = lock = None
1215 try:
1215 try:
1216 wlock = pullop.repo.wlock()
1216 wlock = pullop.repo.wlock()
1217 lock = pullop.repo.lock()
1217 lock = pullop.repo.lock()
1218 pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
1218 pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
1219 streamclone.maybeperformlegacystreamclone(pullop)
1219 streamclone.maybeperformlegacystreamclone(pullop)
1220 # This should ideally be in _pullbundle2(). However, it needs to run
1220 # This should ideally be in _pullbundle2(). However, it needs to run
1221 # before discovery to avoid extra work.
1221 # before discovery to avoid extra work.
1222 _maybeapplyclonebundle(pullop)
1222 _maybeapplyclonebundle(pullop)
1223 _pulldiscovery(pullop)
1223 _pulldiscovery(pullop)
1224 if pullop.canusebundle2:
1224 if pullop.canusebundle2:
1225 _pullbundle2(pullop)
1225 _pullbundle2(pullop)
1226 _pullchangeset(pullop)
1226 _pullchangeset(pullop)
1227 _pullphase(pullop)
1227 _pullphase(pullop)
1228 _pullbookmarks(pullop)
1228 _pullbookmarks(pullop)
1229 _pullobsolete(pullop)
1229 _pullobsolete(pullop)
1230 pullop.trmanager.close()
1230 pullop.trmanager.close()
1231 finally:
1231 finally:
1232 lockmod.release(pullop.trmanager, lock, wlock)
1232 lockmod.release(pullop.trmanager, lock, wlock)
1233
1233
1234 return pullop
1234 return pullop
1235
1235
1236 # list of steps to perform discovery before pull
1236 # list of steps to perform discovery before pull
1237 pulldiscoveryorder = []
1237 pulldiscoveryorder = []
1238
1238
1239 # Mapping between step name and function
1239 # Mapping between step name and function
1240 #
1240 #
1241 # This exists to help extensions wrap steps if necessary
1241 # This exists to help extensions wrap steps if necessary
1242 pulldiscoverymapping = {}
1242 pulldiscoverymapping = {}
1243
1243
1244 def pulldiscovery(stepname):
1244 def pulldiscovery(stepname):
1245 """decorator for function performing discovery before pull
1245 """decorator for function performing discovery before pull
1246
1246
1247 The function is added to the step -> function mapping and appended to the
1247 The function is added to the step -> function mapping and appended to the
1248 list of steps. Beware that decorated function will be added in order (this
1248 list of steps. Beware that decorated function will be added in order (this
1249 may matter).
1249 may matter).
1250
1250
1251 You can only use this decorator for a new step, if you want to wrap a step
1251 You can only use this decorator for a new step, if you want to wrap a step
1252 from an extension, change the pulldiscovery dictionary directly."""
1252 from an extension, change the pulldiscovery dictionary directly."""
1253 def dec(func):
1253 def dec(func):
1254 assert stepname not in pulldiscoverymapping
1254 assert stepname not in pulldiscoverymapping
1255 pulldiscoverymapping[stepname] = func
1255 pulldiscoverymapping[stepname] = func
1256 pulldiscoveryorder.append(stepname)
1256 pulldiscoveryorder.append(stepname)
1257 return func
1257 return func
1258 return dec
1258 return dec
1259
1259
1260 def _pulldiscovery(pullop):
1260 def _pulldiscovery(pullop):
1261 """Run all discovery steps"""
1261 """Run all discovery steps"""
1262 for stepname in pulldiscoveryorder:
1262 for stepname in pulldiscoveryorder:
1263 step = pulldiscoverymapping[stepname]
1263 step = pulldiscoverymapping[stepname]
1264 step(pullop)
1264 step(pullop)
1265
1265
1266 @pulldiscovery('b1:bookmarks')
1266 @pulldiscovery('b1:bookmarks')
1267 def _pullbookmarkbundle1(pullop):
1267 def _pullbookmarkbundle1(pullop):
1268 """fetch bookmark data in bundle1 case
1268 """fetch bookmark data in bundle1 case
1269
1269
1270 If not using bundle2, we have to fetch bookmarks before changeset
1270 If not using bundle2, we have to fetch bookmarks before changeset
1271 discovery to reduce the chance and impact of race conditions."""
1271 discovery to reduce the chance and impact of race conditions."""
1272 if pullop.remotebookmarks is not None:
1272 if pullop.remotebookmarks is not None:
1273 return
1273 return
1274 if pullop.canusebundle2 and 'listkeys' in pullop.remotebundle2caps:
1274 if pullop.canusebundle2 and 'listkeys' in pullop.remotebundle2caps:
1275 # all known bundle2 servers now support listkeys, but lets be nice with
1275 # all known bundle2 servers now support listkeys, but lets be nice with
1276 # new implementation.
1276 # new implementation.
1277 return
1277 return
1278 pullop.remotebookmarks = pullop.remote.listkeys('bookmarks')
1278 pullop.remotebookmarks = pullop.remote.listkeys('bookmarks')
1279
1279
1280
1280
1281 @pulldiscovery('changegroup')
1281 @pulldiscovery('changegroup')
1282 def _pulldiscoverychangegroup(pullop):
1282 def _pulldiscoverychangegroup(pullop):
1283 """discovery phase for the pull
1283 """discovery phase for the pull
1284
1284
1285 Current handle changeset discovery only, will change handle all discovery
1285 Current handle changeset discovery only, will change handle all discovery
1286 at some point."""
1286 at some point."""
1287 tmp = discovery.findcommonincoming(pullop.repo,
1287 tmp = discovery.findcommonincoming(pullop.repo,
1288 pullop.remote,
1288 pullop.remote,
1289 heads=pullop.heads,
1289 heads=pullop.heads,
1290 force=pullop.force)
1290 force=pullop.force)
1291 common, fetch, rheads = tmp
1291 common, fetch, rheads = tmp
1292 nm = pullop.repo.unfiltered().changelog.nodemap
1292 nm = pullop.repo.unfiltered().changelog.nodemap
1293 if fetch and rheads:
1293 if fetch and rheads:
1294 # If a remote heads in filtered locally, lets drop it from the unknown
1294 # If a remote heads in filtered locally, lets drop it from the unknown
1295 # remote heads and put in back in common.
1295 # remote heads and put in back in common.
1296 #
1296 #
1297 # This is a hackish solution to catch most of "common but locally
1297 # This is a hackish solution to catch most of "common but locally
1298 # hidden situation". We do not performs discovery on unfiltered
1298 # hidden situation". We do not performs discovery on unfiltered
1299 # repository because it end up doing a pathological amount of round
1299 # repository because it end up doing a pathological amount of round
1300 # trip for w huge amount of changeset we do not care about.
1300 # trip for w huge amount of changeset we do not care about.
1301 #
1301 #
1302 # If a set of such "common but filtered" changeset exist on the server
1302 # If a set of such "common but filtered" changeset exist on the server
1303 # but are not including a remote heads, we'll not be able to detect it,
1303 # but are not including a remote heads, we'll not be able to detect it,
1304 scommon = set(common)
1304 scommon = set(common)
1305 filteredrheads = []
1305 filteredrheads = []
1306 for n in rheads:
1306 for n in rheads:
1307 if n in nm:
1307 if n in nm:
1308 if n not in scommon:
1308 if n not in scommon:
1309 common.append(n)
1309 common.append(n)
1310 else:
1310 else:
1311 filteredrheads.append(n)
1311 filteredrheads.append(n)
1312 if not filteredrheads:
1312 if not filteredrheads:
1313 fetch = []
1313 fetch = []
1314 rheads = filteredrheads
1314 rheads = filteredrheads
1315 pullop.common = common
1315 pullop.common = common
1316 pullop.fetch = fetch
1316 pullop.fetch = fetch
1317 pullop.rheads = rheads
1317 pullop.rheads = rheads
1318
1318
1319 def _pullbundle2(pullop):
1319 def _pullbundle2(pullop):
1320 """pull data using bundle2
1320 """pull data using bundle2
1321
1321
1322 For now, the only supported data are changegroup."""
1322 For now, the only supported data are changegroup."""
1323 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
1323 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
1324
1324
1325 streaming, streamreqs = streamclone.canperformstreamclone(pullop)
1325 streaming, streamreqs = streamclone.canperformstreamclone(pullop)
1326
1326
1327 # pulling changegroup
1327 # pulling changegroup
1328 pullop.stepsdone.add('changegroup')
1328 pullop.stepsdone.add('changegroup')
1329
1329
1330 kwargs['common'] = pullop.common
1330 kwargs['common'] = pullop.common
1331 kwargs['heads'] = pullop.heads or pullop.rheads
1331 kwargs['heads'] = pullop.heads or pullop.rheads
1332 kwargs['cg'] = pullop.fetch
1332 kwargs['cg'] = pullop.fetch
1333 if 'listkeys' in pullop.remotebundle2caps:
1333 if 'listkeys' in pullop.remotebundle2caps:
1334 kwargs['listkeys'] = ['phases']
1334 kwargs['listkeys'] = ['phases']
1335 if pullop.remotebookmarks is None:
1335 if pullop.remotebookmarks is None:
1336 # make sure to always includes bookmark data when migrating
1336 # make sure to always includes bookmark data when migrating
1337 # `hg incoming --bundle` to using this function.
1337 # `hg incoming --bundle` to using this function.
1338 kwargs['listkeys'].append('bookmarks')
1338 kwargs['listkeys'].append('bookmarks')
1339
1339
1340 # If this is a full pull / clone and the server supports the clone bundles
1340 # If this is a full pull / clone and the server supports the clone bundles
1341 # feature, tell the server whether we attempted a clone bundle. The
1341 # feature, tell the server whether we attempted a clone bundle. The
1342 # presence of this flag indicates the client supports clone bundles. This
1342 # presence of this flag indicates the client supports clone bundles. This
1343 # will enable the server to treat clients that support clone bundles
1343 # will enable the server to treat clients that support clone bundles
1344 # differently from those that don't.
1344 # differently from those that don't.
1345 if (pullop.remote.capable('clonebundles')
1345 if (pullop.remote.capable('clonebundles')
1346 and pullop.heads is None and list(pullop.common) == [nullid]):
1346 and pullop.heads is None and list(pullop.common) == [nullid]):
1347 kwargs['cbattempted'] = pullop.clonebundleattempted
1347 kwargs['cbattempted'] = pullop.clonebundleattempted
1348
1348
1349 if streaming:
1349 if streaming:
1350 pullop.repo.ui.status(_('streaming all changes\n'))
1350 pullop.repo.ui.status(_('streaming all changes\n'))
1351 elif not pullop.fetch:
1351 elif not pullop.fetch:
1352 pullop.repo.ui.status(_("no changes found\n"))
1352 pullop.repo.ui.status(_("no changes found\n"))
1353 pullop.cgresult = 0
1353 pullop.cgresult = 0
1354 else:
1354 else:
1355 if pullop.heads is None and list(pullop.common) == [nullid]:
1355 if pullop.heads is None and list(pullop.common) == [nullid]:
1356 pullop.repo.ui.status(_("requesting all changes\n"))
1356 pullop.repo.ui.status(_("requesting all changes\n"))
1357 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1357 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1358 remoteversions = bundle2.obsmarkersversion(pullop.remotebundle2caps)
1358 remoteversions = bundle2.obsmarkersversion(pullop.remotebundle2caps)
1359 if obsolete.commonversion(remoteversions) is not None:
1359 if obsolete.commonversion(remoteversions) is not None:
1360 kwargs['obsmarkers'] = True
1360 kwargs['obsmarkers'] = True
1361 pullop.stepsdone.add('obsmarkers')
1361 pullop.stepsdone.add('obsmarkers')
1362 _pullbundle2extraprepare(pullop, kwargs)
1362 _pullbundle2extraprepare(pullop, kwargs)
1363 bundle = pullop.remote.getbundle('pull', **kwargs)
1363 bundle = pullop.remote.getbundle('pull', **kwargs)
1364 try:
1364 try:
1365 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
1365 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
1366 except bundle2.AbortFromPart as exc:
1367 pullop.repo.ui.status(_('remote: abort: %s\n') % exc)
1368 raise error.Abort(_('pull failed on remote'), hint=exc.hint)
1366 except error.BundleValueError as exc:
1369 except error.BundleValueError as exc:
1367 raise error.Abort(_('missing support for %s') % exc)
1370 raise error.Abort(_('missing support for %s') % exc)
1368
1371
1369 if pullop.fetch:
1372 if pullop.fetch:
1370 results = [cg['return'] for cg in op.records['changegroup']]
1373 results = [cg['return'] for cg in op.records['changegroup']]
1371 pullop.cgresult = changegroup.combineresults(results)
1374 pullop.cgresult = changegroup.combineresults(results)
1372
1375
1373 # processing phases change
1376 # processing phases change
1374 for namespace, value in op.records['listkeys']:
1377 for namespace, value in op.records['listkeys']:
1375 if namespace == 'phases':
1378 if namespace == 'phases':
1376 _pullapplyphases(pullop, value)
1379 _pullapplyphases(pullop, value)
1377
1380
1378 # processing bookmark update
1381 # processing bookmark update
1379 for namespace, value in op.records['listkeys']:
1382 for namespace, value in op.records['listkeys']:
1380 if namespace == 'bookmarks':
1383 if namespace == 'bookmarks':
1381 pullop.remotebookmarks = value
1384 pullop.remotebookmarks = value
1382
1385
1383 # bookmark data were either already there or pulled in the bundle
1386 # bookmark data were either already there or pulled in the bundle
1384 if pullop.remotebookmarks is not None:
1387 if pullop.remotebookmarks is not None:
1385 _pullbookmarks(pullop)
1388 _pullbookmarks(pullop)
1386
1389
1387 def _pullbundle2extraprepare(pullop, kwargs):
1390 def _pullbundle2extraprepare(pullop, kwargs):
1388 """hook function so that extensions can extend the getbundle call"""
1391 """hook function so that extensions can extend the getbundle call"""
1389 pass
1392 pass
1390
1393
1391 def _pullchangeset(pullop):
1394 def _pullchangeset(pullop):
1392 """pull changeset from unbundle into the local repo"""
1395 """pull changeset from unbundle into the local repo"""
1393 # We delay the open of the transaction as late as possible so we
1396 # We delay the open of the transaction as late as possible so we
1394 # don't open transaction for nothing or you break future useful
1397 # don't open transaction for nothing or you break future useful
1395 # rollback call
1398 # rollback call
1396 if 'changegroup' in pullop.stepsdone:
1399 if 'changegroup' in pullop.stepsdone:
1397 return
1400 return
1398 pullop.stepsdone.add('changegroup')
1401 pullop.stepsdone.add('changegroup')
1399 if not pullop.fetch:
1402 if not pullop.fetch:
1400 pullop.repo.ui.status(_("no changes found\n"))
1403 pullop.repo.ui.status(_("no changes found\n"))
1401 pullop.cgresult = 0
1404 pullop.cgresult = 0
1402 return
1405 return
1403 pullop.gettransaction()
1406 pullop.gettransaction()
1404 if pullop.heads is None and list(pullop.common) == [nullid]:
1407 if pullop.heads is None and list(pullop.common) == [nullid]:
1405 pullop.repo.ui.status(_("requesting all changes\n"))
1408 pullop.repo.ui.status(_("requesting all changes\n"))
1406 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1409 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1407 # issue1320, avoid a race if remote changed after discovery
1410 # issue1320, avoid a race if remote changed after discovery
1408 pullop.heads = pullop.rheads
1411 pullop.heads = pullop.rheads
1409
1412
1410 if pullop.remote.capable('getbundle'):
1413 if pullop.remote.capable('getbundle'):
1411 # TODO: get bundlecaps from remote
1414 # TODO: get bundlecaps from remote
1412 cg = pullop.remote.getbundle('pull', common=pullop.common,
1415 cg = pullop.remote.getbundle('pull', common=pullop.common,
1413 heads=pullop.heads or pullop.rheads)
1416 heads=pullop.heads or pullop.rheads)
1414 elif pullop.heads is None:
1417 elif pullop.heads is None:
1415 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
1418 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
1416 elif not pullop.remote.capable('changegroupsubset'):
1419 elif not pullop.remote.capable('changegroupsubset'):
1417 raise error.Abort(_("partial pull cannot be done because "
1420 raise error.Abort(_("partial pull cannot be done because "
1418 "other repository doesn't support "
1421 "other repository doesn't support "
1419 "changegroupsubset."))
1422 "changegroupsubset."))
1420 else:
1423 else:
1421 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
1424 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
1422 pullop.cgresult = cg.apply(pullop.repo, 'pull', pullop.remote.url())
1425 pullop.cgresult = cg.apply(pullop.repo, 'pull', pullop.remote.url())
1423
1426
1424 def _pullphase(pullop):
1427 def _pullphase(pullop):
1425 # Get remote phases data from remote
1428 # Get remote phases data from remote
1426 if 'phases' in pullop.stepsdone:
1429 if 'phases' in pullop.stepsdone:
1427 return
1430 return
1428 remotephases = pullop.remote.listkeys('phases')
1431 remotephases = pullop.remote.listkeys('phases')
1429 _pullapplyphases(pullop, remotephases)
1432 _pullapplyphases(pullop, remotephases)
1430
1433
1431 def _pullapplyphases(pullop, remotephases):
1434 def _pullapplyphases(pullop, remotephases):
1432 """apply phase movement from observed remote state"""
1435 """apply phase movement from observed remote state"""
1433 if 'phases' in pullop.stepsdone:
1436 if 'phases' in pullop.stepsdone:
1434 return
1437 return
1435 pullop.stepsdone.add('phases')
1438 pullop.stepsdone.add('phases')
1436 publishing = bool(remotephases.get('publishing', False))
1439 publishing = bool(remotephases.get('publishing', False))
1437 if remotephases and not publishing:
1440 if remotephases and not publishing:
1438 # remote is new and non-publishing
1441 # remote is new and non-publishing
1439 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1442 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1440 pullop.pulledsubset,
1443 pullop.pulledsubset,
1441 remotephases)
1444 remotephases)
1442 dheads = pullop.pulledsubset
1445 dheads = pullop.pulledsubset
1443 else:
1446 else:
1444 # Remote is old or publishing all common changesets
1447 # Remote is old or publishing all common changesets
1445 # should be seen as public
1448 # should be seen as public
1446 pheads = pullop.pulledsubset
1449 pheads = pullop.pulledsubset
1447 dheads = []
1450 dheads = []
1448 unfi = pullop.repo.unfiltered()
1451 unfi = pullop.repo.unfiltered()
1449 phase = unfi._phasecache.phase
1452 phase = unfi._phasecache.phase
1450 rev = unfi.changelog.nodemap.get
1453 rev = unfi.changelog.nodemap.get
1451 public = phases.public
1454 public = phases.public
1452 draft = phases.draft
1455 draft = phases.draft
1453
1456
1454 # exclude changesets already public locally and update the others
1457 # exclude changesets already public locally and update the others
1455 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1458 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1456 if pheads:
1459 if pheads:
1457 tr = pullop.gettransaction()
1460 tr = pullop.gettransaction()
1458 phases.advanceboundary(pullop.repo, tr, public, pheads)
1461 phases.advanceboundary(pullop.repo, tr, public, pheads)
1459
1462
1460 # exclude changesets already draft locally and update the others
1463 # exclude changesets already draft locally and update the others
1461 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1464 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1462 if dheads:
1465 if dheads:
1463 tr = pullop.gettransaction()
1466 tr = pullop.gettransaction()
1464 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1467 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1465
1468
1466 def _pullbookmarks(pullop):
1469 def _pullbookmarks(pullop):
1467 """process the remote bookmark information to update the local one"""
1470 """process the remote bookmark information to update the local one"""
1468 if 'bookmarks' in pullop.stepsdone:
1471 if 'bookmarks' in pullop.stepsdone:
1469 return
1472 return
1470 pullop.stepsdone.add('bookmarks')
1473 pullop.stepsdone.add('bookmarks')
1471 repo = pullop.repo
1474 repo = pullop.repo
1472 remotebookmarks = pullop.remotebookmarks
1475 remotebookmarks = pullop.remotebookmarks
1473 remotebookmarks = bookmod.unhexlifybookmarks(remotebookmarks)
1476 remotebookmarks = bookmod.unhexlifybookmarks(remotebookmarks)
1474 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1477 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1475 pullop.remote.url(),
1478 pullop.remote.url(),
1476 pullop.gettransaction,
1479 pullop.gettransaction,
1477 explicit=pullop.explicitbookmarks)
1480 explicit=pullop.explicitbookmarks)
1478
1481
1479 def _pullobsolete(pullop):
1482 def _pullobsolete(pullop):
1480 """utility function to pull obsolete markers from a remote
1483 """utility function to pull obsolete markers from a remote
1481
1484
1482 The `gettransaction` is function that return the pull transaction, creating
1485 The `gettransaction` is function that return the pull transaction, creating
1483 one if necessary. We return the transaction to inform the calling code that
1486 one if necessary. We return the transaction to inform the calling code that
1484 a new transaction have been created (when applicable).
1487 a new transaction have been created (when applicable).
1485
1488
1486 Exists mostly to allow overriding for experimentation purpose"""
1489 Exists mostly to allow overriding for experimentation purpose"""
1487 if 'obsmarkers' in pullop.stepsdone:
1490 if 'obsmarkers' in pullop.stepsdone:
1488 return
1491 return
1489 pullop.stepsdone.add('obsmarkers')
1492 pullop.stepsdone.add('obsmarkers')
1490 tr = None
1493 tr = None
1491 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1494 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1492 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1495 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1493 remoteobs = pullop.remote.listkeys('obsolete')
1496 remoteobs = pullop.remote.listkeys('obsolete')
1494 if 'dump0' in remoteobs:
1497 if 'dump0' in remoteobs:
1495 tr = pullop.gettransaction()
1498 tr = pullop.gettransaction()
1496 markers = []
1499 markers = []
1497 for key in sorted(remoteobs, reverse=True):
1500 for key in sorted(remoteobs, reverse=True):
1498 if key.startswith('dump'):
1501 if key.startswith('dump'):
1499 data = base85.b85decode(remoteobs[key])
1502 data = base85.b85decode(remoteobs[key])
1500 version, newmarks = obsolete._readmarkers(data)
1503 version, newmarks = obsolete._readmarkers(data)
1501 markers += newmarks
1504 markers += newmarks
1502 if markers:
1505 if markers:
1503 pullop.repo.obsstore.add(tr, markers)
1506 pullop.repo.obsstore.add(tr, markers)
1504 pullop.repo.invalidatevolatilesets()
1507 pullop.repo.invalidatevolatilesets()
1505 return tr
1508 return tr
1506
1509
1507 def caps20to10(repo):
1510 def caps20to10(repo):
1508 """return a set with appropriate options to use bundle20 during getbundle"""
1511 """return a set with appropriate options to use bundle20 during getbundle"""
1509 caps = set(['HG20'])
1512 caps = set(['HG20'])
1510 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
1513 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
1511 caps.add('bundle2=' + urlreq.quote(capsblob))
1514 caps.add('bundle2=' + urlreq.quote(capsblob))
1512 return caps
1515 return caps
1513
1516
1514 # List of names of steps to perform for a bundle2 for getbundle, order matters.
1517 # List of names of steps to perform for a bundle2 for getbundle, order matters.
1515 getbundle2partsorder = []
1518 getbundle2partsorder = []
1516
1519
1517 # Mapping between step name and function
1520 # Mapping between step name and function
1518 #
1521 #
1519 # This exists to help extensions wrap steps if necessary
1522 # This exists to help extensions wrap steps if necessary
1520 getbundle2partsmapping = {}
1523 getbundle2partsmapping = {}
1521
1524
1522 def getbundle2partsgenerator(stepname, idx=None):
1525 def getbundle2partsgenerator(stepname, idx=None):
1523 """decorator for function generating bundle2 part for getbundle
1526 """decorator for function generating bundle2 part for getbundle
1524
1527
1525 The function is added to the step -> function mapping and appended to the
1528 The function is added to the step -> function mapping and appended to the
1526 list of steps. Beware that decorated functions will be added in order
1529 list of steps. Beware that decorated functions will be added in order
1527 (this may matter).
1530 (this may matter).
1528
1531
1529 You can only use this decorator for new steps, if you want to wrap a step
1532 You can only use this decorator for new steps, if you want to wrap a step
1530 from an extension, attack the getbundle2partsmapping dictionary directly."""
1533 from an extension, attack the getbundle2partsmapping dictionary directly."""
1531 def dec(func):
1534 def dec(func):
1532 assert stepname not in getbundle2partsmapping
1535 assert stepname not in getbundle2partsmapping
1533 getbundle2partsmapping[stepname] = func
1536 getbundle2partsmapping[stepname] = func
1534 if idx is None:
1537 if idx is None:
1535 getbundle2partsorder.append(stepname)
1538 getbundle2partsorder.append(stepname)
1536 else:
1539 else:
1537 getbundle2partsorder.insert(idx, stepname)
1540 getbundle2partsorder.insert(idx, stepname)
1538 return func
1541 return func
1539 return dec
1542 return dec
1540
1543
1541 def bundle2requested(bundlecaps):
1544 def bundle2requested(bundlecaps):
1542 if bundlecaps is not None:
1545 if bundlecaps is not None:
1543 return any(cap.startswith('HG2') for cap in bundlecaps)
1546 return any(cap.startswith('HG2') for cap in bundlecaps)
1544 return False
1547 return False
1545
1548
1546 def getbundlechunks(repo, source, heads=None, common=None, bundlecaps=None,
1549 def getbundlechunks(repo, source, heads=None, common=None, bundlecaps=None,
1547 **kwargs):
1550 **kwargs):
1548 """Return chunks constituting a bundle's raw data.
1551 """Return chunks constituting a bundle's raw data.
1549
1552
1550 Could be a bundle HG10 or a bundle HG20 depending on bundlecaps
1553 Could be a bundle HG10 or a bundle HG20 depending on bundlecaps
1551 passed.
1554 passed.
1552
1555
1553 Returns an iterator over raw chunks (of varying sizes).
1556 Returns an iterator over raw chunks (of varying sizes).
1554 """
1557 """
1555 usebundle2 = bundle2requested(bundlecaps)
1558 usebundle2 = bundle2requested(bundlecaps)
1556 # bundle10 case
1559 # bundle10 case
1557 if not usebundle2:
1560 if not usebundle2:
1558 if bundlecaps and not kwargs.get('cg', True):
1561 if bundlecaps and not kwargs.get('cg', True):
1559 raise ValueError(_('request for bundle10 must include changegroup'))
1562 raise ValueError(_('request for bundle10 must include changegroup'))
1560
1563
1561 if kwargs:
1564 if kwargs:
1562 raise ValueError(_('unsupported getbundle arguments: %s')
1565 raise ValueError(_('unsupported getbundle arguments: %s')
1563 % ', '.join(sorted(kwargs.keys())))
1566 % ', '.join(sorted(kwargs.keys())))
1564 outgoing = _computeoutgoing(repo, heads, common)
1567 outgoing = _computeoutgoing(repo, heads, common)
1565 bundler = changegroup.getbundler('01', repo, bundlecaps)
1568 bundler = changegroup.getbundler('01', repo, bundlecaps)
1566 return changegroup.getsubsetraw(repo, outgoing, bundler, source)
1569 return changegroup.getsubsetraw(repo, outgoing, bundler, source)
1567
1570
1568 # bundle20 case
1571 # bundle20 case
1569 b2caps = {}
1572 b2caps = {}
1570 for bcaps in bundlecaps:
1573 for bcaps in bundlecaps:
1571 if bcaps.startswith('bundle2='):
1574 if bcaps.startswith('bundle2='):
1572 blob = urlreq.unquote(bcaps[len('bundle2='):])
1575 blob = urlreq.unquote(bcaps[len('bundle2='):])
1573 b2caps.update(bundle2.decodecaps(blob))
1576 b2caps.update(bundle2.decodecaps(blob))
1574 bundler = bundle2.bundle20(repo.ui, b2caps)
1577 bundler = bundle2.bundle20(repo.ui, b2caps)
1575
1578
1576 kwargs['heads'] = heads
1579 kwargs['heads'] = heads
1577 kwargs['common'] = common
1580 kwargs['common'] = common
1578
1581
1579 for name in getbundle2partsorder:
1582 for name in getbundle2partsorder:
1580 func = getbundle2partsmapping[name]
1583 func = getbundle2partsmapping[name]
1581 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
1584 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
1582 **kwargs)
1585 **kwargs)
1583
1586
1584 return bundler.getchunks()
1587 return bundler.getchunks()
1585
1588
1586 @getbundle2partsgenerator('changegroup')
1589 @getbundle2partsgenerator('changegroup')
1587 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
1590 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
1588 b2caps=None, heads=None, common=None, **kwargs):
1591 b2caps=None, heads=None, common=None, **kwargs):
1589 """add a changegroup part to the requested bundle"""
1592 """add a changegroup part to the requested bundle"""
1590 cg = None
1593 cg = None
1591 if kwargs.get('cg', True):
1594 if kwargs.get('cg', True):
1592 # build changegroup bundle here.
1595 # build changegroup bundle here.
1593 version = '01'
1596 version = '01'
1594 cgversions = b2caps.get('changegroup')
1597 cgversions = b2caps.get('changegroup')
1595 if cgversions: # 3.1 and 3.2 ship with an empty value
1598 if cgversions: # 3.1 and 3.2 ship with an empty value
1596 cgversions = [v for v in cgversions
1599 cgversions = [v for v in cgversions
1597 if v in changegroup.supportedoutgoingversions(repo)]
1600 if v in changegroup.supportedoutgoingversions(repo)]
1598 if not cgversions:
1601 if not cgversions:
1599 raise ValueError(_('no common changegroup version'))
1602 raise ValueError(_('no common changegroup version'))
1600 version = max(cgversions)
1603 version = max(cgversions)
1601 outgoing = _computeoutgoing(repo, heads, common)
1604 outgoing = _computeoutgoing(repo, heads, common)
1602 cg = changegroup.getlocalchangegroupraw(repo, source, outgoing,
1605 cg = changegroup.getlocalchangegroupraw(repo, source, outgoing,
1603 bundlecaps=bundlecaps,
1606 bundlecaps=bundlecaps,
1604 version=version)
1607 version=version)
1605
1608
1606 if cg:
1609 if cg:
1607 part = bundler.newpart('changegroup', data=cg)
1610 part = bundler.newpart('changegroup', data=cg)
1608 if cgversions:
1611 if cgversions:
1609 part.addparam('version', version)
1612 part.addparam('version', version)
1610 part.addparam('nbchanges', str(len(outgoing.missing)), mandatory=False)
1613 part.addparam('nbchanges', str(len(outgoing.missing)), mandatory=False)
1611 if 'treemanifest' in repo.requirements:
1614 if 'treemanifest' in repo.requirements:
1612 part.addparam('treemanifest', '1')
1615 part.addparam('treemanifest', '1')
1613
1616
1614 @getbundle2partsgenerator('listkeys')
1617 @getbundle2partsgenerator('listkeys')
1615 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
1618 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
1616 b2caps=None, **kwargs):
1619 b2caps=None, **kwargs):
1617 """add parts containing listkeys namespaces to the requested bundle"""
1620 """add parts containing listkeys namespaces to the requested bundle"""
1618 listkeys = kwargs.get('listkeys', ())
1621 listkeys = kwargs.get('listkeys', ())
1619 for namespace in listkeys:
1622 for namespace in listkeys:
1620 part = bundler.newpart('listkeys')
1623 part = bundler.newpart('listkeys')
1621 part.addparam('namespace', namespace)
1624 part.addparam('namespace', namespace)
1622 keys = repo.listkeys(namespace).items()
1625 keys = repo.listkeys(namespace).items()
1623 part.data = pushkey.encodekeys(keys)
1626 part.data = pushkey.encodekeys(keys)
1624
1627
1625 @getbundle2partsgenerator('obsmarkers')
1628 @getbundle2partsgenerator('obsmarkers')
1626 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
1629 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
1627 b2caps=None, heads=None, **kwargs):
1630 b2caps=None, heads=None, **kwargs):
1628 """add an obsolescence markers part to the requested bundle"""
1631 """add an obsolescence markers part to the requested bundle"""
1629 if kwargs.get('obsmarkers', False):
1632 if kwargs.get('obsmarkers', False):
1630 if heads is None:
1633 if heads is None:
1631 heads = repo.heads()
1634 heads = repo.heads()
1632 subset = [c.node() for c in repo.set('::%ln', heads)]
1635 subset = [c.node() for c in repo.set('::%ln', heads)]
1633 markers = repo.obsstore.relevantmarkers(subset)
1636 markers = repo.obsstore.relevantmarkers(subset)
1634 markers = sorted(markers)
1637 markers = sorted(markers)
1635 buildobsmarkerspart(bundler, markers)
1638 buildobsmarkerspart(bundler, markers)
1636
1639
1637 @getbundle2partsgenerator('hgtagsfnodes')
1640 @getbundle2partsgenerator('hgtagsfnodes')
1638 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
1641 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
1639 b2caps=None, heads=None, common=None,
1642 b2caps=None, heads=None, common=None,
1640 **kwargs):
1643 **kwargs):
1641 """Transfer the .hgtags filenodes mapping.
1644 """Transfer the .hgtags filenodes mapping.
1642
1645
1643 Only values for heads in this bundle will be transferred.
1646 Only values for heads in this bundle will be transferred.
1644
1647
1645 The part data consists of pairs of 20 byte changeset node and .hgtags
1648 The part data consists of pairs of 20 byte changeset node and .hgtags
1646 filenodes raw values.
1649 filenodes raw values.
1647 """
1650 """
1648 # Don't send unless:
1651 # Don't send unless:
1649 # - changeset are being exchanged,
1652 # - changeset are being exchanged,
1650 # - the client supports it.
1653 # - the client supports it.
1651 if not (kwargs.get('cg', True) and 'hgtagsfnodes' in b2caps):
1654 if not (kwargs.get('cg', True) and 'hgtagsfnodes' in b2caps):
1652 return
1655 return
1653
1656
1654 outgoing = _computeoutgoing(repo, heads, common)
1657 outgoing = _computeoutgoing(repo, heads, common)
1655
1658
1656 if not outgoing.missingheads:
1659 if not outgoing.missingheads:
1657 return
1660 return
1658
1661
1659 cache = tags.hgtagsfnodescache(repo.unfiltered())
1662 cache = tags.hgtagsfnodescache(repo.unfiltered())
1660 chunks = []
1663 chunks = []
1661
1664
1662 # .hgtags fnodes are only relevant for head changesets. While we could
1665 # .hgtags fnodes are only relevant for head changesets. While we could
1663 # transfer values for all known nodes, there will likely be little to
1666 # transfer values for all known nodes, there will likely be little to
1664 # no benefit.
1667 # no benefit.
1665 #
1668 #
1666 # We don't bother using a generator to produce output data because
1669 # We don't bother using a generator to produce output data because
1667 # a) we only have 40 bytes per head and even esoteric numbers of heads
1670 # a) we only have 40 bytes per head and even esoteric numbers of heads
1668 # consume little memory (1M heads is 40MB) b) we don't want to send the
1671 # consume little memory (1M heads is 40MB) b) we don't want to send the
1669 # part if we don't have entries and knowing if we have entries requires
1672 # part if we don't have entries and knowing if we have entries requires
1670 # cache lookups.
1673 # cache lookups.
1671 for node in outgoing.missingheads:
1674 for node in outgoing.missingheads:
1672 # Don't compute missing, as this may slow down serving.
1675 # Don't compute missing, as this may slow down serving.
1673 fnode = cache.getfnode(node, computemissing=False)
1676 fnode = cache.getfnode(node, computemissing=False)
1674 if fnode is not None:
1677 if fnode is not None:
1675 chunks.extend([node, fnode])
1678 chunks.extend([node, fnode])
1676
1679
1677 if chunks:
1680 if chunks:
1678 bundler.newpart('hgtagsfnodes', data=''.join(chunks))
1681 bundler.newpart('hgtagsfnodes', data=''.join(chunks))
1679
1682
1680 def _getbookmarks(repo, **kwargs):
1683 def _getbookmarks(repo, **kwargs):
1681 """Returns bookmark to node mapping.
1684 """Returns bookmark to node mapping.
1682
1685
1683 This function is primarily used to generate `bookmarks` bundle2 part.
1686 This function is primarily used to generate `bookmarks` bundle2 part.
1684 It is a separate function in order to make it easy to wrap it
1687 It is a separate function in order to make it easy to wrap it
1685 in extensions. Passing `kwargs` to the function makes it easy to
1688 in extensions. Passing `kwargs` to the function makes it easy to
1686 add new parameters in extensions.
1689 add new parameters in extensions.
1687 """
1690 """
1688
1691
1689 return dict(bookmod.listbinbookmarks(repo))
1692 return dict(bookmod.listbinbookmarks(repo))
1690
1693
1691 def check_heads(repo, their_heads, context):
1694 def check_heads(repo, their_heads, context):
1692 """check if the heads of a repo have been modified
1695 """check if the heads of a repo have been modified
1693
1696
1694 Used by peer for unbundling.
1697 Used by peer for unbundling.
1695 """
1698 """
1696 heads = repo.heads()
1699 heads = repo.heads()
1697 heads_hash = hashlib.sha1(''.join(sorted(heads))).digest()
1700 heads_hash = hashlib.sha1(''.join(sorted(heads))).digest()
1698 if not (their_heads == ['force'] or their_heads == heads or
1701 if not (their_heads == ['force'] or their_heads == heads or
1699 their_heads == ['hashed', heads_hash]):
1702 their_heads == ['hashed', heads_hash]):
1700 # someone else committed/pushed/unbundled while we
1703 # someone else committed/pushed/unbundled while we
1701 # were transferring data
1704 # were transferring data
1702 raise error.PushRaced('repository changed while %s - '
1705 raise error.PushRaced('repository changed while %s - '
1703 'please try again' % context)
1706 'please try again' % context)
1704
1707
1705 def unbundle(repo, cg, heads, source, url):
1708 def unbundle(repo, cg, heads, source, url):
1706 """Apply a bundle to a repo.
1709 """Apply a bundle to a repo.
1707
1710
1708 this function makes sure the repo is locked during the application and have
1711 this function makes sure the repo is locked during the application and have
1709 mechanism to check that no push race occurred between the creation of the
1712 mechanism to check that no push race occurred between the creation of the
1710 bundle and its application.
1713 bundle and its application.
1711
1714
1712 If the push was raced as PushRaced exception is raised."""
1715 If the push was raced as PushRaced exception is raised."""
1713 r = 0
1716 r = 0
1714 # need a transaction when processing a bundle2 stream
1717 # need a transaction when processing a bundle2 stream
1715 # [wlock, lock, tr] - needs to be an array so nested functions can modify it
1718 # [wlock, lock, tr] - needs to be an array so nested functions can modify it
1716 lockandtr = [None, None, None]
1719 lockandtr = [None, None, None]
1717 recordout = None
1720 recordout = None
1718 # quick fix for output mismatch with bundle2 in 3.4
1721 # quick fix for output mismatch with bundle2 in 3.4
1719 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture',
1722 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture',
1720 False)
1723 False)
1721 if url.startswith('remote:http:') or url.startswith('remote:https:'):
1724 if url.startswith('remote:http:') or url.startswith('remote:https:'):
1722 captureoutput = True
1725 captureoutput = True
1723 try:
1726 try:
1724 check_heads(repo, heads, 'uploading changes')
1727 check_heads(repo, heads, 'uploading changes')
1725 # push can proceed
1728 # push can proceed
1726 if util.safehasattr(cg, 'params'):
1729 if util.safehasattr(cg, 'params'):
1727 r = None
1730 r = None
1728 try:
1731 try:
1729 def gettransaction():
1732 def gettransaction():
1730 if not lockandtr[2]:
1733 if not lockandtr[2]:
1731 lockandtr[0] = repo.wlock()
1734 lockandtr[0] = repo.wlock()
1732 lockandtr[1] = repo.lock()
1735 lockandtr[1] = repo.lock()
1733 lockandtr[2] = repo.transaction(source)
1736 lockandtr[2] = repo.transaction(source)
1734 lockandtr[2].hookargs['source'] = source
1737 lockandtr[2].hookargs['source'] = source
1735 lockandtr[2].hookargs['url'] = url
1738 lockandtr[2].hookargs['url'] = url
1736 lockandtr[2].hookargs['bundle2'] = '1'
1739 lockandtr[2].hookargs['bundle2'] = '1'
1737 return lockandtr[2]
1740 return lockandtr[2]
1738
1741
1739 # Do greedy locking by default until we're satisfied with lazy
1742 # Do greedy locking by default until we're satisfied with lazy
1740 # locking.
1743 # locking.
1741 if not repo.ui.configbool('experimental', 'bundle2lazylocking'):
1744 if not repo.ui.configbool('experimental', 'bundle2lazylocking'):
1742 gettransaction()
1745 gettransaction()
1743
1746
1744 op = bundle2.bundleoperation(repo, gettransaction,
1747 op = bundle2.bundleoperation(repo, gettransaction,
1745 captureoutput=captureoutput)
1748 captureoutput=captureoutput)
1746 try:
1749 try:
1747 op = bundle2.processbundle(repo, cg, op=op)
1750 op = bundle2.processbundle(repo, cg, op=op)
1748 finally:
1751 finally:
1749 r = op.reply
1752 r = op.reply
1750 if captureoutput and r is not None:
1753 if captureoutput and r is not None:
1751 repo.ui.pushbuffer(error=True, subproc=True)
1754 repo.ui.pushbuffer(error=True, subproc=True)
1752 def recordout(output):
1755 def recordout(output):
1753 r.newpart('output', data=output, mandatory=False)
1756 r.newpart('output', data=output, mandatory=False)
1754 if lockandtr[2] is not None:
1757 if lockandtr[2] is not None:
1755 lockandtr[2].close()
1758 lockandtr[2].close()
1756 except BaseException as exc:
1759 except BaseException as exc:
1757 exc.duringunbundle2 = True
1760 exc.duringunbundle2 = True
1758 if captureoutput and r is not None:
1761 if captureoutput and r is not None:
1759 parts = exc._bundle2salvagedoutput = r.salvageoutput()
1762 parts = exc._bundle2salvagedoutput = r.salvageoutput()
1760 def recordout(output):
1763 def recordout(output):
1761 part = bundle2.bundlepart('output', data=output,
1764 part = bundle2.bundlepart('output', data=output,
1762 mandatory=False)
1765 mandatory=False)
1763 parts.append(part)
1766 parts.append(part)
1764 raise
1767 raise
1765 else:
1768 else:
1766 lockandtr[1] = repo.lock()
1769 lockandtr[1] = repo.lock()
1767 r = cg.apply(repo, source, url)
1770 r = cg.apply(repo, source, url)
1768 finally:
1771 finally:
1769 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0])
1772 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0])
1770 if recordout is not None:
1773 if recordout is not None:
1771 recordout(repo.ui.popbuffer())
1774 recordout(repo.ui.popbuffer())
1772 return r
1775 return r
1773
1776
1774 def _maybeapplyclonebundle(pullop):
1777 def _maybeapplyclonebundle(pullop):
1775 """Apply a clone bundle from a remote, if possible."""
1778 """Apply a clone bundle from a remote, if possible."""
1776
1779
1777 repo = pullop.repo
1780 repo = pullop.repo
1778 remote = pullop.remote
1781 remote = pullop.remote
1779
1782
1780 if not repo.ui.configbool('ui', 'clonebundles', True):
1783 if not repo.ui.configbool('ui', 'clonebundles', True):
1781 return
1784 return
1782
1785
1783 # Only run if local repo is empty.
1786 # Only run if local repo is empty.
1784 if len(repo):
1787 if len(repo):
1785 return
1788 return
1786
1789
1787 if pullop.heads:
1790 if pullop.heads:
1788 return
1791 return
1789
1792
1790 if not remote.capable('clonebundles'):
1793 if not remote.capable('clonebundles'):
1791 return
1794 return
1792
1795
1793 res = remote._call('clonebundles')
1796 res = remote._call('clonebundles')
1794
1797
1795 # If we call the wire protocol command, that's good enough to record the
1798 # If we call the wire protocol command, that's good enough to record the
1796 # attempt.
1799 # attempt.
1797 pullop.clonebundleattempted = True
1800 pullop.clonebundleattempted = True
1798
1801
1799 entries = parseclonebundlesmanifest(repo, res)
1802 entries = parseclonebundlesmanifest(repo, res)
1800 if not entries:
1803 if not entries:
1801 repo.ui.note(_('no clone bundles available on remote; '
1804 repo.ui.note(_('no clone bundles available on remote; '
1802 'falling back to regular clone\n'))
1805 'falling back to regular clone\n'))
1803 return
1806 return
1804
1807
1805 entries = filterclonebundleentries(repo, entries)
1808 entries = filterclonebundleentries(repo, entries)
1806 if not entries:
1809 if not entries:
1807 # There is a thundering herd concern here. However, if a server
1810 # There is a thundering herd concern here. However, if a server
1808 # operator doesn't advertise bundles appropriate for its clients,
1811 # operator doesn't advertise bundles appropriate for its clients,
1809 # they deserve what's coming. Furthermore, from a client's
1812 # they deserve what's coming. Furthermore, from a client's
1810 # perspective, no automatic fallback would mean not being able to
1813 # perspective, no automatic fallback would mean not being able to
1811 # clone!
1814 # clone!
1812 repo.ui.warn(_('no compatible clone bundles available on server; '
1815 repo.ui.warn(_('no compatible clone bundles available on server; '
1813 'falling back to regular clone\n'))
1816 'falling back to regular clone\n'))
1814 repo.ui.warn(_('(you may want to report this to the server '
1817 repo.ui.warn(_('(you may want to report this to the server '
1815 'operator)\n'))
1818 'operator)\n'))
1816 return
1819 return
1817
1820
1818 entries = sortclonebundleentries(repo.ui, entries)
1821 entries = sortclonebundleentries(repo.ui, entries)
1819
1822
1820 url = entries[0]['URL']
1823 url = entries[0]['URL']
1821 repo.ui.status(_('applying clone bundle from %s\n') % url)
1824 repo.ui.status(_('applying clone bundle from %s\n') % url)
1822 if trypullbundlefromurl(repo.ui, repo, url):
1825 if trypullbundlefromurl(repo.ui, repo, url):
1823 repo.ui.status(_('finished applying clone bundle\n'))
1826 repo.ui.status(_('finished applying clone bundle\n'))
1824 # Bundle failed.
1827 # Bundle failed.
1825 #
1828 #
1826 # We abort by default to avoid the thundering herd of
1829 # We abort by default to avoid the thundering herd of
1827 # clients flooding a server that was expecting expensive
1830 # clients flooding a server that was expecting expensive
1828 # clone load to be offloaded.
1831 # clone load to be offloaded.
1829 elif repo.ui.configbool('ui', 'clonebundlefallback', False):
1832 elif repo.ui.configbool('ui', 'clonebundlefallback', False):
1830 repo.ui.warn(_('falling back to normal clone\n'))
1833 repo.ui.warn(_('falling back to normal clone\n'))
1831 else:
1834 else:
1832 raise error.Abort(_('error applying bundle'),
1835 raise error.Abort(_('error applying bundle'),
1833 hint=_('if this error persists, consider contacting '
1836 hint=_('if this error persists, consider contacting '
1834 'the server operator or disable clone '
1837 'the server operator or disable clone '
1835 'bundles via '
1838 'bundles via '
1836 '"--config ui.clonebundles=false"'))
1839 '"--config ui.clonebundles=false"'))
1837
1840
1838 def parseclonebundlesmanifest(repo, s):
1841 def parseclonebundlesmanifest(repo, s):
1839 """Parses the raw text of a clone bundles manifest.
1842 """Parses the raw text of a clone bundles manifest.
1840
1843
1841 Returns a list of dicts. The dicts have a ``URL`` key corresponding
1844 Returns a list of dicts. The dicts have a ``URL`` key corresponding
1842 to the URL and other keys are the attributes for the entry.
1845 to the URL and other keys are the attributes for the entry.
1843 """
1846 """
1844 m = []
1847 m = []
1845 for line in s.splitlines():
1848 for line in s.splitlines():
1846 fields = line.split()
1849 fields = line.split()
1847 if not fields:
1850 if not fields:
1848 continue
1851 continue
1849 attrs = {'URL': fields[0]}
1852 attrs = {'URL': fields[0]}
1850 for rawattr in fields[1:]:
1853 for rawattr in fields[1:]:
1851 key, value = rawattr.split('=', 1)
1854 key, value = rawattr.split('=', 1)
1852 key = urlreq.unquote(key)
1855 key = urlreq.unquote(key)
1853 value = urlreq.unquote(value)
1856 value = urlreq.unquote(value)
1854 attrs[key] = value
1857 attrs[key] = value
1855
1858
1856 # Parse BUNDLESPEC into components. This makes client-side
1859 # Parse BUNDLESPEC into components. This makes client-side
1857 # preferences easier to specify since you can prefer a single
1860 # preferences easier to specify since you can prefer a single
1858 # component of the BUNDLESPEC.
1861 # component of the BUNDLESPEC.
1859 if key == 'BUNDLESPEC':
1862 if key == 'BUNDLESPEC':
1860 try:
1863 try:
1861 comp, version, params = parsebundlespec(repo, value,
1864 comp, version, params = parsebundlespec(repo, value,
1862 externalnames=True)
1865 externalnames=True)
1863 attrs['COMPRESSION'] = comp
1866 attrs['COMPRESSION'] = comp
1864 attrs['VERSION'] = version
1867 attrs['VERSION'] = version
1865 except error.InvalidBundleSpecification:
1868 except error.InvalidBundleSpecification:
1866 pass
1869 pass
1867 except error.UnsupportedBundleSpecification:
1870 except error.UnsupportedBundleSpecification:
1868 pass
1871 pass
1869
1872
1870 m.append(attrs)
1873 m.append(attrs)
1871
1874
1872 return m
1875 return m
1873
1876
1874 def filterclonebundleentries(repo, entries):
1877 def filterclonebundleentries(repo, entries):
1875 """Remove incompatible clone bundle manifest entries.
1878 """Remove incompatible clone bundle manifest entries.
1876
1879
1877 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
1880 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
1878 and returns a new list consisting of only the entries that this client
1881 and returns a new list consisting of only the entries that this client
1879 should be able to apply.
1882 should be able to apply.
1880
1883
1881 There is no guarantee we'll be able to apply all returned entries because
1884 There is no guarantee we'll be able to apply all returned entries because
1882 the metadata we use to filter on may be missing or wrong.
1885 the metadata we use to filter on may be missing or wrong.
1883 """
1886 """
1884 newentries = []
1887 newentries = []
1885 for entry in entries:
1888 for entry in entries:
1886 spec = entry.get('BUNDLESPEC')
1889 spec = entry.get('BUNDLESPEC')
1887 if spec:
1890 if spec:
1888 try:
1891 try:
1889 parsebundlespec(repo, spec, strict=True)
1892 parsebundlespec(repo, spec, strict=True)
1890 except error.InvalidBundleSpecification as e:
1893 except error.InvalidBundleSpecification as e:
1891 repo.ui.debug(str(e) + '\n')
1894 repo.ui.debug(str(e) + '\n')
1892 continue
1895 continue
1893 except error.UnsupportedBundleSpecification as e:
1896 except error.UnsupportedBundleSpecification as e:
1894 repo.ui.debug('filtering %s because unsupported bundle '
1897 repo.ui.debug('filtering %s because unsupported bundle '
1895 'spec: %s\n' % (entry['URL'], str(e)))
1898 'spec: %s\n' % (entry['URL'], str(e)))
1896 continue
1899 continue
1897
1900
1898 if 'REQUIRESNI' in entry and not sslutil.hassni:
1901 if 'REQUIRESNI' in entry and not sslutil.hassni:
1899 repo.ui.debug('filtering %s because SNI not supported\n' %
1902 repo.ui.debug('filtering %s because SNI not supported\n' %
1900 entry['URL'])
1903 entry['URL'])
1901 continue
1904 continue
1902
1905
1903 newentries.append(entry)
1906 newentries.append(entry)
1904
1907
1905 return newentries
1908 return newentries
1906
1909
1907 class clonebundleentry(object):
1910 class clonebundleentry(object):
1908 """Represents an item in a clone bundles manifest.
1911 """Represents an item in a clone bundles manifest.
1909
1912
1910 This rich class is needed to support sorting since sorted() in Python 3
1913 This rich class is needed to support sorting since sorted() in Python 3
1911 doesn't support ``cmp`` and our comparison is complex enough that ``key=``
1914 doesn't support ``cmp`` and our comparison is complex enough that ``key=``
1912 won't work.
1915 won't work.
1913 """
1916 """
1914
1917
1915 def __init__(self, value, prefers):
1918 def __init__(self, value, prefers):
1916 self.value = value
1919 self.value = value
1917 self.prefers = prefers
1920 self.prefers = prefers
1918
1921
1919 def _cmp(self, other):
1922 def _cmp(self, other):
1920 for prefkey, prefvalue in self.prefers:
1923 for prefkey, prefvalue in self.prefers:
1921 avalue = self.value.get(prefkey)
1924 avalue = self.value.get(prefkey)
1922 bvalue = other.value.get(prefkey)
1925 bvalue = other.value.get(prefkey)
1923
1926
1924 # Special case for b missing attribute and a matches exactly.
1927 # Special case for b missing attribute and a matches exactly.
1925 if avalue is not None and bvalue is None and avalue == prefvalue:
1928 if avalue is not None and bvalue is None and avalue == prefvalue:
1926 return -1
1929 return -1
1927
1930
1928 # Special case for a missing attribute and b matches exactly.
1931 # Special case for a missing attribute and b matches exactly.
1929 if bvalue is not None and avalue is None and bvalue == prefvalue:
1932 if bvalue is not None and avalue is None and bvalue == prefvalue:
1930 return 1
1933 return 1
1931
1934
1932 # We can't compare unless attribute present on both.
1935 # We can't compare unless attribute present on both.
1933 if avalue is None or bvalue is None:
1936 if avalue is None or bvalue is None:
1934 continue
1937 continue
1935
1938
1936 # Same values should fall back to next attribute.
1939 # Same values should fall back to next attribute.
1937 if avalue == bvalue:
1940 if avalue == bvalue:
1938 continue
1941 continue
1939
1942
1940 # Exact matches come first.
1943 # Exact matches come first.
1941 if avalue == prefvalue:
1944 if avalue == prefvalue:
1942 return -1
1945 return -1
1943 if bvalue == prefvalue:
1946 if bvalue == prefvalue:
1944 return 1
1947 return 1
1945
1948
1946 # Fall back to next attribute.
1949 # Fall back to next attribute.
1947 continue
1950 continue
1948
1951
1949 # If we got here we couldn't sort by attributes and prefers. Fall
1952 # If we got here we couldn't sort by attributes and prefers. Fall
1950 # back to index order.
1953 # back to index order.
1951 return 0
1954 return 0
1952
1955
1953 def __lt__(self, other):
1956 def __lt__(self, other):
1954 return self._cmp(other) < 0
1957 return self._cmp(other) < 0
1955
1958
1956 def __gt__(self, other):
1959 def __gt__(self, other):
1957 return self._cmp(other) > 0
1960 return self._cmp(other) > 0
1958
1961
1959 def __eq__(self, other):
1962 def __eq__(self, other):
1960 return self._cmp(other) == 0
1963 return self._cmp(other) == 0
1961
1964
1962 def __le__(self, other):
1965 def __le__(self, other):
1963 return self._cmp(other) <= 0
1966 return self._cmp(other) <= 0
1964
1967
1965 def __ge__(self, other):
1968 def __ge__(self, other):
1966 return self._cmp(other) >= 0
1969 return self._cmp(other) >= 0
1967
1970
1968 def __ne__(self, other):
1971 def __ne__(self, other):
1969 return self._cmp(other) != 0
1972 return self._cmp(other) != 0
1970
1973
1971 def sortclonebundleentries(ui, entries):
1974 def sortclonebundleentries(ui, entries):
1972 prefers = ui.configlist('ui', 'clonebundleprefers', default=[])
1975 prefers = ui.configlist('ui', 'clonebundleprefers', default=[])
1973 if not prefers:
1976 if not prefers:
1974 return list(entries)
1977 return list(entries)
1975
1978
1976 prefers = [p.split('=', 1) for p in prefers]
1979 prefers = [p.split('=', 1) for p in prefers]
1977
1980
1978 items = sorted(clonebundleentry(v, prefers) for v in entries)
1981 items = sorted(clonebundleentry(v, prefers) for v in entries)
1979 return [i.value for i in items]
1982 return [i.value for i in items]
1980
1983
1981 def trypullbundlefromurl(ui, repo, url):
1984 def trypullbundlefromurl(ui, repo, url):
1982 """Attempt to apply a bundle from a URL."""
1985 """Attempt to apply a bundle from a URL."""
1983 lock = repo.lock()
1986 lock = repo.lock()
1984 try:
1987 try:
1985 tr = repo.transaction('bundleurl')
1988 tr = repo.transaction('bundleurl')
1986 try:
1989 try:
1987 try:
1990 try:
1988 fh = urlmod.open(ui, url)
1991 fh = urlmod.open(ui, url)
1989 cg = readbundle(ui, fh, 'stream')
1992 cg = readbundle(ui, fh, 'stream')
1990
1993
1991 if isinstance(cg, bundle2.unbundle20):
1994 if isinstance(cg, bundle2.unbundle20):
1992 bundle2.processbundle(repo, cg, lambda: tr)
1995 bundle2.processbundle(repo, cg, lambda: tr)
1993 elif isinstance(cg, streamclone.streamcloneapplier):
1996 elif isinstance(cg, streamclone.streamcloneapplier):
1994 cg.apply(repo)
1997 cg.apply(repo)
1995 else:
1998 else:
1996 cg.apply(repo, 'clonebundles', url)
1999 cg.apply(repo, 'clonebundles', url)
1997 tr.close()
2000 tr.close()
1998 return True
2001 return True
1999 except urlerr.httperror as e:
2002 except urlerr.httperror as e:
2000 ui.warn(_('HTTP error fetching bundle: %s\n') % str(e))
2003 ui.warn(_('HTTP error fetching bundle: %s\n') % str(e))
2001 except urlerr.urlerror as e:
2004 except urlerr.urlerror as e:
2002 ui.warn(_('error fetching bundle: %s\n') % e.reason[1])
2005 ui.warn(_('error fetching bundle: %s\n') % e.reason[1])
2003
2006
2004 return False
2007 return False
2005 finally:
2008 finally:
2006 tr.release()
2009 tr.release()
2007 finally:
2010 finally:
2008 lock.release()
2011 lock.release()
General Comments 0
You need to be logged in to leave comments. Login now