hgext: turn 'hgext' into a namespace package
Actually since Python 2.3, there is some way to turn top level package into
"namespace package" so that multiple subpackage installed in different part of
the path can still be imported transparently. This feature was previously
thought (at least by myself) to be only provided by some setuptool black magic.
Turning hgext into such namespace package allows third extensions to install
themselves inside the "hgext" namespace package to avoid polluting the global
python module namespace. They will now be able to do so without making it a pain
to use a Mercurial "installed" in a different way/location than these
extensions.
"""Small extension altering some push behavior
- Add a new wire protocol command to exchange obsolescence markers. Sending the
raw file as a binary instead of using pushkey hack.
- Add a "push done" notification
- Push obsolescence marker before anything else (This works around the lack
of global transaction)
"""
import errno
from StringIO import StringIO
from mercurial.i18n import _
from mercurial import extensions
from mercurial import wireproto
from mercurial import obsolete
from mercurial import localrepo
def client_pushobsmarkers(self, obsfile):
"""wireprotocol peer method"""
self.requirecap('_push_experiment_pushobsmarkers_0',
_('push obsolete markers faster'))
ret, output = self._callpush('push_experiment_pushobsmarkers_0', obsfile)
for l in output.splitlines(True):
self.ui.status(_('remote: '), l)
return ret
def srv_pushobsmarkers(repo, proto):
"""wireprotocol command"""
fp = StringIO()
proto.redirect()
proto.getfile(fp)
data = fp.getvalue()
fp.close()
lock = repo.lock()
try:
tr = repo.transaction('pushkey: obsolete markers')
try:
repo.obsstore.mergemarkers(tr, data)
tr.close()
finally:
tr.release()
finally:
lock.release()
return wireproto.pushres(0)
def syncpush(orig, repo, remote):
"""wraper for obsolete.syncpush to use the fast way if possible"""
if not (obsolete.isenabled(repo, obsolete.exchangeopt) and
repo.obsstore):
return
if remote.capable('_push_experiment_pushobsmarkers_0'):
return # already pushed before changeset
remote.push_experiment_pushobsmarkers_0(obsfp)
return
return orig(repo, remote)
def client_notifypushend(self):
"""wire peer command to notify a push is done"""
self.requirecap('_push_experiment_notifypushend_0',
_('hook once push is all done'))
return self._call('push_experiment_notifypushend_0')
def srv_notifypushend(repo, proto):
"""wire protocol command to notify a push is done"""
proto.redirect()
repo.hook('notifypushend')
return wireproto.pushres(0)
def augmented_push(orig, repo, remote, *args, **kwargs):
"""push wrapped that call the wire protocol command"""
if not remote.canpush():
raise error.Abort(_("destination does not support push"))
if (obsolete.isenabled(repo, obsolete.exchangeopt) and repo.obsstore
and remote.capable('_push_experiment_pushobsmarkers_0')):
# push marker early to limit damage of pushing too early.
try:
obsfp = repo.svfs('obsstore')
except IOError as e:
if e.errno != errno.ENOENT:
raise
else:
remote.push_experiment_pushobsmarkers_0(obsfp)
ret = orig(repo, remote, *args, **kwargs)
if remote.capable('_push_experiment_notifypushend_0'):
remote.push_experiment_notifypushend_0()
return ret
def capabilities(orig, repo, proto):
"""wrapper to advertise new capability"""
caps = orig(repo, proto)
if obsolete.isenabled(repo, obsolete.exchangeopt):
caps += ' _push_experiment_pushobsmarkers_0'
caps += ' _push_experiment_notifypushend_0'
return caps
def extsetup(ui):
wireproto.wirepeer.push_experiment_pushobsmarkers_0 = client_pushobsmarkers
wireproto.wirepeer.push_experiment_notifypushend_0 = client_notifypushend
wireproto.commands['push_experiment_pushobsmarkers_0'] = \
(srv_pushobsmarkers, '')
wireproto.commands['push_experiment_notifypushend_0'] = \
(srv_notifypushend, '')
extensions.wrapfunction(wireproto, 'capabilities', capabilities)
extensions.wrapfunction(obsolete, 'syncpush', syncpush)
extensions.wrapfunction(localrepo.localrepository, 'push', augmented_push)