##// END OF EJS Templates
exchange: refactor APIs to obtain bundle data (API)...
exchange: refactor APIs to obtain bundle data (API) Currently, exchange.getbundle() returns either a cg1unpacker or a util.chunkbuffer (in the case of bundle2). This is kinda OK, as both expose a .read() to consumers. However, localpeer.getbundle() has code inferring what the response type is based on arguments and converts the util.chunkbuffer returned in the bundle2 case to a bundle2.unbundle20 instance. This is a sign that the API for exchange.getbundle() is not ideal because it doesn't consistently return an "unbundler" instance. In addition, unbundlers mask the fact that there is an underlying generator of changegroup data. In both cg1 and bundle2, this generator is being fed into a util.chunkbuffer so it can be re-exposed as a file object. util.chunkbuffer is a nice abstraction. However, it should only be used "at the edges." This is because keeping data as a generator is more efficient than converting it to a chunkbuffer, especially if we convert that chunkbuffer back to a generator (as is the case in some code paths currently). This patch refactors exchange.getbundle() into exchange.getbundlechunks(). The new API returns an iterator of chunks instead of a file-like object. Callers of exchange.getbundle() have been updated to use the new API. There is a minor change of behavior in test-getbundle.t. This is because `hg debuggetbundle` isn't defining bundlecaps. As a result, a cg1 data stream and unpacker is being produced. This is getting fed into a new bundle20 instance via bundle2.writebundle(), which uses a backchannel mechanism between changegroup generation to add the "nbchanges" part parameter. I never liked this backchannel mechanism and I plan to remove it someday. `hg bundle` still produces the "nbchanges" part parameter, so there should be no user-visible change of behavior. I consider this "regression" a bug in `hg debuggetbundle`. And that bug is captured by an existing "TODO" in the code to use bundle2 capabilities.

File last commit:

