##// END OF EJS Templates
pull: introduce a pulloperation object...
pull: introduce a pulloperation object This object will hold all data and state gathered through the pull. This will allow us to split the long function into multiple small one. Smaller function will be easier to maintains and wrap. The idea is to blindly store all information related to the pull in this object so that each step and extension can use them if necessary. We start by putting the `repo` variable in the object. More migration in other function.

File last commit:

r20472:b97a453b default
r20472:b97a453b default
Show More
exchange.py
473 lines | 18.4 KiB | text/x-python | PythonLexer
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 # exchange.py - utily to exchange data between repo.
#
# 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.
from i18n import _
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469 from node import hex, nullid
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 import errno
import util, scmutil, changegroup
import discovery, phases, obsolete, bookmarks
Pierre-Yves David
push: introduce a pushoperation object...
r20346
class pushoperation(object):
"""A object that represent a single push operation
It purpose is to carry push related state and very common operation.
A new should be created at the begining of each push and discarded
afterward.
"""
Pierre-Yves David
push: move `newbranch` argument into the push object...
r20351 def __init__(self, repo, remote, force=False, revs=None, newbranch=False):
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: 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: move push return value in the push object...
r20439 # Integer version of the push result
# - 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()
self.ret = None
Pierre-Yves David
push: move outgoing object in the push object...
r20440 # discover.outgoing object (contains common and outgoin data)
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: move `commonheads` into the push object...
r20467 # set of all heads common after changeset bundle push
self.commonheads = None
Pierre-Yves David
push: introduce a pushoperation object...
r20346
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 def push(repo, remote, force=False, revs=None, newbranch=False):
'''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()
'''
Pierre-Yves David
push: move `newbranch` argument into the push object...
r20351 pushop = pushoperation(repo, remote, force, revs, newbranch)
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)))
raise util.Abort(msg)
# 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
exchange: extract push function from localrepo...
r20345 raise util.Abort(_("destination does not support push"))
# get local lock as we might write phase data
locallock = None
try:
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
Pierre-Yves David
exchange: extract push function from localrepo...
r20345 except IOError, 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:
Pierre-Yves David
push: move `revs` argument into the push object...
r20350 pushop.repo.checkpush(pushop.force, pushop.revs)
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
push: move outgoing check logic in its own function...
r20465 if _pushcheckoutgoing(pushop):
Pierre-Yves David
push: move changeset push logic in its own function...
r20463 _pushchangeset(pushop)
Pierre-Yves David
push: extract new common set computation from phase synchronisation...
r20468 _pushcomputecommonheads(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
exchange: extract push function from localrepo...
r20345 finally:
if lock is not None:
lock.release()
finally:
if locallock is not None:
locallock.release()
Pierre-Yves David
push: feed pushoperation object to _pushbookmark function...
r20431 _pushbookmark(pushop)
Pierre-Yves David
push: move push return value in the push object...
r20439 return pushop.ret
Pierre-Yves David
push: move bookmarks exchange in the exchange module...
r20352
Pierre-Yves David
push: move discovery in its own function...
r20466 def _pushdiscovery(pushop):
# discovery
unfi = pushop.repo.unfiltered()
fci = discovery.findcommonincoming
commoninc = fci(unfi, pushop.remote, force=pushop.force)
common, inc, remoteheads = commoninc
fco = discovery.findcommonoutgoing
outgoing = fco(unfi, pushop.remote, onlyheads=pushop.revs,
commoninc=commoninc, force=pushop.force)
pushop.outgoing = outgoing
pushop.remoteheads = remoteheads
pushop.incoming = inc
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!")
mst = "push includes %s changeset: %s!"
# plain versions for i18n tool to detect them
_("push includes unstable changeset: %s!")
_("push includes bumped changeset: %s!")
_("push includes divergent changeset: %s!")
# 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():
raise util.Abort(mso % ctx)
elif ctx.troubled():
raise util.Abort(_(mst)
% (ctx.troubles()[0],
ctx))
newbm = pushop.ui.configlist('bookmarks', 'pushing')
discovery.checkheads(unfi, pushop.remote, outgoing,
pushop.remoteheads,
pushop.newbranch,
bool(pushop.incoming),
newbm)
return True
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"""
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
bundler = changegroup.bundle10(pushop.repo, bundlecaps)
cg = pushop.repo._changegroupsubset(outgoing,
bundler,
'push',
fastpath=True)
else:
cg = pushop.repo.getlocalbundle('push', outgoing, bundlecaps)
# 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
pushop.ret = pushop.remote.unbundle(cg, remoteheads,
'push')
else:
# we return an integer indicating remote head count
# change
pushop.ret = pushop.remote.addchangegroup(cg, 'push',
pushop.repo.url())
Pierre-Yves David
push: extract new common set computation from phase synchronisation...
r20468 def _pushcomputecommonheads(pushop):
unfi = pushop.repo.unfiltered()
if pushop.ret:
# push succeed, synchronize target of the push
cheads = pushop.outgoing.missingheads
elif pushop.revs is None:
# All out push fails. synchronize all common
cheads = pushop.outgoing.commonheads
else:
# 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)
common = set(pushop.outgoing.common)
nm = pushop.repo.changelog.nodemap
cheads = [node for node in pushop.revs if nm[node] in common]
# and
# * commonheads parents on missing
revset = unfi.set('%ln and parents(roots(%ln))',
pushop.outgoing.commonheads,
pushop.outgoing.missing)
cheads.extend(c.node() for c in revset)
pushop.commonheads = cheads
Pierre-Yves David
push: move phases synchronisation function in its own function...
r20441 def _pushsyncphase(pushop):
"""synchronise phase information locally and remotly"""
unfi = pushop.repo.unfiltered()
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 if pushop.ret:
# push succeed, synchronize target of the push
cheads = pushop.outgoing.missingheads
elif pushop.revs is None:
# All out push fails. synchronize all common
cheads = pushop.outgoing.commonheads
else:
# 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)
common = set(pushop.outgoing.common)
nm = pushop.repo.changelog.nodemap
cheads = [node for node in pushop.revs if nm[node] in common]
# and
# * commonheads parents on missing
revset = unfi.set('%ln and parents(roots(%ln))',
pushop.outgoing.commonheads,
pushop.outgoing.missing)
cheads.extend(c.node() for c in revset)
Pierre-Yves David
push: move `commonheads` into the push object...
r20467 pushop.commonheads = cheads
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
and pushop.ret is None # nothing was pushed
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'}
if not remotephases: # old server or public only rer
_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
# 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
outdated = unfi.set('heads((%ln::%ln) and public())',
droots, cheads)
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 local phase move in a normal function...
r20438 def _localphasemove(pushop, nodes, phase=phases.public):
"""move <nodes> to <phase> in the local source repo"""
if pushop.locallocked:
phases.advanceboundary(pushop.repo, phase, nodes)
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: move obsolescence related message into _pushobsolescence function...
r20435 pushop.ui.debug('try to push obsolete markers to remote\n')
Pierre-Yves David
push: feed pushoperation object to _pushobsolete function...
r20433 repo = pushop.repo
remote = pushop.remote
Pierre-Yves David
push: move obsolescence marker exchange in the exchange module...
r20432 if (obsolete._enabled and repo.obsstore and
'obsolete' in remote.listkeys('namespaces')):
rslts = []
remotedata = repo.listkeys('obsolete')
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: feed pushoperation object to _pushbookmark function...
r20431 ui = pushop.ui
repo = pushop.repo.unfiltered()
remote = pushop.remote
Pierre-Yves David
push: move bookmarks exchange in the exchange module...
r20352 ui.debug("checking for updated bookmarks\n")
Pierre-Yves David
push: feed pushoperation object to _pushbookmark function...
r20431 revnums = map(repo.changelog.rev, pushop.revs or [])
Pierre-Yves David
push: move bookmarks exchange in the exchange module...
r20352 ancestors = [a for a in repo.changelog.ancestors(revnums, inclusive=True)]
(addsrc, adddst, advsrc, advdst, diverge, differ, invalid
) = bookmarks.compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
srchex=hex)
for b, scid, dcid in advsrc:
if ancestors and repo[scid].rev() not in ancestors:
continue
if remote.pushkey('bookmarks', b, dcid, scid):
ui.status(_("updating bookmark %s\n") % b)
else:
ui.warn(_('updating bookmark %s failed!\n') % b)
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
It purpose is to carry push related state and very common operation.
A new should be created at the begining of each push and discarded
afterward.
"""
def __init__(self, repo):
# repo we pull from
self.repo = repo
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469
def pull(repo, remote, heads=None, force=False):
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 pullop = pulloperation(repo)
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469 if remote.local():
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 missing = set(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)))
raise util.Abort(msg)
# don't open transaction for nothing or you break future useful
# rollback call
tr = None
trname = 'pull\n' + util.hidepassword(remote.url())
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 lock = pullop.repo.lock()
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469 try:
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 tmp = discovery.findcommonincoming(pullop.repo.unfiltered(), remote,
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469 heads=heads, force=force)
common, fetch, rheads = tmp
if not fetch:
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 pullop.repo.ui.status(_("no changes found\n"))
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469 result = 0
else:
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 tr = pullop.repo.transaction(trname)
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469 if heads is None and list(common) == [nullid]:
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 pullop.repo.ui.status(_("requesting all changes\n"))
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469 elif heads is None and remote.capable('changegroupsubset'):
# issue1320, avoid a race if remote changed after discovery
heads = rheads
if remote.capable('getbundle'):
# TODO: get bundlecaps from remote
cg = remote.getbundle('pull', common=common,
heads=heads or rheads)
elif heads is None:
cg = remote.changegroup(fetch, 'pull')
elif not remote.capable('changegroupsubset'):
raise util.Abort(_("partial pull cannot be done because "
"other repository doesn't support "
"changegroupsubset."))
else:
cg = remote.changegroupsubset(fetch, heads, 'pull')
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 result = pullop.repo.addchangegroup(cg, 'pull', remote.url())
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469
# compute target subset
if heads is None:
# We pulled every thing possible
# sync on everything common
subset = common + rheads
else:
# We pulled a specific subset
# sync on this subset
subset = heads
# Get remote phases data from remote
remotephases = remote.listkeys('phases')
publishing = bool(remotephases.get('publishing', False))
if remotephases and not publishing:
# remote is new and unpublishing
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 pheads, _dr = phases.analyzeremotephases(pullop.repo, subset,
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469 remotephases)
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 phases.advanceboundary(pullop.repo, phases.public, pheads)
phases.advanceboundary(pullop.repo, phases.draft, subset)
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469 else:
# Remote is old or publishing all common changesets
# should be seen as public
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 phases.advanceboundary(pullop.repo, phases.public, subset)
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469
def gettransaction():
if tr is None:
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 return pullop.repo.transaction(trname)
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469 return tr
Pierre-Yves David
pull: introduce a pulloperation object...
r20472 obstr = obsolete.syncpull(pullop.repo, remote, gettransaction)
Pierre-Yves David
exchange: extract pull function from localrepo...
r20469 if obstr is not None:
tr = obstr
if tr is not None:
tr.close()
finally:
if tr is not None:
tr.release()
lock.release()
return result