view hgext/drophack.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 1a39b1b8e092
children 1f8428096078
line wrap: on
line source

# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
'''This extension add a hacky command to drop changeset during review

This extension is intended as a temporary hack to allow Matt Mackall to use
evolve in the Mercurial review it self. You should probably not use it if your
name is not Matt Mackall.
'''

import os
import time
import contextlib

from mercurial.i18n import _
from mercurial import cmdutil
from mercurial import repair
from mercurial import scmutil
from mercurial import lock as lockmod
from mercurial import util
from mercurial import commands

cmdtable = {}
command = cmdutil.command(cmdtable)


@contextlib.contextmanager
def timed(ui, caption):
    ostart = os.times()
    cstart = time.time()
    yield
    cstop = time.time()
    ostop = os.times()
    wall = cstop - cstart
    user = ostop[0] - ostart[0]
    sys  = ostop[1] - ostart[1]
    comb = user + sys
    ui.write("%s: wall %f comb %f user %f sys %f\n"
             % (caption, wall, comb, user, sys))

def obsmarkerchainfrom(obsstore, nodes):
    """return all marker chain starting from node

    Starting from mean "use as successors"."""
    # XXX need something smarter for descendant of bumped changeset
    seennodes = set(nodes)
    seenmarkers = set()
    pendingnodes = set(nodes)
    precursorsmarkers = obsstore.precursors
    while pendingnodes:
        current = pendingnodes.pop()
        new = set()
        for precmark in precursorsmarkers.get(current, ()):
            if precmark in seenmarkers:
                continue
            seenmarkers.add(precmark)
            new.add(precmark[0])
            yield precmark
        new -= seennodes
        pendingnodes |= new

def stripmarker(ui, repo, markers):
    """remove <markers> from the repo obsstore

    The old obsstore content is saved in a `obsstore.prestrip` file
    """
    repo = repo.unfiltered()
    repo.destroying()
    oldmarkers = list(repo.obsstore._all)
    util.rename(repo.sjoin('obsstore'),
                repo.join('obsstore.prestrip'))
    del repo.obsstore # drop the cache
    newstore = repo.obsstore
    assert not newstore # should be empty after rename
    newmarkers = [m for m in oldmarkers if m not in markers]
    tr = repo.transaction('drophack')
    try:
        newstore.add(tr, newmarkers)
        tr.close()
    finally:
        tr.release()
    repo.destroyed()


@command('drop', [('r', 'rev', [], 'revision to update')], _('[-r] revs'))
def cmddrop(ui, repo, *revs, **opts):
    """I'm hacky do not use me!

    This command strip a changeset, its precursors and all obsolescence marker
    associated to its chain.

    There is no way to limit the extend of the purge yet. You may have to
    repull from other source to get some changeset and obsolescence marker
    back.

    This intended for Matt Mackall usage only. do not use me.
    """
    revs = list(revs)
    revs.extend(opts['rev'])
    if not revs:
        revs = ['.']
    # get the changeset
    revs = scmutil.revrange(repo, revs)
    if not revs:
        ui.write_err('no revision to drop\n')
        return 1
    # lock from the beginning to prevent race
    wlock = lock = None
    try:
        wlock = repo.wlock()
        lock = repo.lock()
        # check they have no children
        if repo.revs('%ld and public()', revs):
            ui.write_err('cannot drop public revision')
            return 1
        if repo.revs('children(%ld) - %ld', revs, revs):
            ui.write_err('cannot drop revision with children')
            return 1
        if repo.revs('. and %ld', revs):
            newrevs = repo.revs('max(::. - %ld)', revs)
            if newrevs:
                assert len(newrevs) == 1
                newrev = newrevs.first()
            else:
                newrev = -1
            commands.update(ui, repo, newrev)
            ui.status(_('working directory now at %s\n') % repo[newrev])
        # get all markers and successors up to root
        nodes = [repo[r].node() for r in revs]
        with timed(ui, 'search obsmarker'):
            markers = set(obsmarkerchainfrom(repo.obsstore, nodes))
        ui.write('%i obsmarkers found\n' % len(markers))
        cl = repo.unfiltered().changelog
        with timed(ui, 'search nodes'):
            allnodes = set(nodes)
            allnodes.update(m[0] for m in markers if cl.hasnode(m[0]))
        ui.write('%i nodes found\n' % len(allnodes))
        cl = repo.changelog
        visiblenodes = set(n for n in allnodes if cl.hasnode(n))
        # check constraint again
        if repo.revs('%ln and public()', visiblenodes):
            ui.write_err('cannot drop public revision')
            return 1
        if repo.revs('children(%ln) - %ln', visiblenodes, visiblenodes):
            ui.write_err('cannot drop revision with children')
            return 1

        if markers:
            # strip them
            with timed(ui, 'strip obsmarker'):
                stripmarker(ui, repo, markers)
        # strip the changeset
        with timed(ui, 'strip nodes'):
            repair.strip(ui, repo, allnodes, backup="all", topic='drophack')

    finally:
        lockmod.release(lock, wlock)

    # rewrite the whole file.
    # print data.
    # - time to compute the chain
    # - time to strip the changeset
    # - time to strip the obs marker.