view hgext/pushexperiment.py @ 1209:fa35aeb64d32 stable

evolve: prevent a crash in httpclient_pushobsmarkers() when pushing I've been running into a crash when pushing from my hg repo in a Fedora 16 VM to Win7 running 'hg serve', even with extensions disabled on both sides: ../hg push -r . pc pushing to http://192.168.1.4:8000/ searching for changes no changes found pushing 2 obsolescence markers (263 bytes) ** unknown exception encountered, please report by visiting ... File "hg-evolve/hgext/evolve.py", line 2482, in _pushobsolete remote.evoext_pushobsmarkers_0(obsdata) File "hg-evolve/hgext/evolve.py", line 2522, in httpclient_pushobsmarkers ret, output = self._call('evoext_pushobsmarkers_0', data=obsfile) ValueError: too many values to unpack I'm not sure how this repo differs from the one in the test suite, so I'm not sure how to craft a test for this. The failure occurs even when there _are_ csets to push. There was no crash if no obsolete markers needed to be pushed. At any rate, this code was stolen from httppeer._callpush(), where it calls self._call(). The socket exception handling wasn't necessary to fix the crash, but the calling code might as well be duplicated in its entirety. A successful push with this patch looks like this. Note the final line is _not_ in the output of the http push in test-simple4server.t: ../hg push -r . pc pushing to http://192.168.1.4:8000/ searching for changes remote has heads on branch 'default' that are not known locally: 3af110194a0c 56000e3ae44d 57ac6e51d290 7da4355c21b8 and 8 others remote: adding changesets remote: adding manifests remote: adding file changes remote: added 1 changesets with 0 changes to 1 files (+1 heads) pushing 4 obsolescence markers (525 bytes) remote: 2 obsolescence markers added
author Matt Harbison <matt_harbison@yahoo.com>
date Thu, 05 Mar 2015 20:02:07 -0500
parents 05ec92d8150d
children 196c650d5ba9
line wrap: on
line source

"""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._enabled 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 util.Abort(_("destination does not support push"))
    if (obsolete._enabled and repo.obsstore
        and remote.capable('_push_experiment_pushobsmarkers_0')):
        # push marker early to limit damage of pushing too early.
        try:
            obsfp = repo.sopener('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._enabled:
        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)