r30187:3e86261b default
r30187:3e86261b default
Show More
exchange.py
1956 lines | 72.5 KiB | text/x-python | PythonLexer
Mads Kiilerich
spelling: fixes from spell checker
r21024 # exchange.py - utility to exchange data between repos.
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 #
# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
Gregory Szorc
exchange: use absolute_import
r27523 from __future__ import absolute_import
import errno
Augie Fackler
cleanup: replace uses of util.(md5|sha1|sha256|sha512) with hashlib.\1...
r29341 import hashlib
Gregory Szorc
exchange: use absolute_import
r27523
from .i18n import _
from .node import (
hex,
nullid,
)
from . import (
base85,
bookmarks as bookmod,
bundle2,
changegroup,
discovery,
error,
lock as lockmod,
obsolete,
phases,
pushkey,
scmutil,
sslutil,
streamclone,
tags,
url as urlmod,
util,
)
Pierre-Yves David
exchange: extract push function from localrepo...
r20345
timeless
pycompat: switch to util.urlreq/util.urlerr for py3 compat
r28883 urlerr = util.urlerr
urlreq = util.urlreq
Gregory Szorc
exchange: refactor bundle specification parsing...
r26640 # Maps bundle compression human names to internal representation.
_bundlespeccompressions = {'none': None,
'bzip2': 'BZ',
'gzip': 'GZ',
}
Gregory Szorc
exchange: move bundle specification parsing from cmdutil...
r26639
Gregory Szorc
exchange: refactor bundle specification parsing...
r26640 # Maps bundle version human names to changegroup versions.
_bundlespeccgversions = {'v1': '01',
'v2': '02',
Gregory Szorc
exchange: support for streaming clone bundles...
r26756 'packed1': 's1',
Gregory Szorc
exchange: refactor bundle specification parsing...
r26640 'bundle2': '02', #legacy
}
Gregory Szorc
exchange: support preserving external names when parsing bundle specs...
r26646 def parsebundlespec(repo, spec, strict=True, externalnames=False):
Gregory Szorc
exchange: refactor bundle specification parsing...
r26640 """Parse a bundle string specification into parts.
Bundle specifications denote a well-defined bundle/exchange format.
The content of a given specification should not change over time in
order to ensure that bundles produced by a newer version of Mercurial are
readable from an older version.
The string currently has the form:
Gregory Szorc
exchange: move bundle specification parsing from cmdutil...
r26639
Gregory Szorc
exchange: support parameters in bundle specification strings...
r26759 <compression>-<type>[;<parameter0>[;<parameter1>]]
Gregory Szorc
exchange: refactor bundle specification parsing...
r26640
Where <compression> is one of the supported compression formats
Gregory Szorc
exchange: support parameters in bundle specification strings...
r26759 and <type> is (currently) a version string. A ";" can follow the type and
all text afterwards is interpretted as URI encoded, ";" delimited key=value
pairs.
Gregory Szorc
exchange: move bundle specification parsing from cmdutil...
r26639
Gregory Szorc
exchange: refactor bundle specification parsing...
r26640 If ``strict`` is True (the default) <compression> is required. Otherwise,
it is optional.
Gregory Szorc
exchange: move bundle specification parsing from cmdutil...
r26639
Gregory Szorc
exchange: support preserving external names when parsing bundle specs...
r26646 If ``externalnames`` is False (the default), the human-centric names will
be converted to their internal representation.
Gregory Szorc
exchange: support parameters in bundle specification strings...
r26759 Returns a 3-tuple of (compression, version, parameters). Compression will
be ``None`` if not in strict mode and a compression isn't defined.
Gregory Szorc
exchange: move bundle specification parsing from cmdutil...
r26639
Gregory Szorc
exchange: refactor bundle specification parsing...
r26640 An ``InvalidBundleSpecification`` is raised when the specification is
not syntactically well formed.
An ``UnsupportedBundleSpecification`` is raised when the compression or
bundle type/version is not recognized.
Gregory Szorc
exchange: move bundle specification parsing from cmdutil...
r26639
Gregory Szorc
exchange: refactor bundle specification parsing...
r26640 Note: this function will likely eventually return a more complex data
structure, including bundle2 part information.
Gregory Szorc
exchange: move bundle specification parsing from cmdutil...
r26639 """
Gregory Szorc
exchange: support parameters in bundle specification strings...
r26759 def parseparams(s):
if ';' not in s:
return s, {}
params = {}
version, paramstr = s.split(';', 1)
for p in paramstr.split(';'):
if '=' not in p:
raise error.InvalidBundleSpecification(
_('invalid bundle specification: '
'missing "=" in parameter: %s') % p)
key, value = p.split('=', 1)
timeless
pycompat: switch to util.urlreq/util.urlerr for py3 compat
r28883 key = urlreq.unquote(key)
value = urlreq.unquote(value)
Gregory Szorc
exchange: support parameters in bundle specification strings...
r26759 params[key] = value
return version, params
Gregory Szorc
exchange: refactor bundle specification parsing...
r26640 if strict and '-' not in spec:
raise error.InvalidBundleSpecification(
_('invalid bundle specification; '
'must be prefixed with compression: %s') % spec)
Gregory Szorc
exchange: move bundle specification parsing from cmdutil...
r26639
if '-' in spec:
Gregory Szorc
exchange: refactor bundle specification parsing...
r26640 compression, version = spec.split('-', 1)
Gregory Szorc
exchange: move bundle specification parsing from cmdutil...
r26639
Gregory Szorc
exchange: refactor bundle specification parsing...
r26640 if compression not in _bundlespeccompressions:
raise error.UnsupportedBundleSpecification(
_('%s compression is not supported') % compression)
Gregory Szorc
exchange: support parameters in bundle specification strings...
r26759 version, params = parseparams(version)
Gregory Szorc
exchange: refactor bundle specification parsing...
r26640 if version not in _bundlespeccgversions:
raise error.UnsupportedBundleSpecification(
_('%s is not a recognized bundle version') % version)
Gregory Szorc
exchange: move bundle specification parsing from cmdutil...
r26639 else:
Gregory Szorc
exchange: refactor bundle specification parsing...
r26640 # Value could be just the compression or just the version, in which
# case some defaults are assumed (but only when not in strict mode).
assert not strict
Gregory Szorc
exchange: move bundle specification parsing from cmdutil...
r26639
Gregory Szorc
exchange: support parameters in bundle specification strings...
r26759 spec, params = parseparams(spec)
Gregory Szorc
exchange: refactor bundle specification parsing...
r26640 if spec in _bundlespeccompressions:
compression = spec
version = 'v1'
if 'generaldelta' in repo.requirements:
version = 'v2'
elif spec in _bundlespeccgversions:
Gregory Szorc
exchange: support for streaming clone bundles...
r26756 if spec == 'packed1':
compression = 'none'
else:
compression = 'bzip2'
Gregory Szorc
exchange: refactor bundle specification parsing...
r26640 version = spec
else:
raise error.UnsupportedBundleSpecification(
_('%s is not a recognized bundle specification') % spec)
Gregory Szorc
exchange: move bundle specification parsing from cmdutil...
r26639
Gregory Szorc
exchange: parse requirements from stream clone specification string...
r26760 # The specification for packed1 can optionally declare the data formats
# required to apply it. If we see this metadata, compare against what the
# repo supports and error if the bundle isn't compatible.
if version == 'packed1' and 'requirements' in params:
requirements = set(params['requirements'].split(','))
missingreqs = requirements - repo.supportedformats
if missingreqs:
raise error.UnsupportedBundleSpecification(
_('missing support for repository features: %s') %
', '.join(sorted(missingreqs)))
Gregory Szorc
exchange: support preserving external names when parsing bundle specs...
r26646 if not externalnames:
compression = _bundlespeccompressions[compression]
version = _bundlespeccgversions[version]
Gregory Szorc
exchange: support parameters in bundle specification strings...
r26759 return compression, version, params
Gregory Szorc
exchange: move bundle specification parsing from cmdutil...
r26639
Pierre-Yves David
bundle2: add a ui argument to readbundle...
r21064 def readbundle(ui, fh, fname, vfs=None):
Pierre-Yves David
bundle2: prepare readbundle to return more that one type of bundle...
r21065 header = changegroup.readexactly(fh, 4)
Pierre-Yves David
bundle2: move `readbundle` into the `exchange` module...
r21063
Pierre-Yves David
bundle2: prepare readbundle to return more that one type of bundle...
r21065 alg = None
Pierre-Yves David
bundle2: move `readbundle` into the `exchange` module...
r21063 if not fname:
fname = "stream"
if not header.startswith('HG') and header.startswith('\0'):
fh = changegroup.headerlessfixup(fh, header)
Pierre-Yves David
bundle2: prepare readbundle to return more that one type of bundle...
r21065 header = "HG10"
alg = 'UN'
Pierre-Yves David
bundle2: move `readbundle` into the `exchange` module...
r21063 elif vfs:
fname = vfs.join(fname)
Pierre-Yves David
bundle2: prepare readbundle to return more that one type of bundle...
r21065 magic, version = header[0:2], header[2:4]
Pierre-Yves David
bundle2: move `readbundle` into the `exchange` module...
r21063
if magic != 'HG':
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('%s: not a Mercurial bundle') % fname)
Pierre-Yves David
bundle2: prepare readbundle to return more that one type of bundle...
r21065 if version == '10':
if alg is None:
alg = changegroup.readexactly(fh, 2)
Sune Foldager
changegroup: rename bundle-related functions and classes...
r22390 return changegroup.cg1unpacker(fh, alg)
Pierre-Yves David
bundle2: detect bundle2 stream/request on /HG2./ instead of /HG2Y/...
r24649 elif version.startswith('2'):
Pierre-Yves David
bundle2.getunbundler: rename "header" to "magicstring"...
r25640 return bundle2.getunbundler(ui, fh, magicstring=magic + version)
Gregory Szorc
exchange: support for streaming clone bundles...
r26756 elif version == 'S1':
return streamclone.streamcloneapplier(fh)
Pierre-Yves David
bundle2: prepare readbundle to return more that one type of bundle...
r21065 else:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_('%s: unknown bundle version %s') % (fname, version))
Pierre-Yves David
bundle2: move `readbundle` into the `exchange` module...
r21063
Gregory Szorc
exchange: implement function for inferring bundle specification...
r27883 def getbundlespec(ui, fh):
"""Infer the bundlespec from a bundle file handle.
The input file handle is seeked and the original seek position is not
restored.
"""
def speccompression(alg):
for k, v in _bundlespeccompressions.items():
if v == alg:
return k
return None
b = readbundle(ui, fh, None)
if isinstance(b, changegroup.cg1unpacker):
alg = b._type
if alg == '_truncatedBZ':
alg = 'BZ'
comp = speccompression(alg)
if not comp:
raise error.Abort(_('unknown compression algorithm: %s') % alg)
return '%s-v1' % comp
elif isinstance(b, bundle2.unbundle20):
if 'Compression' in b.params:
comp = speccompression(b.params['Compression'])
if not comp:
raise error.Abort(_('unknown compression algorithm: %s') % comp)
else:
comp = 'none'
version = None
for part in b.iterparts():
if part.type == 'changegroup':
version = part.params['version']
if version in ('01', '02'):
version = 'v2'
else:
raise error.Abort(_('changegroup version %s does not have '
'a known bundlespec') % version,
hint=_('try upgrading your Mercurial '
'client'))
if not version:
raise error.Abort(_('could not identify changegroup version in '
'bundle'))
return '%s-%s' % (comp, version)
elif isinstance(b, streamclone.streamcloneapplier):
requirements = streamclone.readbundle1header(fh)[2]
params = 'requirements=%s' % ','.join(sorted(requirements))
timeless
pycompat: switch to util.urlreq/util.urlerr for py3 compat
r28883 return 'none-packed1;%s' % urlreq.quote(params)
Gregory Szorc
exchange: implement function for inferring bundle specification...
r27883 else:
raise error.Abort(_('unknown bundle type: %s') % b)
Pierre-Yves David
exchange: add a `buildobsmarkerpart` function...
r22346 def buildobsmarkerspart(bundler, markers):
"""add an obsmarker part to the bundler with <markers>
No part is created if markers is empty.
Raises ValueError if the bundler doesn't support any known obsmarker format.
"""
if markers:
remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
version = obsolete.commonversion(remoteversions)
if version is None:
timeless@mozdev.org
grammar: use does instead of do where appropriate
r26779 raise ValueError('bundler does not support common obsmarker format')
Pierre-Yves David
exchange: add a `buildobsmarkerpart` function...
r22346 stream = obsolete.encodemarkers(markers, True, version=version)
Pierre-Yves David
bundle2: rename format, parts and config to final names...
r24686 return bundler.newpart('obsmarkers', data=stream)
Pierre-Yves David
exchange: add a `buildobsmarkerpart` function...
r22346 return None
Pierre-Yves David
push: introduce a pushoperation object...
r20346
Pierre-Yves David
computeoutgoing: move the function from 'changegroup' to 'exchange'...
r29808 def _computeoutgoing(repo, heads, common):
"""Computes which revs are outgoing given a set of common
and a set of heads.
This is a separate function so extensions can have access to
the logic.
Returns a discovery.outgoing object.
"""
cl = repo.changelog
if common:
hasnode = cl.hasnode
common = [n for n in common if hasnode(n)]
else:
common = [nullid]
if not heads:
heads = cl.heads()
return discovery.outgoing(repo, common, heads)
Pierre-Yves David
bundle2: rename the _canusebundle2 method to _forcebundle1...
r29682 def _forcebundle1(op):
"""return true if a pull/push must use bundle1
Pierre-Yves David
exchange: introduce a '_canusebundle2' function...
r24650
Pierre-Yves David
bundle2: add a devel option controling bundle version used for exchange...
r29683 This function is used to allow testing of the older bundle version"""
ui = op.repo.ui
forcebundle1 = False
# The goal is this config is to allow developper to choose the bundle
# version used during exchanged. This is especially handy during test.
# Value is a list of bundle version to be picked from, highest version
# should be used.
#
# developer config: devel.legacy.exchange
exchange = ui.configlist('devel', 'legacy.exchange')
Pierre-Yves David
bundle2: remove 'experimental.bundle2-exp' boolean config (BC)...
r29689 forcebundle1 = 'bundle2' not in exchange and 'bundle1' in exchange
Pierre-Yves David
bundle2: add a devel option controling bundle version used for exchange...
r29683 return forcebundle1 or not op.remote.capable('bundle2')
Pierre-Yves David
exchange: introduce a '_canusebundle2' function...
r24650
Pierre-Yves David
push: introduce a pushoperation object...
r20346 class pushoperation(object):
"""A object that represent a single push operation
Nathan Goldbaum
pushoperation: fix language issues in docstring
r28456 Its purpose is to carry push related state and very common operations.
Pierre-Yves David
push: introduce a pushoperation object...
r20346
Nathan Goldbaum
pushoperation: fix language issues in docstring
r28456 A new pushoperation should be created at the beginning of each push and
discarded afterward.
Pierre-Yves David
push: introduce a pushoperation object...
r20346 """
Pierre-Yves David
push: pass list of bookmark to `exchange.push`...
r22623 def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
bookmarks=()):
Pierre-Yves David
push: introduce a pushoperation object...
r20346 # repo we push from
self.repo = repo
Pierre-Yves David
push: ease access to current ui object...
r20347 self.ui = repo.ui
Pierre-Yves David
push: move `remote` argument in the push object...
r20348 # repo we push to
self.remote = remote
Pierre-Yves David
push: move `force` argument into the push object...
r20349 # force option provided
self.force = force
Pierre-Yves David
push: move `revs` argument into the push object...
r20350 # revs to be pushed (None is "all")
self.revs = revs
Pierre-Yves David
push: pass list of bookmark to `exchange.push`...
r22623 # bookmark explicitly pushed
self.bookmarks = bookmarks
Pierre-Yves David
push: move `newbranch` argument into the push object...
r20351 # allow push of new branch
self.newbranch = newbranch
Pierre-Yves David
push: move local lock logic in pushoperation...
r20436 # did a local lock get acquired?
self.locallocked = None
Pierre-Yves David
push: add a ``pushop.stepsdone`` attribute...
r21901 # step already performed
# (used to check what steps have been already performed through bundle2)
self.stepsdone = set()
Pierre-Yves David
push: rename `pushop.ret` to `pushop.cgresult`...
r22615 # Integer version of the changegroup push result
Pierre-Yves David
push: move push return value in the push object...
r20439 # - None means nothing to push
# - 0 means HTTP error
# - 1 means we pushed and remote head count is unchanged *or*
# we have outgoing changesets but refused to push
# - other values as described by addchangegroup()
Pierre-Yves David
push: rename `pushop.ret` to `pushop.cgresult`...
r22615 self.cgresult = None
Pierre-Yves David
push: add `pushoperation.bkresult`...
r22624 # Boolean value for the bookmark push
self.bkresult = None
Mads Kiilerich
spelling: fixes from spell checker
r21024 # discover.outgoing object (contains common and outgoing data)
Pierre-Yves David
push: move outgoing object in the push object...
r20440 self.outgoing = None
Pierre-Yves David
push: move `remoteheads` into the push object...
r20462 # all remote heads before the push
self.remoteheads = None
Pierre-Yves David
push: move `incoming` into the push object...
r20464 # testable as a boolean indicating if any nodes are missing locally.
self.incoming = None
Pierre-Yves David
push: perform phases discovery before the push...
r22019 # phases changes that must be pushed along side the changesets
self.outdatedphases = None
# phases changes that must be pushed if changeset push fails
self.fallbackoutdatedphases = None
Pierre-Yves David
push: move the list of obsmarker to push into the push operation...
r22034 # outgoing obsmarkers
Pierre-Yves David
push: introduce a discovery step for obsmarker...
r22035 self.outobsmarkers = set()
Pierre-Yves David
push: move bookmark discovery with other discovery steps...
r22239 # outgoing bookmarks
self.outbookmarks = []
Eric Sumner
push: elevate phase transaction to cover entire operation...
r23437 # transaction manager
self.trmanager = None
Pierre-Yves David
push: catch and process PushkeyFailed error...
r25485 # map { pushkey partid -> callback handling failure}
# used to handle exception from mandatory pushkey part failure
self.pkfailcb = {}
Pierre-Yves David
push: introduce a pushoperation object...
r20346
Pierre-Yves David
push: extract future heads computation into pushop...
r22014 @util.propertycache
def futureheads(self):
"""future remote heads if the changeset push succeeds"""
return self.outgoing.missingheads
Pierre-Yves David
push: extract fallback heads computation into pushop...
r22015 @util.propertycache
def fallbackheads(self):
"""future remote heads if the changeset push fails"""
if self.revs is None:
# not target to push, all common are relevant
return self.outgoing.commonheads
unfi = self.repo.unfiltered()
# I want cheads = heads(::missingheads and ::commonheads)
# (missingheads is revs with secret changeset filtered out)
#
# This can be expressed as:
# cheads = ( (missingheads and ::commonheads)
# + (commonheads and ::missingheads))"
# )
#
# while trying to push we already computed the following:
# common = (::commonheads)
# missing = ((commonheads::missingheads) - commonheads)
#
# We can pick:
# * missingheads part of common (::commonheads)
Durham Goode
exchange: allow fallbackheads to use lazy set behavior...
r26184 common = self.outgoing.common
Pierre-Yves David
push: extract fallback heads computation into pushop...
r22015 nm = self.repo.changelog.nodemap
cheads = [node for node in self.revs if nm[node] in common]
# and
# * commonheads parents on missing
revset = unfi.set('%ln and parents(roots(%ln))',
self.outgoing.commonheads,
self.outgoing.missing)
cheads.extend(c.node() for c in revset)
return cheads
Pierre-Yves David
push: move common heads computation into pushop...
r22016 @property
def commonheads(self):
"""set of all common heads after changeset bundle push"""
Pierre-Yves David
push: rename `pushop.ret` to `pushop.cgresult`...
r22615 if self.cgresult:
Pierre-Yves David
push: move common heads computation into pushop...
r22016 return self.futureheads
else:
return self.fallbackheads
Pierre-Yves David
push: extract fallback heads computation into pushop...
r22015
Pierre-Yves David
push: prepare the issue of multiple kinds of messages...
r22650 # mapping of message used when pushing bookmark
bookmsgmap = {'update': (_("updating bookmark %s\n"),
_('updating bookmark %s failed!\n')),
'export': (_("exporting bookmark %s\n"),
_('exporting bookmark %s failed!\n')),
'delete': (_("deleting remote bookmark %s\n"),
_('deleting remote bookmark %s failed!\n')),
}
Sean Farley
exchange: add oparg to push so that extensions can wrap pushop
r26729 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=(),
opargs=None):
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 '''Push outgoing changesets (limited by revs) from a local
repository to remote. Return an integer:
- None means nothing to push
- 0 means HTTP error
- 1 means we pushed and remote head count is unchanged *or*
we have outgoing changesets but refused to push
- other values as described by addchangegroup()
'''
Sean Farley
exchange: add oparg to push so that extensions can wrap pushop
r26729 if opargs is None:
opargs = {}
pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
**opargs)
Pierre-Yves David
push: move `remote` argument in the push object...
r20348 if pushop.remote.local():
missing = (set(pushop.repo.requirements)
- pushop.remote.local().supported)
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 if missing:
msg = _("required features are not"
" supported in the destination:"
" %s") % (', '.join(sorted(missing)))
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(msg)
Pierre-Yves David
exchange: extract push function from localrepo...
r20345
# there are two ways to push to remote repo:
#
# addchangegroup assumes local user can lock remote
# repo (local filesystem, old ssh servers).
#
# unbundle assumes local user cannot lock remote repo (new ssh
# servers, http servers).
Pierre-Yves David
push: move `remote` argument in the push object...
r20348 if not pushop.remote.canpush():
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_("destination does not support push"))
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 # get local lock as we might write phase data
Pierre-Yves David
push: acquire local 'wlock' if "pushback" is expected (BC) (issue4596)...
r24754 localwlock = locallock = None
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 try:
Pierre-Yves David
push: acquire local 'wlock' if "pushback" is expected (BC) (issue4596)...
r24754 # bundle2 push may receive a reply bundle touching bookmarks or other
# things requiring the wlock. Take it now to ensure proper ordering.
maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
Pierre-Yves David
bundle2: rename the _canusebundle2 method to _forcebundle1...
r29682 if (not _forcebundle1(pushop)) and maypushback:
Pierre-Yves David
push: acquire local 'wlock' if "pushback" is expected (BC) (issue4596)...
r24754 localwlock = pushop.repo.wlock()
Pierre-Yves David
push: introduce a pushoperation object...
r20346 locallock = pushop.repo.lock()
Pierre-Yves David
push: move local lock logic in pushoperation...
r20436 pushop.locallocked = True
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except IOError as err:
Pierre-Yves David
push: move local lock logic in pushoperation...
r20436 pushop.locallocked = False
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 if err.errno != errno.EACCES:
raise
# source repo cannot be locked.
# We do not abort the push, but just disable the local phase
# synchronisation.
msg = 'cannot lock source repository: %s\n' % err
Pierre-Yves David
push: ease access to current ui object...
r20347 pushop.ui.debug(msg)
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 try:
Eric Sumner
push: elevate phase transaction to cover entire operation...
r23437 if pushop.locallocked:
Sean Farley
exchange: use pushop.repo instead of repo
r26672 pushop.trmanager = transactionmanager(pushop.repo,
Eric Sumner
push: elevate phase transaction to cover entire operation...
r23437 'push-response',
pushop.remote.url())
Pierre-Yves David
push: pass a `pushoperation` object to localrepo.checkpush...
r20924 pushop.repo.checkpush(pushop)
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 lock = None
Pierre-Yves David
push: move `remote` argument in the push object...
r20348 unbundle = pushop.remote.capable('unbundle')
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 if not unbundle:
Pierre-Yves David
push: move `remote` argument in the push object...
r20348 lock = pushop.remote.lock()
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 try:
Pierre-Yves David
push: move discovery in its own function...
r20466 _pushdiscovery(pushop)
Pierre-Yves David
bundle2: rename the _canusebundle2 method to _forcebundle1...
r29682 if not _forcebundle1(pushop):
Pierre-Yves David
bundle2-push: move changegroup push validation inside _pushb2ctx...
r21903 _pushbundle2(pushop)
_pushchangeset(pushop)
Pierre-Yves David
push: move phases synchronisation function in its own function...
r20441 _pushsyncphase(pushop)
Pierre-Yves David
push: feed pushoperation object to _pushobsolete function...
r20433 _pushobsolete(pushop)
Pierre-Yves David
push: update bookmarks within the remote lock...
r22224 _pushbookmark(pushop)
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 finally:
if lock is not None:
lock.release()
Eric Sumner
push: elevate phase transaction to cover entire operation...
r23437 if pushop.trmanager:
pushop.trmanager.close()
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 finally:
Eric Sumner
push: elevate phase transaction to cover entire operation...
r23437 if pushop.trmanager:
pushop.trmanager.release()
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 if locallock is not None:
locallock.release()
Pierre-Yves David
push: acquire local 'wlock' if "pushback" is expected (BC) (issue4596)...
r24754 if localwlock is not None:
localwlock.release()
Pierre-Yves David
exchange: extract push function from localrepo...
r20345
Pierre-Yves David
push: `exchange.push` now returns the `pushoperation` object...
r22616 return pushop
Pierre-Yves David
push: move bookmarks exchange in the exchange module...
r20352
Pierre-Yves David
push: make discovery extensible...
r22018 # list of steps to perform discovery before push
pushdiscoveryorder = []
# Mapping between step name and function
#
# This exists to help extensions wrap steps if necessary
pushdiscoverymapping = {}
def pushdiscovery(stepname):
"""decorator for function performing discovery before push
The function is added to the step -> function mapping and appended to the
list of steps. Beware that decorated function will be added in order (this
may matter).
You can only use this decorator for a new step, if you want to wrap a step
from an extension, change the pushdiscovery dictionary directly."""
def dec(func):
assert stepname not in pushdiscoverymapping
pushdiscoverymapping[stepname] = func
pushdiscoveryorder.append(stepname)
return func
return dec
Pierre-Yves David
push: move discovery in its own function...
r20466 def _pushdiscovery(pushop):
Pierre-Yves David
push: make discovery extensible...
r22018 """Run all discovery steps"""
for stepname in pushdiscoveryorder:
step = pushdiscoverymapping[stepname]
step(pushop)
@pushdiscovery('changeset')
def _pushdiscoverychangeset(pushop):
"""discover the changeset that need to be pushed"""
Pierre-Yves David
push: move discovery in its own function...
r20466 fci = discovery.findcommonincoming
Pierre-Yves David
discovery: run discovery on filtered repository...
r23848 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force)
Pierre-Yves David
push: move discovery in its own function...
r20466 common, inc, remoteheads = commoninc
fco = discovery.findcommonoutgoing
Pierre-Yves David
discovery: run discovery on filtered repository...
r23848 outgoing = fco(pushop.repo, pushop.remote, onlyheads=pushop.revs,
Pierre-Yves David
push: move discovery in its own function...
r20466 commoninc=commoninc, force=pushop.force)
pushop.outgoing = outgoing
pushop.remoteheads = remoteheads
pushop.incoming = inc
Pierre-Yves David
push: perform phases discovery before the push...
r22019 @pushdiscovery('phase')
def _pushdiscoveryphase(pushop):
"""discover the phase that needs to be pushed
(computed for both success and failure case for changesets push)"""
outgoing = pushop.outgoing
unfi = pushop.repo.unfiltered()
remotephases = pushop.remote.listkeys('phases')
publishing = remotephases.get('publishing', False)
Pierre-Yves David
subrepo: detect issue3781 case earlier so it apply to bundle2...
r25337 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
and remotephases # server supports phases
and not pushop.outgoing.missing # no changesets to be pushed
and publishing):
# When:
# - this is a subrepo push
# - and remote support phase
# - and no changeset are to be pushed
# - and remote is publishing
# We may be in issue 3871 case!
# We drop the possible phase synchronisation done by
# courtesy to publish changesets possibly locally draft
# on the remote.
remotephases = {'publishing': 'True'}
Pierre-Yves David
push: perform phases discovery before the push...
r22019 ana = phases.analyzeremotephases(pushop.repo,
pushop.fallbackheads,
remotephases)
pheads, droots = ana
extracond = ''
if not publishing:
extracond = ' and public()'
revset = 'heads((%%ln::%%ln) %s)' % extracond
# Get the list of all revs draft on remote by public here.
# XXX Beware that revset break if droots is not strictly
# XXX root we may want to ensure it is but it is costly
fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
if not outgoing.missing:
future = fallback
else:
# adds changeset we are going to push as draft
#
Mads Kiilerich
spelling: fixes from proofreading of spell checker issues
r23139 # should not be necessary for publishing server, but because of an
Pierre-Yves David
push: perform phases discovery before the push...
r22019 # issue fixed in xxxxx we have to do it anyway.
fdroots = list(unfi.set('roots(%ln + %ln::)',
outgoing.missing, droots))
fdroots = [f.node() for f in fdroots]
future = list(unfi.set(revset, fdroots, pushop.futureheads))
pushop.outdatedphases = future
pushop.fallbackoutdatedphases = fallback
Pierre-Yves David
push: introduce a discovery step for obsmarker...
r22035 @pushdiscovery('obsmarker')
def _pushdiscoveryobsmarkers(pushop):
Durham Goode
obsolete: add exchange option...
r22953 if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
Pierre-Yves David
push: check if local and remote support evolution during discovery...
r22269 and pushop.repo.obsstore
and 'obsolete' in pushop.remote.listkeys('namespaces')):
Pierre-Yves David
push: only push obsmarkers relevant to the "pushed subset"...
r22350 repo = pushop.repo
# very naive computation, that can be quite expensive on big repo.
# However: evolution is currently slow on them anyway.
nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
Pierre-Yves David
push: introduce a discovery step for obsmarker...
r22035
Pierre-Yves David
push: move bookmark discovery with other discovery steps...
r22239 @pushdiscovery('bookmarks')
def _pushdiscoverybookmarks(pushop):
ui = pushop.ui
repo = pushop.repo.unfiltered()
remote = pushop.remote
ui.debug("checking for updated bookmarks\n")
ancestors = ()
if pushop.revs:
revnums = map(repo.changelog.rev, pushop.revs)
ancestors = repo.changelog.ancestors(revnums, inclusive=True)
remotebookmark = remote.listkeys('bookmarks')
liscju
bookmarks: add 'hg push -B .' for pushing the active bookmark (issue4917)
r28182 explicit = set([repo._bookmarks.expandname(bookmark)
for bookmark in pushop.bookmarks])
Pierre-Yves David
push: gather all bookmark decisions together...
r22651
Pierre-Yves David
exchange: import bookmarks as bookmod...
r22622 comp = bookmod.compare(repo, repo._bookmarks, remotebookmark, srchex=hex)
Gregory Szorc
bookmarks: explicitly track identical bookmarks...
r23081 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
Pierre-Yves David
push: move bookmark discovery with other discovery steps...
r22239 for b, scid, dcid in advsrc:
Pierre-Yves David
push: gather all bookmark decisions together...
r22651 if b in explicit:
explicit.remove(b)
Pierre-Yves David
push: move bookmark discovery with other discovery steps...
r22239 if not ancestors or repo[scid].rev() in ancestors:
pushop.outbookmarks.append((b, dcid, scid))
Pierre-Yves David
push: gather all bookmark decisions together...
r22651 # search added bookmark
for b, scid, dcid in addsrc:
if b in explicit:
explicit.remove(b)
pushop.outbookmarks.append((b, '', scid))
# search for overwritten bookmark
for b, scid, dcid in advdst + diverge + differ:
if b in explicit:
explicit.remove(b)
pushop.outbookmarks.append((b, dcid, scid))
# search for bookmark to delete
for b, scid, dcid in adddst:
if b in explicit:
explicit.remove(b)
# treat as "deleted locally"
pushop.outbookmarks.append((b, dcid, ''))
Gregory Szorc
exchange: don't report failure from identical bookmarks...
r23082 # identical bookmarks shouldn't get reported
for b, scid, dcid in same:
if b in explicit:
explicit.remove(b)
Pierre-Yves David
push: gather all bookmark decisions together...
r22651
if explicit:
explicit = sorted(explicit)
# we should probably list all of them
ui.warn(_('bookmark %s does not exist on the local '
'or remote repository!\n') % explicit[0])
pushop.bkresult = 2
pushop.outbookmarks.sort()
Pierre-Yves David
push: move bookmark discovery with other discovery steps...
r22239
Pierre-Yves David
push: move outgoing check logic in its own function...
r20465 def _pushcheckoutgoing(pushop):
outgoing = pushop.outgoing
unfi = pushop.repo.unfiltered()
if not outgoing.missing:
# nothing to push
scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
return False
# something to push
if not pushop.force:
# if repo.obsstore == False --> no obsolete
# then, save the iteration
if unfi.obsstore:
# this message are here for 80 char limit reason
mso = _("push includes obsolete changeset: %s!")
Matt Mackall
exchange: remove a broken i18n abuse
r22628 mst = {"unstable": _("push includes unstable changeset: %s!"),
"bumped": _("push includes bumped changeset: %s!"),
"divergent": _("push includes divergent changeset: %s!")}
Pierre-Yves David
push: move outgoing check logic in its own function...
r20465 # If we are to push if there is at least one
# obsolete or unstable changeset in missing, at
# least one of the missinghead will be obsolete or
# unstable. So checking heads only is ok
for node in outgoing.missingheads:
ctx = unfi[node]
if ctx.obsolete():
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(mso % ctx)
Pierre-Yves David
push: move outgoing check logic in its own function...
r20465 elif ctx.troubled():
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(mst[ctx.troubles()[0]] % ctx)
Matt Mackall
bookmarks: mark internal-only config option
r25836
Ryan McElroy
exchange: pass pushop to discovery.checkheads...
r26935 discovery.checkheads(pushop)
Pierre-Yves David
push: move outgoing check logic in its own function...
r20465 return True
Pierre-Yves David
push: rework the bundle2partsgenerators logic...
r22017 # List of names of steps to perform for an outgoing bundle2, order matters.
b2partsgenorder = []
# Mapping between step name and function
#
# This exists to help extensions wrap steps if necessary
b2partsgenmapping = {}
Pierre-Yves David
bundle2: add an 'idx' argument to the 'b2partsgenerator'...
r24731 def b2partsgenerator(stepname, idx=None):
Pierre-Yves David
push: rework the bundle2partsgenerators logic...
r22017 """decorator for function generating bundle2 part
The function is added to the step -> function mapping and appended to the
list of steps. Beware that decorated functions will be added in order
(this may matter).
You can only use this decorator for new steps, if you want to wrap a step
from an extension, attack the b2partsgenmapping dictionary directly."""
def dec(func):
assert stepname not in b2partsgenmapping
b2partsgenmapping[stepname] = func
Pierre-Yves David
bundle2: add an 'idx' argument to the 'b2partsgenerator'...
r24731 if idx is None:
b2partsgenorder.append(stepname)
else:
b2partsgenorder.insert(idx, stepname)
Pierre-Yves David
push: rework the bundle2partsgenerators logic...
r22017 return func
return dec
Ryan McElroy
bundle2: generate check:heads in a independent function
r26428 def _pushb2ctxcheckheads(pushop, bundler):
"""Generate race condition checking parts
Mads Kiilerich
spelling: trivial spell checking
r26781 Exists as an independent function to aid extensions
Ryan McElroy
bundle2: generate check:heads in a independent function
r26428 """
if not pushop.force:
bundler.newpart('check:heads', data=iter(pushop.remoteheads))
Pierre-Yves David
push: rework the bundle2partsgenerators logic...
r22017 @b2partsgenerator('changeset')
Pierre-Yves David
bundle2-push: extract changegroup logic in its own function...
r21899 def _pushb2ctx(pushop, bundler):
"""handle changegroup push through bundle2
Pierre-Yves David
push: rename `pushop.ret` to `pushop.cgresult`...
r22615 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
Pierre-Yves David
bundle2-push: extract changegroup logic in its own function...
r21899 """
Pierre-Yves David
push: use `stepsdone` to control changegroup push through bundle10 or bundle20...
r21902 if 'changesets' in pushop.stepsdone:
return
pushop.stepsdone.add('changesets')
Pierre-Yves David
bundle2-push: extract changegroup logic in its own function...
r21899 # Send known heads to the server for race detection.
Pierre-Yves David
bundle2-push: move changegroup push validation inside _pushb2ctx...
r21903 if not _pushcheckoutgoing(pushop):
return
Mads Kiilerich
localrepo: refactor prepushoutgoinghook to take a pushop...
r28876 pushop.repo.prepushoutgoinghooks(pushop)
Ryan McElroy
bundle2: generate check:heads in a independent function
r26428
_pushb2ctxcheckheads(pushop, bundler)
Pierre-Yves David
push: send highest changegroup format supported by both side...
r23180 b2caps = bundle2.bundle2caps(pushop.remote)
Martin von Zweigbergk
exchange: make _pushb2ctx() look more like _getbundlechangegrouppart()...
r28668 version = '01'
Pierre-Yves David
bundle2: rename format, parts and config to final names...
r24686 cgversions = b2caps.get('changegroup')
Martin von Zweigbergk
exchange: make _pushb2ctx() look more like _getbundlechangegrouppart()...
r28668 if cgversions: # 3.1 and 3.2 ship with an empty value
Martin von Zweigbergk
changegroup: hide packermap behind methods...
r27751 cgversions = [v for v in cgversions
Martin von Zweigbergk
changegroup: fix pulling to treemanifest repo from flat repo (issue5066)...
r27953 if v in changegroup.supportedoutgoingversions(
pushop.repo)]
Pierre-Yves David
push: send highest changegroup format supported by both side...
r23180 if not cgversions:
raise ValueError(_('no common changegroup version'))
version = max(cgversions)
Martin von Zweigbergk
exchange: make _pushb2ctx() look more like _getbundlechangegrouppart()...
r28668 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
pushop.outgoing,
version=version)
Pierre-Yves David
bundle2: rename format, parts and config to final names...
r24686 cgpart = bundler.newpart('changegroup', data=cg)
Martin von Zweigbergk
exchange: make _pushb2ctx() look more like _getbundlechangegrouppart()...
r28668 if cgversions:
Pierre-Yves David
push: send highest changegroup format supported by both side...
r23180 cgpart.addparam('version', version)
Martin von Zweigbergk
exchange: set 'treemanifest' param on pushed changegroups too...
r27938 if 'treemanifest' in pushop.repo.requirements:
cgpart.addparam('treemanifest', '1')
Pierre-Yves David
bundle2-push: extract changegroup logic in its own function...
r21899 def handlereply(op):
Mads Kiilerich
spelling: fixes from proofreading of spell checker issues
r23139 """extract addchangegroup returns from server reply"""
Pierre-Yves David
bundle2-push: extract changegroup logic in its own function...
r21899 cgreplies = op.records.getreplies(cgpart.id)
assert len(cgreplies['changegroup']) == 1
Pierre-Yves David
push: rename `pushop.ret` to `pushop.cgresult`...
r22615 pushop.cgresult = cgreplies['changegroup'][0]['return']
Pierre-Yves David
bundle2-push: extract changegroup logic in its own function...
r21899 return handlereply
Pierre-Yves David
push: include phase push in the unified bundle2 push...
r22020 @b2partsgenerator('phase')
def _pushb2phases(pushop, bundler):
"""handle phase push through bundle2"""
if 'phases' in pushop.stepsdone:
return
b2caps = bundle2.bundle2caps(pushop.remote)
Pierre-Yves David
bundle2: rename format, parts and config to final names...
r24686 if not 'pushkey' in b2caps:
Pierre-Yves David
push: include phase push in the unified bundle2 push...
r22020 return
pushop.stepsdone.add('phases')
part2node = []
Pierre-Yves David
phases: abort the whole push if phases fail to update (BC)...
r25502
def handlefailure(pushop, exc):
targetid = int(exc.partid)
for partid, node in part2node:
if partid == targetid:
raise error.Abort(_('updating %s to public failed') % node)
Pierre-Yves David
push: include phase push in the unified bundle2 push...
r22020 enc = pushkey.encode
for newremotehead in pushop.outdatedphases:
Pierre-Yves David
phases: abort the whole push if phases fail to update (BC)...
r25502 part = bundler.newpart('pushkey')
Pierre-Yves David
push: include phase push in the unified bundle2 push...
r22020 part.addparam('namespace', enc('phases'))
part.addparam('key', enc(newremotehead.hex()))
part.addparam('old', enc(str(phases.draft)))
part.addparam('new', enc(str(phases.public)))
part2node.append((part.id, newremotehead))
Pierre-Yves David
phases: abort the whole push if phases fail to update (BC)...
r25502 pushop.pkfailcb[part.id] = handlefailure
Pierre-Yves David
push: include phase push in the unified bundle2 push...
r22020 def handlereply(op):
for partid, node in part2node:
partrep = op.records.getreplies(partid)
results = partrep['pushkey']
assert len(results) <= 1
msg = None
if not results:
msg = _('server ignored update of %s to public!\n') % node
elif not int(results[0]['return']):
msg = _('updating %s to public failed!\n') % node
if msg is not None:
pushop.ui.warn(msg)
return handlereply
Pierre-Yves David
bundle2-push: introduce a list of part generating functions...
r21904
Pierre-Yves David
push: use bundle2 to push obsmarkers when possible
r22347 @b2partsgenerator('obsmarkers')
def _pushb2obsmarkers(pushop, bundler):
if 'obsmarkers' in pushop.stepsdone:
return
remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
if obsolete.commonversion(remoteversions) is None:
return
pushop.stepsdone.add('obsmarkers')
if pushop.outobsmarkers:
Pierre-Yves David
obsolete: sort obsmarkers during exchange...
r25118 markers = sorted(pushop.outobsmarkers)
buildobsmarkerspart(bundler, markers)
Pierre-Yves David
push: use bundle2 to push obsmarkers when possible
r22347
Pierre-Yves David
push: add bookmarks to the unified bundle2 push...
r22242 @b2partsgenerator('bookmarks')
def _pushb2bookmarks(pushop, bundler):
Martin von Zweigbergk
exchange: s/phase/bookmark/ in _pushb2bookmarks()
r25895 """handle bookmark push through bundle2"""
Pierre-Yves David
push: add bookmarks to the unified bundle2 push...
r22242 if 'bookmarks' in pushop.stepsdone:
return
b2caps = bundle2.bundle2caps(pushop.remote)
Pierre-Yves David
bundle2: rename format, parts and config to final names...
r24686 if 'pushkey' not in b2caps:
Pierre-Yves David
push: add bookmarks to the unified bundle2 push...
r22242 return
pushop.stepsdone.add('bookmarks')
part2book = []
enc = pushkey.encode
Pierre-Yves David
bookmarks: abort the whole push if bookmarks fails to update (BC)...
r25501
def handlefailure(pushop, exc):
targetid = int(exc.partid)
for partid, book, action in part2book:
if partid == targetid:
raise error.Abort(bookmsgmap[action][1].rstrip() % book)
# we should not be called for part we did not generated
assert False
Pierre-Yves David
push: add bookmarks to the unified bundle2 push...
r22242 for book, old, new in pushop.outbookmarks:
Pierre-Yves David
bookmarks: abort the whole push if bookmarks fails to update (BC)...
r25501 part = bundler.newpart('pushkey')
Pierre-Yves David
push: add bookmarks to the unified bundle2 push...
r22242 part.addparam('namespace', enc('bookmarks'))
part.addparam('key', enc(book))
part.addparam('old', enc(old))
part.addparam('new', enc(new))
Pierre-Yves David
push: prepare the issue of multiple kinds of messages...
r22650 action = 'update'
if not old:
action = 'export'
elif not new:
action = 'delete'
part2book.append((part.id, book, action))
Pierre-Yves David
bookmarks: abort the whole push if bookmarks fails to update (BC)...
r25501 pushop.pkfailcb[part.id] = handlefailure
Pierre-Yves David
push: prepare the issue of multiple kinds of messages...
r22650
Pierre-Yves David
push: add bookmarks to the unified bundle2 push...
r22242 def handlereply(op):
Pierre-Yves David
push: prepare the issue of multiple kinds of messages...
r22650 ui = pushop.ui
for partid, book, action in part2book:
Pierre-Yves David
push: add bookmarks to the unified bundle2 push...
r22242 partrep = op.records.getreplies(partid)
results = partrep['pushkey']
assert len(results) <= 1
if not results:
pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
else:
ret = int(results[0]['return'])
if ret:
Pierre-Yves David
push: prepare the issue of multiple kinds of messages...
r22650 ui.status(bookmsgmap[action][0] % book)
Pierre-Yves David
push: add bookmarks to the unified bundle2 push...
r22242 else:
Pierre-Yves David
push: prepare the issue of multiple kinds of messages...
r22650 ui.warn(bookmsgmap[action][1] % book)
Pierre-Yves David
push: set bkresult when pushing bookmarks through bundle2
r22649 if pushop.bkresult is not None:
pushop.bkresult = 1
Pierre-Yves David
push: add bookmarks to the unified bundle2 push...
r22242 return handlereply
Pierre-Yves David
bundle2: allow using bundle2 for push...
r21061 def _pushbundle2(pushop):
"""push data to the remote using bundle2
The only currently supported type of data is changegroup but this will
evolve in the future."""
Pierre-Yves David
bundle2: introduce a bundle2caps function...
r21644 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
Eric Sumner
bundle2-push: provide transaction to reply unbundler...
r23439 pushback = (pushop.trmanager
and pushop.ui.configbool('experimental', 'bundle2.pushback'))
Pierre-Yves David
bundle2: include client capabilities in the pushed bundle...
r21142 # create reply capability
Eric Sumner
bundle2-push: provide transaction to reply unbundler...
r23439 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo,
allowpushback=pushback))
Pierre-Yves David
bundle2: rename format, parts and config to final names...
r24686 bundler.newpart('replycaps', data=capsblob)
Pierre-Yves David
bundle2-push: introduce a list of part generating functions...
r21904 replyhandlers = []
Pierre-Yves David
push: rework the bundle2partsgenerators logic...
r22017 for partgenname in b2partsgenorder:
partgen = b2partsgenmapping[partgenname]
Pierre-Yves David
bundle2-push: introduce a list of part generating functions...
r21904 ret = partgen(pushop, bundler)
Pierre-Yves David
bundle2: only use callable return as reply handler...
r21941 if callable(ret):
replyhandlers.append(ret)
Pierre-Yves David
bundle2-push: introduce a list of part generating functions...
r21904 # do not push if nothing to push
Pierre-Yves David
bundle2-push: move changegroup push validation inside _pushb2ctx...
r21903 if bundler.nbparts <= 1:
return
Pierre-Yves David
bundle2: allow using bundle2 for push...
r21061 stream = util.chunkbuffer(bundler.getchunks())
Pierre-Yves David
bundle2: catch UnknownPartError during local push...
r21182 try:
Pierre-Yves David
push: catch and process PushkeyFailed error...
r25485 try:
Augie Fackler
exchange: correctly specify url to unbundle (issue5145)...
r29704 reply = pushop.remote.unbundle(
stream, ['force'], pushop.remote.url())
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except error.BundleValueError as exc:
liscju
i18n: translate abort messages...
r29389 raise error.Abort(_('missing support for %s') % exc)
Pierre-Yves David
push: catch and process PushkeyFailed error...
r25485 try:
trgetter = None
if pushback:
trgetter = pushop.trmanager.transaction
op = bundle2.processbundle(pushop.repo, reply, trgetter)
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except error.BundleValueError as exc:
liscju
i18n: translate abort messages...
r29389 raise error.Abort(_('missing support for %s') % exc)
Gregory Szorc
bundle2: attribute remote failures to remote (issue4788)...
r26829 except bundle2.AbortFromPart as exc:
pushop.ui.status(_('remote: %s\n') % exc)
raise error.Abort(_('push failed on remote'), hint=exc.hint)
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except error.PushkeyFailed as exc:
Pierre-Yves David
push: catch and process PushkeyFailed error...
r25485 partid = int(exc.partid)
if partid not in pushop.pkfailcb:
raise
pushop.pkfailcb[partid](pushop, exc)
Pierre-Yves David
bundle2-push: introduce a list of part generating functions...
r21904 for rephand in replyhandlers:
rephand(op)
Pierre-Yves David
bundle2: allow using bundle2 for push...
r21061
Pierre-Yves David
push: move changeset push logic in its own function...
r20463 def _pushchangeset(pushop):
"""Make the actual push of changeset bundle to remote repo"""
Pierre-Yves David
push: use `stepsdone` to control changegroup push through bundle10 or bundle20...
r21902 if 'changesets' in pushop.stepsdone:
return
pushop.stepsdone.add('changesets')
Pierre-Yves David
bundle2-push: move changegroup push validation inside _pushb2ctx...
r21903 if not _pushcheckoutgoing(pushop):
return
Mads Kiilerich
localrepo: refactor prepushoutgoinghook to take a pushop...
r28876 pushop.repo.prepushoutgoinghooks(pushop)
Pierre-Yves David
push: move changeset push logic in its own function...
r20463 outgoing = pushop.outgoing
unbundle = pushop.remote.capable('unbundle')
# TODO: get bundlecaps from remote
bundlecaps = None
# create a changegroup from local
if pushop.revs is None and not (outgoing.excluded
or pushop.repo.changelog.filteredrevs):
# push everything,
# use the fast path, no race possible on push
Sune Foldager
changegroup: rename bundle-related functions and classes...
r22390 bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
Pierre-Yves David
localrepo: move the _changegroupsubset method in changegroup module...
r20925 cg = changegroup.getsubset(pushop.repo,
outgoing,
bundler,
'push',
fastpath=True)
Pierre-Yves David
push: move changeset push logic in its own function...
r20463 else:
Sune Foldager
changegroup: rename bundle-related functions and classes...
r22390 cg = changegroup.getlocalchangegroup(pushop.repo, 'push', outgoing,
Pierre-Yves David
localrepo: move the getlocalbundle method in changegroup module...
r20928 bundlecaps)
Pierre-Yves David
push: move changeset push logic in its own function...
r20463
# apply changegroup to remote
if unbundle:
# local repo finds heads on server, finds out what
# revs it must push. once revs transferred, if server
# finds it has different heads (someone else won
# commit/push race), server aborts.
if pushop.force:
remoteheads = ['force']
else:
remoteheads = pushop.remoteheads
# ssh: return remote's addchangegroup()
# http: return remote's addchangegroup() or 0 for error
Pierre-Yves David
push: rename `pushop.ret` to `pushop.cgresult`...
r22615 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
Matt Mackall
push: restore contents of HG_URL for hooks (issue4268)
r21761 pushop.repo.url())
Pierre-Yves David
push: move changeset push logic in its own function...
r20463 else:
# we return an integer indicating remote head count
# change
Pierre-Yves David
push: rename `pushop.ret` to `pushop.cgresult`...
r22615 pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
pushop.repo.url())
Pierre-Yves David
push: move changeset push logic in its own function...
r20463
Pierre-Yves David
push: move phases synchronisation function in its own function...
r20441 def _pushsyncphase(pushop):
Mads Kiilerich
spelling: fixes from spell checker
r21024 """synchronise phase information locally and remotely"""
Pierre-Yves David
push: extract new common set computation from phase synchronisation...
r20468 cheads = pushop.commonheads
Pierre-Yves David
push: move phases synchronisation function in its own function...
r20441 # even when we don't push, exchanging phase data is useful
remotephases = pushop.remote.listkeys('phases')
if (pushop.ui.configbool('ui', '_usedassubrepo', False)
and remotephases # server supports phases
Pierre-Yves David
push: rename `pushop.ret` to `pushop.cgresult`...
r22615 and pushop.cgresult is None # nothing was pushed
Pierre-Yves David
push: move phases synchronisation function in its own function...
r20441 and remotephases.get('publishing', False)):
# When:
# - this is a subrepo push
# - and remote support phase
# - and no changeset was pushed
# - and remote is publishing
# We may be in issue 3871 case!
# We drop the possible phase synchronisation done by
# courtesy to publish changesets possibly locally draft
# on the remote.
remotephases = {'publishing': 'True'}
Pierre-Yves David
exchange: restore truncated comment...
r21012 if not remotephases: # old server or public only reply from non-publishing
Pierre-Yves David
push: move phases synchronisation function in its own function...
r20441 _localphasemove(pushop, cheads)
# don't push any phase data as there is nothing to push
else:
ana = phases.analyzeremotephases(pushop.repo, cheads,
remotephases)
pheads, droots = ana
### Apply remote phase on local
if remotephases.get('publishing', False):
_localphasemove(pushop, cheads)
else: # publish = False
_localphasemove(pushop, pheads)
_localphasemove(pushop, cheads, phases.draft)
### Apply local phase on remote
Pierre-Yves David
push: rename `pushop.ret` to `pushop.cgresult`...
r22615 if pushop.cgresult:
Pierre-Yves David
push: include phase push in the unified bundle2 push...
r22020 if 'phases' in pushop.stepsdone:
# phases already pushed though bundle2
return
Pierre-Yves David
push: perform phases discovery before the push...
r22019 outdated = pushop.outdatedphases
else:
outdated = pushop.fallbackoutdatedphases
Pierre-Yves David
push: include phase push in the unified bundle2 push...
r22020 pushop.stepsdone.add('phases')
Pierre-Yves David
push: perform phases discovery before the push...
r22019 # filter heads already turned public by the push
outdated = [c for c in outdated if c.node() not in pheads]
Pierre-Yves David
push: stop independent usage of bundle2 in syncphase (issue4454)...
r23376 # fallback to independent pushkey command
for newremotehead in outdated:
r = pushop.remote.pushkey('phases',
newremotehead.hex(),
str(phases.draft),
str(phases.public))
if not r:
pushop.ui.warn(_('updating %s to public failed!\n')
% newremotehead)
Pierre-Yves David
push: move phases synchronisation function in its own function...
r20441
Pierre-Yves David
push: move local phase move in a normal function...
r20438 def _localphasemove(pushop, nodes, phase=phases.public):
"""move <nodes> to <phase> in the local source repo"""
Eric Sumner
push: elevate phase transaction to cover entire operation...
r23437 if pushop.trmanager:
phases.advanceboundary(pushop.repo,
pushop.trmanager.transaction(),
phase,
nodes)
Pierre-Yves David
push: move local phase move in a normal function...
r20438 else:
# repo is not locked, do not change any phases!
# Informs the user that phases should have been moved when
# applicable.
actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
phasestr = phases.phasenames[phase]
if actualmoves:
pushop.ui.status(_('cannot lock source repo, skipping '
'local %s phase update\n') % phasestr)
Pierre-Yves David
push: feed pushoperation object to _pushobsolete function...
r20433 def _pushobsolete(pushop):
Pierre-Yves David
push: drop now outdated comment...
r20434 """utility function to push obsolete markers to a remote"""
Pierre-Yves David
push: use stepsdone for obsmarkers push...
r22036 if 'obsmarkers' in pushop.stepsdone:
return
Pierre-Yves David
push: feed pushoperation object to _pushobsolete function...
r20433 repo = pushop.repo
remote = pushop.remote
Pierre-Yves David
push: use stepsdone for obsmarkers push...
r22036 pushop.stepsdone.add('obsmarkers')
Pierre-Yves David
push: only push obsmarkers relevant to the "pushed subset"...
r22350 if pushop.outobsmarkers:
Pierre-Yves David
push: only say we are trying to push obsmarkers when we actually try...
r25559 pushop.ui.debug('try to push obsolete markers to remote\n')
Pierre-Yves David
push: move obsolescence marker exchange in the exchange module...
r20432 rslts = []
Pierre-Yves David
obsolete: sort obsmarkers during exchange...
r25118 remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers))
Pierre-Yves David
push: move obsolescence marker exchange in the exchange module...
r20432 for key in sorted(remotedata, reverse=True):
# reverse sort to ensure we end with dump0
data = remotedata[key]
rslts.append(remote.pushkey('obsolete', key, '', data))
if [r for r in rslts if not r]:
msg = _('failed to push some obsolete markers!\n')
repo.ui.warn(msg)
Pierre-Yves David
push: feed pushoperation object to _pushbookmark function...
r20431 def _pushbookmark(pushop):
Pierre-Yves David
push: move bookmarks exchange in the exchange module...
r20352 """Update bookmark position on remote"""
Pierre-Yves David
push: rename `pushop.ret` to `pushop.cgresult`...
r22615 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
Pierre-Yves David
pushbookmark: do not attempt to update bookmarks if the push failed (BC)...
r22228 return
Pierre-Yves David
push: use stepsdone to control bookmark push...
r22240 pushop.stepsdone.add('bookmarks')
Pierre-Yves David
push: feed pushoperation object to _pushbookmark function...
r20431 ui = pushop.ui
remote = pushop.remote
Pierre-Yves David
push: prepare the issue of multiple kinds of messages...
r22650
Pierre-Yves David
push: move bookmark discovery with other discovery steps...
r22239 for b, old, new in pushop.outbookmarks:
Pierre-Yves David
push: prepare the issue of multiple kinds of messages...
r22650 action = 'update'
if not old:
action = 'export'
elif not new:
action = 'delete'
Pierre-Yves David
push: move bookmark discovery with other discovery steps...
r22239 if remote.pushkey('bookmarks', b, old, new):
Pierre-Yves David
push: prepare the issue of multiple kinds of messages...
r22650 ui.status(bookmsgmap[action][0] % b)
Pierre-Yves David
push: move bookmarks exchange in the exchange module...
r20352 else:
Pierre-Yves David
push: prepare the issue of multiple kinds of messages...
r22650 ui.warn(bookmsgmap[action][1] % b)
# discovery can have set the value form invalid entry
if pushop.bkresult is not None:
pushop.bkresult = 1
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 class pulloperation(object):
"""A object that represent a single pull operation
Mike Edgar
exchange: swap "push" for "pull" in pulloperation docstring
r23219 It purpose is to carry pull related state and very common operation.
Pierre-Yves David
pull: introduce a pulloperation object...
r20472
Mads Kiilerich
spelling: fixes from spell checker
r21024 A new should be created at the beginning of each pull and discarded
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 afterward.
"""
Pierre-Yves David
pull: prevent race condition in bookmark update when using -B (issue4689)...
r25446 def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),
Gregory Szorc
exchange: teach pull about requested stream clones...
r26448 remotebookmarks=None, streamclonerequested=None):
Siddharth Agarwal
exchange: fix docs for pulloperation...
r20596 # repo we pull into
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 self.repo = repo
Siddharth Agarwal
exchange: fix docs for pulloperation...
r20596 # repo we pull from
Pierre-Yves David
pull: move `remote` argument into pull object...
r20473 self.remote = remote
Pierre-Yves David
pull: move `heads` argument into pull object...
r20474 # revision we try to pull (None is "all")
self.heads = heads
Pierre-Yves David
pull: move bookmark pulling into its own function...
r22654 # bookmark pulled explicitly
liscju
bookmarks: add 'hg pull -B .' for pulling the active bookmark (issue5258)
r29376 self.explicitbookmarks = [repo._bookmarks.expandname(bookmark)
for bookmark in bookmarks]
Pierre-Yves David
pull: move `force` argument into pull object...
r20475 # do we force pull?
self.force = force
Gregory Szorc
exchange: teach pull about requested stream clones...
r26448 # whether a streaming clone was requested
self.streamclonerequested = streamclonerequested
Eric Sumner
pull: extract transaction logic into separate object...
r23436 # transaction manager
self.trmanager = None
Pierre-Yves David
pull: make pulled subset a propertycache of the pull object...
r20487 # set of common changeset between local and remote before pull
self.common = None
# set of pulled head
self.rheads = None
Mads Kiilerich
spelling: fixes from spell checker
r21024 # list of missing changeset to fetch remotely
Pierre-Yves David
pull: move `fetch` subset into the object...
r20488 self.fetch = None
Pierre-Yves David
pull: move bookmark pulling into its own function...
r22654 # remote bookmarks data
Pierre-Yves David
pull: prevent race condition in bookmark update when using -B (issue4689)...
r25446 self.remotebookmarks = remotebookmarks
Mads Kiilerich
spelling: fixes from spell checker
r21024 # result of changegroup pulling (used as return code by pull)
Pierre-Yves David
pull: move return code in the pull operation object...
r20898 self.cgresult = None
Pierre-Yves David
pull: use `stepsdone` instead of `todosteps`...
r22937 # list of step already done
self.stepsdone = set()
Gregory Szorc
exchange: record that we attempted to fetch a clone bundle...
r26689 # Whether we attempted a clone from pre-generated bundles.
self.clonebundleattempted = False
Pierre-Yves David
pull: make pulled subset a propertycache of the pull object...
r20487
@util.propertycache
def pulledsubset(self):
"""heads of the set of changeset target by the pull"""
# compute target subset
if self.heads is None:
# We pulled every thing possible
# sync on everything common
Pierre-Yves David
pull: prevent duplicated entry in `op.pulledsubset`...
r20878 c = set(self.common)
ret = list(self.common)
for n in self.rheads:
if n not in c:
ret.append(n)
return ret
Pierre-Yves David
pull: make pulled subset a propertycache of the pull object...
r20487 else:
# We pulled a specific subset
# sync on this subset
return self.heads
Pierre-Yves David
pull: move transaction logic into the pull object...
r20477
Gregory Szorc
exchange: expose bundle2 capabilities on pulloperation...
r26464 @util.propertycache
Gregory Szorc
exchange: expose bundle2 availability on pulloperation...
r26465 def canusebundle2(self):
Pierre-Yves David
bundle2: rename the _canusebundle2 method to _forcebundle1...
r29682 return not _forcebundle1(self)
Gregory Szorc
exchange: expose bundle2 availability on pulloperation...
r26465
@util.propertycache
Gregory Szorc
exchange: expose bundle2 capabilities on pulloperation...
r26464 def remotebundle2caps(self):
return bundle2.bundle2caps(self.remote)
Pierre-Yves David
pull: move transaction logic into the pull object...
r20477 def gettransaction(self):
Eric Sumner
pull: extract transaction logic into separate object...
r23436 # deprecated; talk to trmanager directly
return self.trmanager.transaction()
class transactionmanager(object):
Mads Kiilerich
spelling: fixes from proofreading of spell checker issues
r23543 """An object to manage the life cycle of a transaction
Eric Sumner
pull: extract transaction logic into separate object...
r23436
It creates the transaction on demand and calls the appropriate hooks when
closing the transaction."""
def __init__(self, repo, source, url):
self.repo = repo
self.source = source
self.url = url
self._tr = None
def transaction(self):
"""Return an open transaction object, constructing if necessary"""
if not self._tr:
trname = '%s\n%s' % (self.source, util.hidepassword(self.url))
self._tr = self.repo.transaction(trname)
self._tr.hookargs['source'] = self.source
self._tr.hookargs['url'] = self.url
Pierre-Yves David
pull: move transaction logic into the pull object...
r20477 return self._tr
Eric Sumner
pull: extract transaction logic into separate object...
r23436 def close(self):
Pierre-Yves David
pull: move transaction logic into the pull object...
r20477 """close transaction if created"""
if self._tr is not None:
Pierre-Yves David
exchange: use the postclose API on transaction...
r23222 self._tr.close()
Pierre-Yves David
pull: move transaction logic into the pull object...
r20477
Eric Sumner
pull: extract transaction logic into separate object...
r23436 def release(self):
Pierre-Yves David
pull: move transaction logic into the pull object...
r20477 """release transaction if created"""
if self._tr is not None:
self._tr.release()
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469
Gregory Szorc
exchange: teach pull about requested stream clones...
r26448 def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,
streamclonerequested=None):
Gregory Szorc
exchange: add docstring to pull()...
r26440 """Fetch repository data from a remote.
This is the main function used to retrieve data from a remote repository.
``repo`` is the local repository to clone into.
``remote`` is a peer instance.
``heads`` is an iterable of revisions we want to pull. ``None`` (the
default) means to pull everything from the remote.
``bookmarks`` is an iterable of bookmarks requesting to be pulled. By
default, all remote bookmarks are pulled.
``opargs`` are additional keyword arguments to pass to ``pulloperation``
initialization.
Gregory Szorc
exchange: teach pull about requested stream clones...
r26448 ``streamclonerequested`` is a boolean indicating whether a "streaming
clone" is requested. A "streaming clone" is essentially a raw file copy
of revlogs from the server. This only works when the local repository is
empty. The default value of ``None`` means to respect the server
configuration for preferring stream clones.
Gregory Szorc
exchange: add docstring to pull()...
r26440
Returns the ``pulloperation`` created for this pull.
"""
Pierre-Yves David
pull: allow a generic way to pass parameters to the pull operation...
r25445 if opargs is None:
opargs = {}
pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
Gregory Szorc
exchange: teach pull about requested stream clones...
r26448 streamclonerequested=streamclonerequested, **opargs)
Pierre-Yves David
pull: move `remote` argument into pull object...
r20473 if pullop.remote.local():
missing = set(pullop.remote.requirements) - pullop.repo.supported
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469 if missing:
msg = _("required features are not"
" supported in the destination:"
" %s") % (', '.join(sorted(missing)))
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(msg)
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469
Pierre-Yves David
pull: grab wlock during pull...
r30068 wlock = lock = None
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469 try:
Pierre-Yves David
pull: grab wlock during pull...
r30068 wlock = pullop.repo.wlock()
lock = pullop.repo.lock()
Eric Sumner
pull: extract transaction logic into separate object...
r23436 pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
Gregory Szorc
streamclone: rename and document maybeperformstreamclone()...
r26462 streamclone.maybeperformlegacystreamclone(pullop)
Gregory Szorc
clonebundles: support for seeding clones from pre-generated bundles...
r26623 # This should ideally be in _pullbundle2(). However, it needs to run
# before discovery to avoid extra work.
_maybeapplyclonebundle(pullop)
Pierre-Yves David
pull: put discovery step in its own function...
r20900 _pulldiscovery(pullop)
Gregory Szorc
exchange: expose bundle2 availability on pulloperation...
r26465 if pullop.canusebundle2:
Pierre-Yves David
bundle2: allow pulling changegroups using bundle2...
r20955 _pullbundle2(pullop)
Pierre-Yves David
pull: perform the todostep inside functions handling old way of pulling...
r22653 _pullchangeset(pullop)
_pullphase(pullop)
Pierre-Yves David
pull: retrieve bookmarks before obsmarkers...
r22655 _pullbookmarks(pullop)
Pierre-Yves David
pull: perform the todostep inside functions handling old way of pulling...
r22653 _pullobsolete(pullop)
Eric Sumner
pull: extract transaction logic into separate object...
r23436 pullop.trmanager.close()
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469 finally:
Pierre-Yves David
pull: grab wlock during pull...
r30068 lockmod.release(pullop.trmanager, lock, wlock)
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469
Pierre-Yves David
exchange: have `pull` return the pulloperation object...
r22693 return pullop
Pierre-Yves David
pull: move obsolescence marker exchange in the exchange module...
r20476
Pierre-Yves David
pull: make discovery phase extensible...
r22936 # list of steps to perform discovery before pull
pulldiscoveryorder = []
# Mapping between step name and function
#
# This exists to help extensions wrap steps if necessary
pulldiscoverymapping = {}
def pulldiscovery(stepname):
"""decorator for function performing discovery before pull
The function is added to the step -> function mapping and appended to the
list of steps. Beware that decorated function will be added in order (this
may matter).
You can only use this decorator for a new step, if you want to wrap a step
from an extension, change the pulldiscovery dictionary directly."""
def dec(func):
assert stepname not in pulldiscoverymapping
pulldiscoverymapping[stepname] = func
pulldiscoveryorder.append(stepname)
return func
return dec
Pierre-Yves David
pull: put discovery step in its own function...
r20900 def _pulldiscovery(pullop):
Pierre-Yves David
pull: make discovery phase extensible...
r22936 """Run all discovery steps"""
for stepname in pulldiscoveryorder:
step = pulldiscoverymapping[stepname]
step(pullop)
Pierre-Yves David
pull: only prefetch bookmarks when using bundle1...
r25369 @pulldiscovery('b1:bookmarks')
def _pullbookmarkbundle1(pullop):
"""fetch bookmark data in bundle1 case
If not using bundle2, we have to fetch bookmarks before changeset
discovery to reduce the chance and impact of race conditions."""
Pierre-Yves David
pull: skip pulling remote bookmarks with bundle1 if a value already exist...
r25443 if pullop.remotebookmarks is not None:
return
Gregory Szorc
exchange: expose bundle2 availability on pulloperation...
r26465 if pullop.canusebundle2 and 'listkeys' in pullop.remotebundle2caps:
Pierre-Yves David
bundle2: pull bookmark the old way if no bundle2 listkeys support (issue4701)...
r25479 # all known bundle2 servers now support listkeys, but lets be nice with
# new implementation.
return
pullop.remotebookmarks = pullop.remote.listkeys('bookmarks')
Pierre-Yves David
pull: only prefetch bookmarks when using bundle1...
r25369
Pierre-Yves David
pull: make discovery phase extensible...
r22936 @pulldiscovery('changegroup')
def _pulldiscoverychangegroup(pullop):
Pierre-Yves David
pull: put discovery step in its own function...
r20900 """discovery phase for the pull
Current handle changeset discovery only, will change handle all discovery
at some point."""
Pierre-Yves David
discovery: run discovery on filtered repository...
r23848 tmp = discovery.findcommonincoming(pullop.repo,
Pierre-Yves David
pull: put discovery step in its own function...
r20900 pullop.remote,
heads=pullop.heads,
force=pullop.force)
Pierre-Yves David
discovery: run discovery on filtered repository...
r23848 common, fetch, rheads = tmp
nm = pullop.repo.unfiltered().changelog.nodemap
if fetch and rheads:
# If a remote heads in filtered locally, lets drop it from the unknown
# remote heads and put in back in common.
#
# This is a hackish solution to catch most of "common but locally
# hidden situation". We do not performs discovery on unfiltered
# repository because it end up doing a pathological amount of round
# trip for w huge amount of changeset we do not care about.
#
# If a set of such "common but filtered" changeset exist on the server
# but are not including a remote heads, we'll not be able to detect it,
scommon = set(common)
filteredrheads = []
for n in rheads:
Pierre-Yves David
discovery: properly exclude locally known but filtered heads...
r23975 if n in nm:
if n not in scommon:
common.append(n)
Pierre-Yves David
discovery: run discovery on filtered repository...
r23848 else:
filteredrheads.append(n)
if not filteredrheads:
fetch = []
rheads = filteredrheads
pullop.common = common
pullop.fetch = fetch
pullop.rheads = rheads
Pierre-Yves David
pull: put discovery step in its own function...
r20900
Pierre-Yves David
bundle2: allow pulling changegroups using bundle2...
r20955 def _pullbundle2(pullop):
"""pull data using bundle2
For now, the only supported data are changegroup."""
Pierre-Yves David
bundle2: introduce a ``caps20to10`` function...
r21645 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
Gregory Szorc
exchange: add "streaming all changes" to bundle2 pulling...
r26471
streaming, streamreqs = streamclone.canperformstreamclone(pullop)
Pierre-Yves David
bundle2: allow pulling changegroups using bundle2...
r20955 # pulling changegroup
Pierre-Yves David
pull: use `stepsdone` instead of `todosteps`...
r22937 pullop.stepsdone.add('changegroup')
Durham Goode
bundle2: fix bundle2 pulling all revs on empty pulls...
r21259
kwargs['common'] = pullop.common
kwargs['heads'] = pullop.heads or pullop.rheads
Pierre-Yves David
pull: use the "cg" argument when pulling a bundle2...
r22352 kwargs['cg'] = pullop.fetch
Gregory Szorc
exchange: expose bundle2 capabilities on pulloperation...
r26464 if 'listkeys' in pullop.remotebundle2caps:
Mike Hommey
bundle2: properly request phases during getbundle...
r29064 kwargs['listkeys'] = ['phases']
Pierre-Yves David
pull: skip pulling remote bookmarks with bundle2 if a value already exists...
r25444 if pullop.remotebookmarks is None:
# make sure to always includes bookmark data when migrating
# `hg incoming --bundle` to using this function.
kwargs['listkeys'].append('bookmarks')
Gregory Szorc
exchange: advertise if a clone bundle was attempted...
r26690
# If this is a full pull / clone and the server supports the clone bundles
# feature, tell the server whether we attempted a clone bundle. The
# presence of this flag indicates the client supports clone bundles. This
# will enable the server to treat clients that support clone bundles
# differently from those that don't.
if (pullop.remote.capable('clonebundles')
and pullop.heads is None and list(pullop.common) == [nullid]):
kwargs['cbattempted'] = pullop.clonebundleattempted
Gregory Szorc
exchange: add "streaming all changes" to bundle2 pulling...
r26471 if streaming:
pullop.repo.ui.status(_('streaming all changes\n'))
elif not pullop.fetch:
Pierre-Yves David
exchange: fix bad indentation...
r21258 pullop.repo.ui.status(_("no changes found\n"))
pullop.cgresult = 0
Pierre-Yves David
bundle2: allow pulling changegroups using bundle2...
r20955 else:
if pullop.heads is None and list(pullop.common) == [nullid]:
pullop.repo.ui.status(_("requesting all changes\n"))
Durham Goode
obsolete: add exchange option...
r22953 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
Gregory Szorc
exchange: expose bundle2 capabilities on pulloperation...
r26464 remoteversions = bundle2.obsmarkersversion(pullop.remotebundle2caps)
Pierre-Yves David
bundle2: pull obsmarkers relevant to the pulled set through bundle2...
r22354 if obsolete.commonversion(remoteversions) is not None:
kwargs['obsmarkers'] = True
Pierre-Yves David
pull: use `stepsdone` instead of `todosteps`...
r22937 pullop.stepsdone.add('obsmarkers')
Pierre-Yves David
bundle2: allow extensions to extend the getbundle request...
r21159 _pullbundle2extraprepare(pullop, kwargs)
Pierre-Yves David
bundle2: allow pulling changegroups using bundle2...
r20955 bundle = pullop.remote.getbundle('pull', **kwargs)
try:
op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except error.BundleValueError as exc:
liscju
i18n: translate abort messages...
r29389 raise error.Abort(_('missing support for %s') % exc)
Durham Goode
bundle2: fix bundle2 pulling all revs on empty pulls...
r21259
if pullop.fetch:
Eric Sumner
pullbundle2: extract addchangegroup result combining into its own function...
r23890 results = [cg['return'] for cg in op.records['changegroup']]
pullop.cgresult = changegroup.combineresults(results)
Pierre-Yves David
bundle2: allow pulling changegroups using bundle2...
r20955
Pierre-Yves David
pull: when remote supports it, pull phase data alongside changesets...
r21658 # processing phases change
for namespace, value in op.records['listkeys']:
if namespace == 'phases':
_pullapplyphases(pullop, value)
Pierre-Yves David
pull: retrieve bookmarks through bundle2...
r22656 # processing bookmark update
for namespace, value in op.records['listkeys']:
if namespace == 'bookmarks':
pullop.remotebookmarks = value
Pierre-Yves David
pull: skip pulling remote bookmarks with bundle2 if a value already exists...
r25444
# bookmark data were either already there or pulled in the bundle
if pullop.remotebookmarks is not None:
_pullbookmarks(pullop)
Pierre-Yves David
pull: retrieve bookmarks through bundle2...
r22656
Pierre-Yves David
bundle2: allow extensions to extend the getbundle request...
r21159 def _pullbundle2extraprepare(pullop, kwargs):
"""hook function so that extensions can extend the getbundle call"""
pass
Pierre-Yves David
pull: move changeset pulling in its own function...
r20489 def _pullchangeset(pullop):
"""pull changeset from unbundle into the local repo"""
# We delay the open of the transaction as late as possible so we
# don't open transaction for nothing or you break future useful
# rollback call
Pierre-Yves David
pull: use `stepsdone` instead of `todosteps`...
r22937 if 'changegroup' in pullop.stepsdone:
Pierre-Yves David
pull: perform the todostep inside functions handling old way of pulling...
r22653 return
Pierre-Yves David
pull: use `stepsdone` instead of `todosteps`...
r22937 pullop.stepsdone.add('changegroup')
Pierre-Yves David
pull: move the cgresult logic in _pullchangeset...
r20899 if not pullop.fetch:
Mike Edgar
exchange: fix indentation in _pullchangeset
r23217 pullop.repo.ui.status(_("no changes found\n"))
pullop.cgresult = 0
return
Pierre-Yves David
pull: move changeset pulling in its own function...
r20489 pullop.gettransaction()
if pullop.heads is None and list(pullop.common) == [nullid]:
pullop.repo.ui.status(_("requesting all changes\n"))
elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
# issue1320, avoid a race if remote changed after discovery
pullop.heads = pullop.rheads
if pullop.remote.capable('getbundle'):
# TODO: get bundlecaps from remote
cg = pullop.remote.getbundle('pull', common=pullop.common,
heads=pullop.heads or pullop.rheads)
elif pullop.heads is None:
cg = pullop.remote.changegroup(pullop.fetch, 'pull')
elif not pullop.remote.capable('changegroupsubset'):
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_("partial pull cannot be done because "
Pierre-Yves David
exchange: fix indentation level
r21554 "other repository doesn't support "
"changegroupsubset."))
Pierre-Yves David
pull: move changeset pulling in its own function...
r20489 else:
cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
Augie Fackler
exchange: use cg?unpacker.apply() instead of changegroup.addchangegroup()
r26700 pullop.cgresult = cg.apply(pullop.repo, 'pull', pullop.remote.url())
Pierre-Yves David
pull: move changeset pulling in its own function...
r20489
Pierre-Yves David
pull: move phases synchronisation in its own function...
r20486 def _pullphase(pullop):
# Get remote phases data from remote
Pierre-Yves David
pull: use `stepsdone` instead of `todosteps`...
r22937 if 'phases' in pullop.stepsdone:
Pierre-Yves David
pull: perform the todostep inside functions handling old way of pulling...
r22653 return
Pierre-Yves David
pull: split remote phases retrieval from actual application...
r21654 remotephases = pullop.remote.listkeys('phases')
_pullapplyphases(pullop, remotephases)
def _pullapplyphases(pullop, remotephases):
"""apply phase movement from observed remote state"""
Pierre-Yves David
pull: use `stepsdone` instead of `todosteps`...
r22937 if 'phases' in pullop.stepsdone:
return
pullop.stepsdone.add('phases')
Pierre-Yves David
pull: move phases synchronisation in its own function...
r20486 publishing = bool(remotephases.get('publishing', False))
if remotephases and not publishing:
# remote is new and unpublishing
pheads, _dr = phases.analyzeremotephases(pullop.repo,
pullop.pulledsubset,
remotephases)
Pierre-Yves David
pull: pre-filter remote phases before moving local ones...
r22068 dheads = pullop.pulledsubset
Pierre-Yves David
pull: move phases synchronisation in its own function...
r20486 else:
# Remote is old or publishing all common changesets
# should be seen as public
Pierre-Yves David
pull: pre-filter remote phases before moving local ones...
r22068 pheads = pullop.pulledsubset
dheads = []
unfi = pullop.repo.unfiltered()
phase = unfi._phasecache.phase
rev = unfi.changelog.nodemap.get
public = phases.public
draft = phases.draft
# exclude changesets already public locally and update the others
pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
if pheads:
Pierre-Yves David
phase: add a transaction argument to advanceboundary...
r22069 tr = pullop.gettransaction()
phases.advanceboundary(pullop.repo, tr, public, pheads)
Pierre-Yves David
pull: pre-filter remote phases before moving local ones...
r22068
# exclude changesets already draft locally and update the others
dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
if dheads:
Pierre-Yves David
phase: add a transaction argument to advanceboundary...
r22069 tr = pullop.gettransaction()
phases.advanceboundary(pullop.repo, tr, draft, dheads)
Pierre-Yves David
pull: move phases synchronisation in its own function...
r20486
Pierre-Yves David
pull: move bookmark pulling into its own function...
r22654 def _pullbookmarks(pullop):
"""process the remote bookmark information to update the local one"""
Pierre-Yves David
pull: use `stepsdone` instead of `todosteps`...
r22937 if 'bookmarks' in pullop.stepsdone:
Pierre-Yves David
pull: move bookmark pulling into its own function...
r22654 return
Pierre-Yves David
pull: use `stepsdone` instead of `todosteps`...
r22937 pullop.stepsdone.add('bookmarks')
Pierre-Yves David
pull: move bookmark pulling into its own function...
r22654 repo = pullop.repo
remotebookmarks = pullop.remotebookmarks
bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
Pierre-Yves David
pull: gather explicit bookmark pulls with bookmark updates...
r22658 pullop.remote.url(),
Pierre-Yves David
pull: perform bookmark updates in the transaction
r22666 pullop.gettransaction,
Pierre-Yves David
pull: gather explicit bookmark pulls with bookmark updates...
r22658 explicit=pullop.explicitbookmarks)
Pierre-Yves David
pull: move bookmark pulling into its own function...
r22654
Pierre-Yves David
push: feed pulloperation object to _pullobsolete function...
r20478 def _pullobsolete(pullop):
Pierre-Yves David
pull: move obsolescence marker exchange in the exchange module...
r20476 """utility function to pull obsolete markers from a remote
The `gettransaction` is function that return the pull transaction, creating
one if necessary. We return the transaction to inform the calling code that
a new transaction have been created (when applicable).
Exists mostly to allow overriding for experimentation purpose"""
Pierre-Yves David
pull: use `stepsdone` instead of `todosteps`...
r22937 if 'obsmarkers' in pullop.stepsdone:
Pierre-Yves David
pull: perform the todostep inside functions handling old way of pulling...
r22653 return
Pierre-Yves David
pull: use `stepsdone` instead of `todosteps`...
r22937 pullop.stepsdone.add('obsmarkers')
Pierre-Yves David
pull: move obsolescence marker exchange in the exchange module...
r20476 tr = None
Durham Goode
obsolete: add exchange option...
r22953 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
Pierre-Yves David
push: feed pulloperation object to _pullobsolete function...
r20478 pullop.repo.ui.debug('fetching remote obsolete markers\n')
remoteobs = pullop.remote.listkeys('obsolete')
Pierre-Yves David
pull: move obsolescence marker exchange in the exchange module...
r20476 if 'dump0' in remoteobs:
Pierre-Yves David
push: feed pulloperation object to _pullobsolete function...
r20478 tr = pullop.gettransaction()
Matt Mackall
pull: make a single call to obsstore.add (issue5006)...
r27558 markers = []
Pierre-Yves David
pull: move obsolescence marker exchange in the exchange module...
r20476 for key in sorted(remoteobs, reverse=True):
if key.startswith('dump'):
data = base85.b85decode(remoteobs[key])
Matt Mackall
pull: make a single call to obsstore.add (issue5006)...
r27558 version, newmarks = obsolete._readmarkers(data)
markers += newmarks
if markers:
pullop.repo.obsstore.add(tr, markers)
Pierre-Yves David
push: feed pulloperation object to _pullobsolete function...
r20478 pullop.repo.invalidatevolatilesets()
Pierre-Yves David
pull: move obsolescence marker exchange in the exchange module...
r20476 return tr
Pierre-Yves David
bundle2: introduce a ``caps20to10`` function...
r21645 def caps20to10(repo):
"""return a set with appropriate options to use bundle20 during getbundle"""
Pierre-Yves David
bundle2: rename format, parts and config to final names...
r24686 caps = set(['HG20'])
Pierre-Yves David
bundle2: introduce a `getrepocaps` to retrieve the bundle2 caps of a repo...
r22342 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
timeless
pycompat: switch to util.urlreq/util.urlerr for py3 compat
r28883 caps.add('bundle2=' + urlreq.quote(capsblob))
Pierre-Yves David
bundle2: introduce a ``caps20to10`` function...
r21645 return caps
Mike Hommey
bundle2: separate bundle10 and bundle2 cases in getbundle()...
r22542 # List of names of steps to perform for a bundle2 for getbundle, order matters.
getbundle2partsorder = []
# Mapping between step name and function
#
# This exists to help extensions wrap steps if necessary
getbundle2partsmapping = {}
Pierre-Yves David
bundle2: add an 'idx' argument to the 'getbundle2partsgenerator'...
r24732 def getbundle2partsgenerator(stepname, idx=None):
Mike Hommey
bundle2: separate bundle10 and bundle2 cases in getbundle()...
r22542 """decorator for function generating bundle2 part for getbundle
The function is added to the step -> function mapping and appended to the
list of steps. Beware that decorated functions will be added in order
(this may matter).
You can only use this decorator for new steps, if you want to wrap a step
from an extension, attack the getbundle2partsmapping dictionary directly."""
def dec(func):
assert stepname not in getbundle2partsmapping
getbundle2partsmapping[stepname] = func
Pierre-Yves David
bundle2: add an 'idx' argument to the 'getbundle2partsgenerator'...
r24732 if idx is None:
getbundle2partsorder.append(stepname)
else:
getbundle2partsorder.insert(idx, stepname)
Mike Hommey
bundle2: separate bundle10 and bundle2 cases in getbundle()...
r22542 return func
return dec
Gregory Szorc
exchange: standalone function to determine if bundle2 is requested...
r27244 def bundle2requested(bundlecaps):
if bundlecaps is not None:
return any(cap.startswith('HG2') for cap in bundlecaps)
return False
Gregory Szorc
exchange: refactor APIs to obtain bundle data (API)...
r30187 def getbundlechunks(repo, source, heads=None, common=None, bundlecaps=None,
**kwargs):
"""Return chunks constituting a bundle's raw data.
Pierre-Yves David
bundle2: add an exchange.getbundle function...
r20954
Pierre-Yves David
bundle2: rename format, parts and config to final names...
r24686 Could be a bundle HG10 or a bundle HG20 depending on bundlecaps
Gregory Szorc
exchange: refactor APIs to obtain bundle data (API)...
r30187 passed.
Pierre-Yves David
bundle2: add an exchange.getbundle function...
r20954
Gregory Szorc
exchange: refactor APIs to obtain bundle data (API)...
r30187 Returns an iterator over raw chunks (of varying sizes).
Pierre-Yves David
bundle2: add an exchange.getbundle function...
r20954 """
Gregory Szorc
exchange: standalone function to determine if bundle2 is requested...
r27244 usebundle2 = bundle2requested(bundlecaps)
Mike Hommey
bundle2: separate bundle10 and bundle2 cases in getbundle()...
r22542 # bundle10 case
Pierre-Yves David
bundle2: detect bundle2 stream/request on /HG2./ instead of /HG2Y/...
r24649 if not usebundle2:
Mike Hommey
bundle2: separate bundle10 and bundle2 cases in getbundle()...
r22542 if bundlecaps and not kwargs.get('cg', True):
raise ValueError(_('request for bundle10 must include changegroup'))
Pierre-Yves David
getbundle: raise error if extra arguments are provided for bundle10...
r21656 if kwargs:
raise ValueError(_('unsupported getbundle arguments: %s')
% ', '.join(sorted(kwargs.keys())))
Pierre-Yves David
computeoutgoing: move the function from 'changegroup' to 'exchange'...
r29808 outgoing = _computeoutgoing(repo, heads, common)
Gregory Szorc
exchange: refactor APIs to obtain bundle data (API)...
r30187 bundler = changegroup.getbundler('01', repo, bundlecaps)
return changegroup.getsubsetraw(repo, outgoing, bundler, source)
Mike Hommey
bundle2: separate bundle10 and bundle2 cases in getbundle()...
r22542
# bundle20 case
Pierre-Yves David
bundle2: transmit capabilities to getbundle during pull...
r21143 b2caps = {}
for bcaps in bundlecaps:
if bcaps.startswith('bundle2='):
timeless
pycompat: switch to util.urlreq/util.urlerr for py3 compat
r28883 blob = urlreq.unquote(bcaps[len('bundle2='):])
Pierre-Yves David
bundle2: transmit capabilities to getbundle during pull...
r21143 b2caps.update(bundle2.decodecaps(blob))
bundler = bundle2.bundle20(repo.ui, b2caps)
Mike Hommey
bundle2: separate bundle10 and bundle2 cases in getbundle()...
r22542
Mike Edgar
exchange: prepare kwargs for bundle2 part generation exactly once
r23218 kwargs['heads'] = heads
kwargs['common'] = common
Mike Hommey
bundle2: separate bundle10 and bundle2 cases in getbundle()...
r22542 for name in getbundle2partsorder:
func = getbundle2partsmapping[name]
Mike Hommey
bundle2: remove heads and common arguments to getbundle parts generators
r22543 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
**kwargs)
Mike Hommey
bundle2: separate bundle10 and bundle2 cases in getbundle()...
r22542
Gregory Szorc
exchange: refactor APIs to obtain bundle data (API)...
r30187 return bundler.getchunks()
Mike Hommey
bundle2: separate bundle10 and bundle2 cases in getbundle()...
r22542
@getbundle2partsgenerator('changegroup')
Mike Hommey
bundle2: remove heads and common arguments to getbundle parts generators
r22543 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
b2caps=None, heads=None, common=None, **kwargs):
Mike Hommey
bundle2: separate bundle10 and bundle2 cases in getbundle()...
r22542 """add a changegroup part to the requested bundle"""
cg = None
if kwargs.get('cg', True):
# build changegroup bundle here.
Martin von Zweigbergk
exchange: get rid of "getcgkwargs" variable...
r28667 version = '01'
Pierre-Yves David
bundle2: rename format, parts and config to final names...
r24686 cgversions = b2caps.get('changegroup')
Pierre-Yves David
getbundle: have a single getchangegroupraw call site...
r25503 if cgversions: # 3.1 and 3.2 ship with an empty value
Martin von Zweigbergk
changegroup: hide packermap behind methods...
r27751 cgversions = [v for v in cgversions
Martin von Zweigbergk
changegroup: fix pulling to treemanifest repo from flat repo (issue5066)...
r27953 if v in changegroup.supportedoutgoingversions(repo)]
Pierre-Yves David
getbundle: send highest changegroup format supported by both side...
r23179 if not cgversions:
raise ValueError(_('no common changegroup version'))
Martin von Zweigbergk
exchange: get rid of "getcgkwargs" variable...
r28667 version = max(cgversions)
Pierre-Yves David
computeoutgoing: move the function from 'changegroup' to 'exchange'...
r29808 outgoing = _computeoutgoing(repo, heads, common)
Pierre-Yves David
exchange: expand usage of getchangegroupraw...
r25504 cg = changegroup.getlocalchangegroupraw(repo, source, outgoing,
bundlecaps=bundlecaps,
Martin von Zweigbergk
exchange: get rid of "getcgkwargs" variable...
r28667 version=version)
Mike Hommey
bundle2: separate bundle10 and bundle2 cases in getbundle()...
r22542
Durham Goode
bundle2: fix bundle2 pulling all revs on empty pulls...
r21259 if cg:
Pierre-Yves David
bundle2: rename format, parts and config to final names...
r24686 part = bundler.newpart('changegroup', data=cg)
Martin von Zweigbergk
exchange: get rid of "getcgkwargs" variable...
r28667 if cgversions:
Pierre-Yves David
getbundle: send highest changegroup format supported by both side...
r23179 part.addparam('version', version)
Pierre-Yves David
getbundle: add data about the number of changesets bundled...
r25516 part.addparam('nbchanges', str(len(outgoing.missing)), mandatory=False)
Martin von Zweigbergk
treemanifests: set bundle2 part parameter indicating treemanifest...
r27734 if 'treemanifest' in repo.requirements:
part.addparam('treemanifest', '1')
Mike Hommey
bundle2: separate bundle10 and bundle2 cases in getbundle()...
r22542
@getbundle2partsgenerator('listkeys')
Mike Hommey
bundle2: remove heads and common arguments to getbundle parts generators
r22543 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
b2caps=None, **kwargs):
Mike Hommey
bundle2: separate bundle10 and bundle2 cases in getbundle()...
r22542 """add parts containing listkeys namespaces to the requested bundle"""
Pierre-Yves David
getbundle: support of listkeys argument when bundle2 is used...
r21657 listkeys = kwargs.get('listkeys', ())
for namespace in listkeys:
Pierre-Yves David
bundle2: rename format, parts and config to final names...
r24686 part = bundler.newpart('listkeys')
Pierre-Yves David
getbundle: support of listkeys argument when bundle2 is used...
r21657 part.addparam('namespace', namespace)
keys = repo.listkeys(namespace).items()
part.data = pushkey.encodekeys(keys)
Pierre-Yves David
unbundle: extract checkheads in its own function...
r20967
Mike Hommey
bundle2: separate bundle10 and bundle2 cases in getbundle()...
r22542 @getbundle2partsgenerator('obsmarkers')
Mike Hommey
bundle2: remove heads and common arguments to getbundle parts generators
r22543 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
b2caps=None, heads=None, **kwargs):
Mike Hommey
bundle2: pass b2caps down to functions adding bundle2 parts for getbundle
r22541 """add an obsolescence markers part to the requested bundle"""
Pierre-Yves David
getbundle: add `obsmarkers` argument to getbundle...
r22353 if kwargs.get('obsmarkers', False):
if heads is None:
heads = repo.heads()
subset = [c.node() for c in repo.set('::%ln', heads)]
markers = repo.obsstore.relevantmarkers(subset)
Pierre-Yves David
obsolete: sort obsmarkers during exchange...
r25118 markers = sorted(markers)
Pierre-Yves David
getbundle: add `obsmarkers` argument to getbundle...
r22353 buildobsmarkerspart(bundler, markers)
Gregory Szorc
exchange: support transferring .hgtags fnodes mapping...
r25402 @getbundle2partsgenerator('hgtagsfnodes')
def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
b2caps=None, heads=None, common=None,
**kwargs):
"""Transfer the .hgtags filenodes mapping.
Only values for heads in this bundle will be transferred.
The part data consists of pairs of 20 byte changeset node and .hgtags
filenodes raw values.
"""
# Don't send unless:
# - changeset are being exchanged,
# - the client supports it.
if not (kwargs.get('cg', True) and 'hgtagsfnodes' in b2caps):
return
Pierre-Yves David
computeoutgoing: move the function from 'changegroup' to 'exchange'...
r29808 outgoing = _computeoutgoing(repo, heads, common)
Gregory Szorc
exchange: support transferring .hgtags fnodes mapping...
r25402
if not outgoing.missingheads:
return
cache = tags.hgtagsfnodescache(repo.unfiltered())
chunks = []
# .hgtags fnodes are only relevant for head changesets. While we could
# transfer values for all known nodes, there will likely be little to
# no benefit.
#
# We don't bother using a generator to produce output data because
# a) we only have 40 bytes per head and even esoteric numbers of heads
# consume little memory (1M heads is 40MB) b) we don't want to send the
# part if we don't have entries and knowing if we have entries requires
# cache lookups.
for node in outgoing.missingheads:
# Don't compute missing, as this may slow down serving.
fnode = cache.getfnode(node, computemissing=False)
if fnode is not None:
chunks.extend([node, fnode])
if chunks:
bundler.newpart('hgtagsfnodes', data=''.join(chunks))
Pierre-Yves David
unbundle: extract checkheads in its own function...
r20967 def check_heads(repo, their_heads, context):
"""check if the heads of a repo have been modified
Used by peer for unbundling.
"""
heads = repo.heads()
Augie Fackler
cleanup: replace uses of util.(md5|sha1|sha256|sha512) with hashlib.\1...
r29341 heads_hash = hashlib.sha1(''.join(sorted(heads))).digest()
Pierre-Yves David
unbundle: extract checkheads in its own function...
r20967 if not (their_heads == ['force'] or their_heads == heads or
their_heads == ['hashed', heads_hash]):
# someone else committed/pushed/unbundled while we
# were transferring data
Pierre-Yves David
bundle2: fix raising errors during heads checking...
r21184 raise error.PushRaced('repository changed while %s - '
'please try again' % context)
Pierre-Yves David
unbundle: extract the core logic in another function...
r20968
def unbundle(repo, cg, heads, source, url):
"""Apply a bundle to a repo.
this function makes sure the repo is locked during the application and have
Mads Kiilerich
spelling: fixes from spell checker
r21024 mechanism to check that no push race occurred between the creation of the
Pierre-Yves David
unbundle: extract the core logic in another function...
r20968 bundle and its application.
If the push was raced as PushRaced exception is raised."""
r = 0
Pierre-Yves David
bundle2: allow using bundle2 for push...
r21061 # need a transaction when processing a bundle2 stream
Durham Goode
bundle2: allow lazily acquiring the lock...
r26566 # [wlock, lock, tr] - needs to be an array so nested functions can modify it
lockandtr = [None, None, None]
Pierre-Yves David
bundle2: capture transaction rollback message output (issue4614)...
r24847 recordout = None
Pierre-Yves David
bundle2: disable ouput capture unless we use http (issue4613 issue4615)...
r24878 # quick fix for output mismatch with bundle2 in 3.4
captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture',
False)
Pierre-Yves David
bundle2: stop capturing output for ssh again...
r25423 if url.startswith('remote:http:') or url.startswith('remote:https:'):
Pierre-Yves David
bundle2: disable ouput capture unless we use http (issue4613 issue4615)...
r24878 captureoutput = True
Pierre-Yves David
unbundle: extract the core logic in another function...
r20968 try:
check_heads(repo, heads, 'uploading changes')
# push can proceed
Pierre-Yves David
bundle2: allow using bundle2 for push...
r21061 if util.safehasattr(cg, 'params'):
Pierre-Yves David
bundle2: store the salvaged output on the exception object...
r24795 r = None
Pierre-Yves David
bundle2: gracefully handle hook abort...
r21187 try:
Durham Goode
bundle2: allow lazily acquiring the lock...
r26566 def gettransaction():
if not lockandtr[2]:
lockandtr[0] = repo.wlock()
lockandtr[1] = repo.lock()
lockandtr[2] = repo.transaction(source)
lockandtr[2].hookargs['source'] = source
lockandtr[2].hookargs['url'] = url
lockandtr[2].hookargs['bundle2'] = '1'
return lockandtr[2]
# Do greedy locking by default until we're satisfied with lazy
# locking.
if not repo.ui.configbool('experimental', 'bundle2lazylocking'):
gettransaction()
op = bundle2.bundleoperation(repo, gettransaction,
Pierre-Yves David
bundle2: disable ouput capture unless we use http (issue4613 issue4615)...
r24878 captureoutput=captureoutput)
Pierre-Yves David
bundle2: also save output when error happens during part processing...
r24851 try:
Martin von Zweigbergk
exchange: fix dead assignment...
r25896 op = bundle2.processbundle(repo, cg, op=op)
Pierre-Yves David
bundle2: also save output when error happens during part processing...
r24851 finally:
r = op.reply
Pierre-Yves David
bundle2: disable ouput capture unless we use http (issue4613 issue4615)...
r24878 if captureoutput and r is not None:
Pierre-Yves David
bundle2: also save output when error happens during part processing...
r24851 repo.ui.pushbuffer(error=True, subproc=True)
def recordout(output):
r.newpart('output', data=output, mandatory=False)
Durham Goode
bundle2: allow lazily acquiring the lock...
r26566 if lockandtr[2] is not None:
lockandtr[2].close()
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except BaseException as exc:
Pierre-Yves David
bundle2: gracefully handle hook abort...
r21187 exc.duringunbundle2 = True
Pierre-Yves David
bundle2: disable ouput capture unless we use http (issue4613 issue4615)...
r24878 if captureoutput and r is not None:
Pierre-Yves David
bundle2: capture transaction rollback message output (issue4614)...
r24847 parts = exc._bundle2salvagedoutput = r.salvageoutput()
def recordout(output):
part = bundle2.bundlepart('output', data=output,
mandatory=False)
parts.append(part)
Pierre-Yves David
bundle2: gracefully handle hook abort...
r21187 raise
Pierre-Yves David
bundle2: allow using bundle2 for push...
r21061 else:
Durham Goode
bundle2: allow lazily acquiring the lock...
r26566 lockandtr[1] = repo.lock()
Augie Fackler
exchange: use cg?unpacker.apply() instead of changegroup.addchangegroup()
r26700 r = cg.apply(repo, source, url)
Pierre-Yves David
unbundle: extract the core logic in another function...
r20968 finally:
Durham Goode
bundle2: allow lazily acquiring the lock...
r26566 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0])
Pierre-Yves David
bundle2: capture transaction rollback message output (issue4614)...
r24847 if recordout is not None:
recordout(repo.ui.popbuffer())
Pierre-Yves David
unbundle: extract the core logic in another function...
r20968 return r
Gregory Szorc
clonebundles: support for seeding clones from pre-generated bundles...
r26623
def _maybeapplyclonebundle(pullop):
"""Apply a clone bundle from a remote, if possible."""
repo = pullop.repo
remote = pullop.remote
Gregory Szorc
exchange: make clone bundles non-experimental and enabled by default...
r27738 if not repo.ui.configbool('ui', 'clonebundles', True):
Gregory Szorc
clonebundles: support for seeding clones from pre-generated bundles...
r26623 return
Gregory Szorc
exchange: do not attempt clone bundle if local repo is non-empty (issue4932)
r26855 # Only run if local repo is empty.
if len(repo):
return
Gregory Szorc
clonebundles: support for seeding clones from pre-generated bundles...
r26623 if pullop.heads:
return
if not remote.capable('clonebundles'):
return
res = remote._call('clonebundles')
Gregory Szorc
exchange: record that we attempted to fetch a clone bundle...
r26689
# If we call the wire protocol command, that's good enough to record the
# attempt.
pullop.clonebundleattempted = True
Gregory Szorc
exchange: extract bundle specification components into own attributes...
r26647 entries = parseclonebundlesmanifest(repo, res)
Gregory Szorc
clonebundles: support for seeding clones from pre-generated bundles...
r26623 if not entries:
repo.ui.note(_('no clone bundles available on remote; '
'falling back to regular clone\n'))
return
Gregory Szorc
clonebundles: filter on bundle specification...
r26644 entries = filterclonebundleentries(repo, entries)
if not entries:
# There is a thundering herd concern here. However, if a server
# operator doesn't advertise bundles appropriate for its clients,
# they deserve what's coming. Furthermore, from a client's
# perspective, no automatic fallback would mean not being able to
# clone!
repo.ui.warn(_('no compatible clone bundles available on server; '
'falling back to regular clone\n'))
repo.ui.warn(_('(you may want to report this to the server '
'operator)\n'))
return
Gregory Szorc
exchange: support sorting URLs by client-side preferences...
r26648 entries = sortclonebundleentries(repo.ui, entries)
Gregory Szorc
clonebundles: filter on bundle specification...
r26644
Gregory Szorc
clonebundles: support for seeding clones from pre-generated bundles...
r26623 url = entries[0]['URL']
repo.ui.status(_('applying clone bundle from %s\n') % url)
if trypullbundlefromurl(repo.ui, repo, url):
repo.ui.status(_('finished applying clone bundle\n'))
# Bundle failed.
#
# We abort by default to avoid the thundering herd of
# clients flooding a server that was expecting expensive
# clone load to be offloaded.
elif repo.ui.configbool('ui', 'clonebundlefallback', False):
repo.ui.warn(_('falling back to normal clone\n'))
else:
raise error.Abort(_('error applying bundle'),
Gregory Szorc
exchange: provide hint on how to disable clone bundles...
r26688 hint=_('if this error persists, consider contacting '
'the server operator or disable clone '
'bundles via '
Gregory Szorc
exchange: make clone bundles non-experimental and enabled by default...
r27738 '"--config ui.clonebundles=false"'))
Gregory Szorc
clonebundles: support for seeding clones from pre-generated bundles...
r26623
Gregory Szorc
exchange: extract bundle specification components into own attributes...
r26647 def parseclonebundlesmanifest(repo, s):
Gregory Szorc
clonebundles: support for seeding clones from pre-generated bundles...
r26623 """Parses the raw text of a clone bundles manifest.
Returns a list of dicts. The dicts have a ``URL`` key corresponding
to the URL and other keys are the attributes for the entry.
"""
m = []
for line in s.splitlines():
fields = line.split()
if not fields:
continue
attrs = {'URL': fields[0]}
for rawattr in fields[1:]:
key, value = rawattr.split('=', 1)
timeless
pycompat: switch to util.urlreq/util.urlerr for py3 compat
r28883 key = urlreq.unquote(key)
value = urlreq.unquote(value)
Gregory Szorc
exchange: extract bundle specification components into own attributes...
r26647 attrs[key] = value
# Parse BUNDLESPEC into components. This makes client-side
# preferences easier to specify since you can prefer a single
# component of the BUNDLESPEC.
if key == 'BUNDLESPEC':
try:
Gregory Szorc
exchange: support parameters in bundle specification strings...
r26759 comp, version, params = parsebundlespec(repo, value,
externalnames=True)
Gregory Szorc
exchange: extract bundle specification components into own attributes...
r26647 attrs['COMPRESSION'] = comp
attrs['VERSION'] = version
except error.InvalidBundleSpecification:
pass
except error.UnsupportedBundleSpecification:
pass
Gregory Szorc
clonebundles: support for seeding clones from pre-generated bundles...
r26623
m.append(attrs)
return m
Gregory Szorc
clonebundles: filter on bundle specification...
r26644 def filterclonebundleentries(repo, entries):
Gregory Szorc
exchange: document filterclonebundleentries
r26687 """Remove incompatible clone bundle manifest entries.
Accepts a list of entries parsed with ``parseclonebundlesmanifest``
and returns a new list consisting of only the entries that this client
should be able to apply.
There is no guarantee we'll be able to apply all returned entries because
the metadata we use to filter on may be missing or wrong.
"""
Gregory Szorc
clonebundles: filter on bundle specification...
r26644 newentries = []
for entry in entries:
spec = entry.get('BUNDLESPEC')
if spec:
try:
parsebundlespec(repo, spec, strict=True)
except error.InvalidBundleSpecification as e:
repo.ui.debug(str(e) + '\n')
continue
except error.UnsupportedBundleSpecification as e:
repo.ui.debug('filtering %s because unsupported bundle '
'spec: %s\n' % (entry['URL'], str(e)))
continue
Gregory Szorc
clonebundles: filter on SNI requirement...
r26645 if 'REQUIRESNI' in entry and not sslutil.hassni:
repo.ui.debug('filtering %s because SNI not supported\n' %
entry['URL'])
continue
Gregory Szorc
clonebundles: filter on bundle specification...
r26644 newentries.append(entry)
return newentries
Gregory Szorc
exchange: support sorting URLs by client-side preferences...
r26648 def sortclonebundleentries(ui, entries):
Gregory Szorc
exchange: make clonebundleprefers non-experimental...
r27737 prefers = ui.configlist('ui', 'clonebundleprefers', default=[])
Gregory Szorc
exchange: support sorting URLs by client-side preferences...
r26648 if not prefers:
return list(entries)
prefers = [p.split('=', 1) for p in prefers]
# Our sort function.
def compareentry(a, b):
for prefkey, prefvalue in prefers:
avalue = a.get(prefkey)
bvalue = b.get(prefkey)
# Special case for b missing attribute and a matches exactly.
if avalue is not None and bvalue is None and avalue == prefvalue:
return -1
# Special case for a missing attribute and b matches exactly.
if bvalue is not None and avalue is None and bvalue == prefvalue:
return 1
# We can't compare unless attribute present on both.
if avalue is None or bvalue is None:
continue
# Same values should fall back to next attribute.
if avalue == bvalue:
continue
# Exact matches come first.
if avalue == prefvalue:
return -1
if bvalue == prefvalue:
return 1
# Fall back to next attribute.
continue
# If we got here we couldn't sort by attributes and prefers. Fall
# back to index order.
return 0
return sorted(entries, cmp=compareentry)
Gregory Szorc
clonebundles: support for seeding clones from pre-generated bundles...
r26623 def trypullbundlefromurl(ui, repo, url):
"""Attempt to apply a bundle from a URL."""
lock = repo.lock()
try:
tr = repo.transaction('bundleurl')
try:
try:
fh = urlmod.open(ui, url)
cg = readbundle(ui, fh, 'stream')
Gregory Szorc
clonebundle: support bundle2...
r26643
if isinstance(cg, bundle2.unbundle20):
bundle2.processbundle(repo, cg, lambda: tr)
Gregory Szorc
exchange: support streaming clone bundles in clone bundles...
r26761 elif isinstance(cg, streamclone.streamcloneapplier):
cg.apply(repo)
Gregory Szorc
clonebundle: support bundle2...
r26643 else:
Augie Fackler
exchange: use cg?unpacker.apply() instead of changegroup.addchangegroup()
r26700 cg.apply(repo, 'clonebundles', url)
Gregory Szorc
clonebundles: support for seeding clones from pre-generated bundles...
r26623 tr.close()
return True
timeless
pycompat: switch to util.urlreq/util.urlerr for py3 compat
r28883 except urlerr.httperror as e:
Gregory Szorc
clonebundles: support for seeding clones from pre-generated bundles...
r26623 ui.warn(_('HTTP error fetching bundle: %s\n') % str(e))
timeless
pycompat: switch to util.urlreq/util.urlerr for py3 compat
r28883 except urlerr.urlerror as e:
Gregory Szorc
exchange: don't print error codes after clone bundle failure...
r26732 ui.warn(_('error fetching bundle: %s\n') % e.reason[1])
Gregory Szorc
clonebundles: support for seeding clones from pre-generated bundles...
r26623
return False
finally:
tr.release()
finally:
lock.release()