changeset 819:0b6af104fd78

merge with stable
author Pierre-Yves David <pierre-yves.david@fb.com>
date Mon, 03 Mar 2014 19:28:43 -0800
parents c2bf0eb727f1 (diff) fcdd9b8c970b (current diff)
children a9a66143e2ec
files hgext/evolve.py
diffstat 14 files changed, 1303 insertions(+), 591 deletions(-) [+]
line wrap: on
line diff
--- a/README	Mon Mar 03 19:27:42 2014 -0800
+++ b/README	Mon Mar 03 19:28:43 2014 -0800
@@ -42,6 +42,18 @@
 Changelog
 =========
 
+3.3.0 --
+
+- drop `latercomer` and `conflicting` compatibility. Those old alias are
+  deprecated for a long time now.
+- added Augie Facklers `fastop` extension (usage not recommended yet)
+- add verbose hint about how to handle corner case by hand.
+  This should help people until evolve is able to to it itself.
+- removed the qsync extension. The only user I knew about (logilab) is not
+  using it anymore. It not compatible with coming Mercurial version 2.9.
+- add progress indicator for long evolve command
+- report troubles creation from `hg import`
+
 3.2.0 -- 2013-11-15
 
 - conform to the Mercurial custom of lowercase messages
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/drophack.py	Mon Mar 03 19:28:43 2014 -0800
@@ -0,0 +1,162 @@
+# 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:
+        lock = 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[0]
+            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.
--- a/hgext/evolve.py	Mon Mar 03 19:27:42 2014 -0800
+++ b/hgext/evolve.py	Mon Mar 03 19:28:43 2014 -0800
@@ -46,6 +46,7 @@
 from mercurial import context
 from mercurial import copies
 from mercurial import error
+from mercurial import exchange
 from mercurial import extensions
 from mercurial import hg
 from mercurial import lock as lockmod
@@ -301,6 +302,56 @@
 reposetup = eh.final_reposetup
 
 #####################################################################
+### experimental behavior                                         ###
+#####################################################################
+
+@eh.wrapfunction(mercurial.obsolete, 'createmarkers')
+def _createmarkers(orig, repo, relations, *args, **kwargs):
+    """register parent information at prune time"""
+    # every time this test is run, a kitten is slain.
+    # Change it as soon as possible
+    if '[,{metadata}]' in orig.__doc__:
+        for idx, rel in enumerate(relations):
+            prec = rel[0]
+            sucs = rel[1]
+            if not sucs:
+                meta = {}
+                if 2 < len(rel):
+                    meta.update(rel[2])
+                for i, p in enumerate(prec.parents(), 1):
+                    meta['p%i' % i] = p.hex()
+                relations[idx] = (prec, sucs, meta)
+    return orig(repo, relations, *args, **kwargs)
+
+def createmarkers(*args, **kwargs):
+    return obsolete.createmarkers(*args, **kwargs)
+
+class pruneobsstore(obsolete.obsstore):
+
+    def __init__(self, *args, **kwargs):
+        self.prunedchildren = {}
+        return super(pruneobsstore, self).__init__(*args, **kwargs)
+
+    def _load(self, markers):
+        markers = self._prunedetectingmarkers(markers)
+        return super(pruneobsstore, self)._load(markers)
+
+
+    def _prunedetectingmarkers(self, markers):
+        for m in markers:
+            if not m[1]: # no successors
+                meta = obsolete.decodemeta(m[3])
+                if 'p1' in meta:
+                    p1 = node.bin(meta['p1'])
+                    self.prunedchildren.setdefault(p1, set()).add(m)
+                if 'p2' in meta:
+                    p2 = node.bin(meta['p2'])
+                    self.prunedchildren.setdefault(p2, set()).add(m)
+            yield m
+
+obsolete.obsstore = pruneobsstore
+
+#####################################################################
 ### Critical fix                                                  ###
 #####################################################################
 
@@ -326,34 +377,6 @@
 getrevs = obsolete.getrevs
 
 #####################################################################
-### Complete troubles computation logic                           ###
-#####################################################################
-
-
-### Cache computation
-latediff = 1  # flag to prevent taking late comer fix into account
-
-### changectx method
-
-@eh.addattr(context.changectx, 'latecomer')
-def latecomer(ctx):
-    """is the changeset bumped (Try to succeed to public change)"""
-    return ctx.bumped()
-
-@eh.addattr(context.changectx, 'conflicting')
-def conflicting(ctx):
-    """is the changeset divergent (Try to succeed to public change)"""
-    return ctx.divergent()
-
-### revset symbol
-
-eh.revset('latecomer')(revset.symbols['bumped'])
-eh.revset('conflicting')(revset.symbols['divergent'])
-
-
-
-
-#####################################################################
 ### Additional Utilities                                          ###
 #####################################################################
 
@@ -365,8 +388,6 @@
 # - function to travel throught the obsolescence graph
 # - function to find useful changeset to stabilize
 
-createmarkers = obsolete.createmarkers
-
 
 ### Useful alias
 
@@ -569,6 +590,7 @@
 # XXX this could wrap transaction code
 # XXX (but this is a bit a layer violation)
 @eh.wrapcommand("commit")
+@eh.wrapcommand("import")
 @eh.wrapcommand("push")
 @eh.wrapcommand("pull")
 @eh.wrapcommand("graft")
@@ -620,8 +642,7 @@
             return result
     repo.__class__ = evolvingrepo
 
-@eh.wrapcommand("summary")
-def obssummary(orig, ui, repo, *args, **kwargs):
+def summaryhook(ui, repo):
     def write(fmt, count):
         s = fmt % count
         if count:
@@ -629,14 +650,16 @@
         else:
             ui.note(s)
 
-    ret = orig(ui, repo, *args, **kwargs)
     nbunstable = len(getrevs(repo, 'unstable'))
     nbbumped = len(getrevs(repo, 'bumped'))
     nbdivergent = len(getrevs(repo, 'divergent'))
     write('unstable: %i changesets\n', nbunstable)
     write('bumped: %i changesets\n', nbbumped)
     write('divergent: %i changesets\n', nbdivergent)
-    return ret
+
+@eh.extsetup
+def obssummarysetup(ui):
+    cmdutil.summaryhooks.add('evolve', summaryhook)
 
 
 #####################################################################
@@ -660,7 +683,6 @@
     except KeyError:
         pass  # rebase not found
 
-
 #####################################################################
 ### Old Evolve extension content                                  ###
 #####################################################################
@@ -766,7 +788,10 @@
     try:
         rebase = extensions.find('rebase')
         # dummy state to trick rebase node
-        assert orig.p2().rev() == node.nullrev, 'no support yet'
+        if not orig.p2().rev() == node.nullrev:
+            raise util.Abort(
+                'no support for evolution merge changesets yet',
+                hint="Redo the merge a use `hg prune` to obsolete the old one")
         destbookmarks = repo.nodebookmarks(dest.node())
         cmdutil.duplicatecopies(repo, orig.node(), dest.node())
         nodesrc = orig.node()
@@ -835,6 +860,167 @@
      _('record the specified user in metadata'), _('USER')),
 ]
 
+@command('debugrecordpruneparents', [], '')
+def cmddebugrecordpruneparents(ui, repo):
+    """add parents data to prune markers when possible
+
+    This commands search the repo for prune markers without parent information.
+    If the pruned node is locally known, a new markers with parent data is
+    created."""
+    pgop = 'reading markers'
+
+    # lock from the beginning to prevent race
+    wlock = lock = tr = None
+    try:
+        wlock = repo.wlock()
+        lock = repo.lock()
+        tr = repo.transaction('recordpruneparents')
+        unfi = repo.unfiltered()
+        nm = unfi.changelog.nodemap
+        store = repo.obsstore
+        pgtotal = len(store._all)
+        for idx, mark in enumerate(list(store._all)):
+            if not mark[1]:
+                rev = nm.get(mark[0])
+                if rev is not None:
+                    ctx = unfi[rev]
+                    meta = obsolete.decodemeta(mark[3])
+                    for i, p in enumerate(ctx.parents(), 1):
+                        meta['p%i' % i] = p.hex()
+                    before = len(store._all)
+                    store.create(tr, mark[0], mark[1], mark[2], meta)
+                    if len(store._all) - before:
+                        ui.write('created new markers for %i\n' % rev)
+            ui.progress(pgop, idx, total=pgtotal)
+        tr.close()
+        ui.progress(pgop, None)
+    finally:
+        if tr is not None:
+            tr.release()
+        lockmod.release(lock, wlock)
+
+@command('debugobsstorestat', [], '')
+def cmddebugobsstorestat(ui, repo):
+    """print statistic about obsolescence markers in the repo"""
+    store = repo.obsstore
+    unfi = repo.unfiltered()
+    nm = unfi.changelog.nodemap
+    ui.write('markers total:              %9i\n' % len(store._all))
+    sucscount = [0, 0 , 0, 0]
+    known = 0
+    parentsdata = 0
+    metatotallenght = 0
+    metakeys = {}
+    # node -> cluster mapping
+    #   a cluster is a (set(nodes), set(markers)) tuple
+    clustersmap = {}
+    # same data using parent information
+    pclustersmap= {}
+    for mark in store:
+        if mark[0] in nm:
+            known += 1
+        nbsucs = len(mark[1])
+        sucscount[min(nbsucs, 3)] += 1
+        metatotallenght += len(mark[3])
+        meta = obsolete.decodemeta(mark[3])
+        for key in meta:
+            metakeys.setdefault(key, 0)
+            metakeys[key] += 1
+        parents = [meta.get('p1'), meta.get('p2')]
+        parents = [node.bin(p) for p in parents if p is not None]
+        if parents:
+            parentsdata += 1
+        # cluster handling
+        nodes = set()
+        nodes.add(mark[0])
+        nodes.update(mark[1])
+        c = (set(nodes), set([mark]))
+
+        toproceed = set(nodes)
+        while toproceed:
+            n = toproceed.pop()
+            other = clustersmap.get(n)
+            if (other is not None
+                and other is not c):
+                other[0].update(c[0])
+                other[1].update(c[1])
+                for on in c[0]:
+                    if on in toproceed:
+                        continue
+                    clustersmap[on] = other
+                c = other
+            clustersmap[n] = c
+        # same with parent data
+        nodes.update(parents)
+        c = (set(nodes), set([mark]))
+        toproceed = set(nodes)
+        while toproceed:
+            n = toproceed.pop()
+            other = pclustersmap.get(n)
+            if (other is not None
+                and other is not c):
+                other[0].update(c[0])
+                other[1].update(c[1])
+                for on in c[0]:
+                    if on in toproceed:
+                        continue
+                    pclustersmap[on] = other
+                c = other
+            pclustersmap[n] = c
+
+    # freezing the result
+    for c in clustersmap.values():
+        fc = (frozenset(c[0]), frozenset(c[1]))
+        for n in fc[0]:
+            clustersmap[n] = fc
+    # same with parent data
+    for c in pclustersmap.values():
+        fc = (frozenset(c[0]), frozenset(c[1]))
+        for n in fc[0]:
+            pclustersmap[n] = fc
+    ui.write('    for known precursors:   %9i\n' % known)
+    ui.write('    with parents data:      %9i\n' % parentsdata)
+    # successors data
+    ui.write('markers with no successors: %9i\n' % sucscount[0])
+    ui.write('              1 successors: %9i\n' % sucscount[1])
+    ui.write('              2 successors: %9i\n' % sucscount[2])
+    ui.write('    more than 2 successors: %9i\n' % sucscount[3])
+    # meta data info
+    ui.write('average meta length:        %9i\n'
+             % (metatotallenght/len(store._all)))
+    ui.write('    available  keys:\n')
+    for key in sorted(metakeys):
+        ui.write('    %15s:        %9i\n' % (key, metakeys[key]))
+
+    allclusters = list(set(clustersmap.values()))
+    allclusters.sort(key=lambda x: len(x[1]))
+    ui.write('disconnected clusters:      %9i\n' % len(allclusters))
+
+    ui.write('        any known node:     %9i\n'
+             % len([c for c in allclusters
+                    if [n for n in c[0] if nm.get(n) is not None]]))
+    if allclusters:
+        nbcluster = len(allclusters)
+        ui.write('        smallest length:    %9i\n' % len(allclusters[0][1]))
+        ui.write('        longer length:      %9i\n' % len(allclusters[-1][1]))
+        median = len(allclusters[nbcluster//2][1])
+        ui.write('        median length:      %9i\n' % median)
+        mean = sum(len(x[1]) for x in allclusters) // nbcluster
+        ui.write('        mean length:        %9i\n' % mean)
+    allpclusters = list(set(pclustersmap.values()))
+    allpclusters.sort(key=lambda x: len(x[1]))
+    ui.write('    using parents data:     %9i\n' % len(allpclusters))
+    ui.write('        any known node:     %9i\n'
+             % len([c for c in allclusters
+                    if [n for n in c[0] if nm.get(n) is not None]]))
+    if allpclusters:
+        nbcluster = len(allpclusters)
+        ui.write('        smallest length:    %9i\n' % len(allpclusters[0][1]))
+        ui.write('        longer length:      %9i\n' % len(allpclusters[-1][1]))
+        median = len(allpclusters[nbcluster//2][1])
+        ui.write('        median length:      %9i\n' % median)
+        mean = sum(len(x[1]) for x in allpclusters) // nbcluster
+        ui.write('        mean length:        %9i\n' % mean)
 
 @command('^evolve|stabilize|solve',
     [('n', 'dry-run', False, 'do not perform actions, print what to be done'),
@@ -925,31 +1111,53 @@
             ui.write_err(_('no troubled changesets\n'))
             return 1
 
+    def progresscb():
+        if allopt:
+            ui.progress('evolve', seen, unit='changesets', total=count)
+    seen = 1
+    count = allopt and _counttroubled(ui, repo) or 1
+
     while tr is not None:
-        result = _evolveany(ui, repo, tr, dryrunopt)
+        progresscb()
+        result = _evolveany(ui, repo, tr, dryrunopt, progresscb=progresscb)
+        progresscb()
+        seen += 1
         if not allopt:
             return result
+        progresscb()
         tr = _picknexttroubled(ui, repo, anyopt or allopt)
 
+    if allopt:
+        ui.progress('evolve', None)
 
-def _evolveany(ui, repo, tr, dryrunopt):
+
+def _evolveany(ui, repo, tr, dryrunopt, progresscb):
     repo = repo.unfiltered()
     tr = repo[tr.rev()]
     cmdutil.bailifchanged(repo)
     troubles = tr.troubles()
     if 'unstable' in troubles:
-        return _solveunstable(ui, repo, tr, dryrunopt)
+        return _solveunstable(ui, repo, tr, dryrunopt, progresscb)
     elif 'bumped' in troubles:
-        return _solvebumped(ui, repo, tr, dryrunopt)
+        return _solvebumped(ui, repo, tr, dryrunopt, progresscb)
     elif 'divergent' in troubles:
         repo = repo.unfiltered()
         tr = repo[tr.rev()]
-        return _solvedivergent(ui, repo, tr, dryrunopt)
+        return _solvedivergent(ui, repo, tr, dryrunopt, progresscb)
     else:
         assert False  # WHAT? unknown troubles
 
-def _picknexttroubled(ui, repo, pickany=False):
+def _counttroubled(ui, repo):
+    """Count the amount of troubled changesets"""
+    troubled = set()
+    troubled.update(getrevs(repo, 'unstable'))
+    troubled.update(getrevs(repo, 'bumped'))
+    troubled.update(getrevs(repo, 'divergent'))
+    return len(troubled)
+
+def _picknexttroubled(ui, repo, pickany=False, progresscb=None):
     """Pick a the next trouble changeset to solve"""
+    if progresscb: progresscb()
     tr = _stabilizableunstable(repo, repo['.'])
     if tr is None:
         wdp = repo['.']
@@ -988,7 +1196,7 @@
                 return child
     return None
 
-def _solveunstable(ui, repo, orig, dryrun=False):
+def _solveunstable(ui, repo, orig, dryrun=False, progresscb=None):
     """Stabilize a unstable changeset"""
     obs = orig.parents()[0]
     if not obs.obsolete():
@@ -1019,11 +1227,13 @@
     repo.ui.status(_('atop:'))
     if not ui.quiet:
         displayer.show(target)
+    if progresscb: progresscb()
     todo = 'hg rebase -r %s -d %s\n' % (orig, target)
     if dryrun:
         repo.ui.write(todo)
     else:
         repo.ui.note(todo)
+        if progresscb: progresscb()
         lock = repo.lock()
         try:
             relocate(repo, orig, target)
@@ -1035,7 +1245,7 @@
         finally:
             lock.release()
 
-def _solvebumped(ui, repo, bumped, dryrun=False):
+def _solvebumped(ui, repo, bumped, dryrun=False, progresscb=None):
     """Stabilize a bumped changeset"""
     # For now we deny bumped merge
     if len(bumped.parents()) > 1:
@@ -1061,6 +1271,7 @@
         repo.ui.write('hg revert --all --rev %s;\n' % bumped)
         repo.ui.write('hg commit --msg "bumped update to %s"')
         return 0
+    if progresscb: progresscb()
     wlock = repo.wlock()
     try:
         newid = tmpctx = None
@@ -1124,7 +1335,7 @@
                 else:
                     phases.retractboundary(repo, bumped.phase(), [newid])
                     createmarkers(repo, [(tmpctx, (repo[newid],))],
-                                           flag=latediff)
+                                           flag=obsolete.bumpedfix)
                 bmupdate(newid)
                 tr.close()
                 repo.ui.status(_('commited as %s\n') % node.short(newid))
@@ -1137,17 +1348,40 @@
     finally:
         wlock.release()
 
-def _solvedivergent(ui, repo, divergent, dryrun=False):
+def _solvedivergent(ui, repo, divergent, dryrun=False, progresscb=None):
     base, others = divergentdata(divergent)
     if len(others) > 1:
-        raise util.Abort("We do not handle split yet")
+        othersstr = "[%s]" % (','.join([str(i) for i in others]))
+        hint = ("changeset %d is divergent with a changeset that got splitted "
+                "| into multiple ones:\n[%s]\n"
+                "| This is not handled by automatic evolution yet\n"
+                "| You have to fallback to manual handling with commands as:\n"
+                "| - hg touch -D\n"
+                "| - hg prune\n"
+                "| \n"
+                "| You should contact your local evolution Guru for help.\n"
+                % (divergent, othersstr))
+        raise util.Abort("We do not handle divergence with split yet",
+                         hint='')
     other = others[0]
     if divergent.phase() <= phases.public:
-        raise util.Abort("We can't resolve this conflict from the public side")
+        raise util.Abort("We can't resolve this conflict from the public side",
+                         hint="%s is public, try from %s" % (divergent, other))
     if len(other.parents()) > 1:
-        raise util.Abort("divergent changeset can't be a merge (yet)")
+        raise util.Abort("divergent changeset can't be a merge (yet)",
+                          hint="You have to fallback to solving this by hand...\n"
+                               "| This probably mean to redo the merge and use "
+                               "| `hg prune` to kill older version.")
     if other.p1() not in divergent.parents():
-        raise util.Abort("parents are not common (not handled yet)")
+        raise util.Abort("parents are not common (not handled yet)",
+                         hint="| %(d)s, %(o)s are not based on the same changeset."
+                              "| With the current state of its implementation, "
+                              "| evolve does not work in that case.\n"
+                              "| rebase one of them next to the other and run "
+                              "| this command again.\n"
+                              "| - either: hg rebase -dest 'p1(%(d)s)' -r %(o)s"
+                              "| - or:     hg rebase -dest 'p1(%(d)s)' -r %(o)s"
+                              % {'d': divergent, 'o': other})
 
     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
     ui.status(_('merge:'))
@@ -1177,6 +1411,7 @@
             repo.ui.status(_('updating to "local" conflict\n'))
             hg.update(repo, divergent.rev())
         repo.ui.note(_('merging divergent changeset\n'))
+        if progresscb: progresscb()
         stats = merge.update(repo,
                              other.node(),
                              branchmerge=True,
@@ -1197,6 +1432,7 @@
 /!\ * hg ci -m "same message as the amended changeset" => new cset Y
 /!\ * hg kill -n Y W Z
 """)
+        if progresscb: progresscb()
         tr = repo.transaction('stabilize-divergent')
         try:
             repo.dirstate.setparents(divergent.node(), node.nullid)
@@ -1332,6 +1568,7 @@
     [('n', 'new', [], _("successor changeset (DEPRECATED)")),
      ('s', 'succ', [], _("successor changeset")),
      ('r', 'rev', [], _("revisions to prune")),
+     ('', 'biject', False, _("do a 1-1 map between rev and successor ranges")),
      ('B', 'bookmark', '', _("remove revs only reachable from given"
                              " bookmark"))] + metadataopts,
     _('[OPTION] [-r] REV...'))
@@ -1347,13 +1584,19 @@
     When the working directory parent is pruned the repository is updated to a
     non obsolete parents.
 
-    you can use the ``--succ`` option to informs mercurial that a newer version
+    You can use the ``--succ`` option to informs mercurial that a newer version
     of the pruned changeset exists.
+
+    You can use the ``--biject`` option to specify a 1-1 (bijection) between
+    revisions to prune and successor changesets. This option may be removed in
+    a future release (with the functionality absored automatically).
+
     """
     revs = set(scmutil.revrange(repo, list(revs) + opts.get('rev')))
     succs = opts['new'] + opts['succ']
     bookmark = opts.get('bookmark')
     metadata = _getmetadata(**opts)
+    biject = opts.get('biject')
 
     if bookmark:
         marks,revs = _reachablefrombookmark(repo, revs, bookmark)
@@ -1383,11 +1626,20 @@
 
         # defines successors changesets
         sucs = tuple(repo[n] for n in sortedrevs(succs))
-        if len(sucs) > 1 and len(precs) > 1:
+        if not biject and len(sucs) > 1 and len(precs) > 1:
             msg = "Can't use multiple successors for multiple precursors"
             raise util.Abort(msg)
+
+        if biject and len(sucs) != len(precs):
+            msg = "Can't use %d successors for %d precursors" % (len(sucs), len(precs))
+            raise util.Abort(msg)
+
+        relations = [(p, sucs) for p in precs]
+        if biject:
+            relations = [(p, (s,)) for p, s in zip(precs, sucs)]
+
         # create markers
-        createmarkers(repo, [(p, sucs) for p in precs], metadata=metadata)
+        createmarkers(repo, relations, metadata=metadata)
 
         # informs that changeset have been pruned
         ui.status(_('%i changesets pruned\n') % len(precs))
@@ -1779,3 +2031,105 @@
     entry[1].append(('O', 'old-obsolete', False,
                      _("make graft obsoletes its source")))
 
+#####################################################################
+### Obsolescence marker exchange experimenation                   ###
+#####################################################################
+
+@command('debugobsoleterelevant',
+         [],
+         'REVSET')
+def debugobsoleterelevant(ui, repo, *revsets):
+    """print allobsolescence marker relevant to a set of revision"""
+    nodes = [ctx.node() for ctx in repo.set('%lr', revsets)]
+    markers = repo.obsstore.relevantmarkers(nodes)
+    for rawmarker in sorted(markers):
+        marker = obsolete.marker(repo, rawmarker)
+        cmdutil.showmarker(ui, marker)
+
+@eh.addattr(obsolete.obsstore, 'relevantmarkers')
+def relevantmarkers(self, nodes):
+    """return a set of all obsolescence marker relevant to a set of node.
+
+    "relevant" to a set of node mean:
+
+    - marker that use this changeset as successors
+    - prune marker of direct children on this changeset.
+    - recursive application of the two rules on precursors of these markers
+
+    It  a set so you cannot rely on order"""
+    seennodes = set(nodes)
+    seenmarkers = set()
+    pendingnodes = set(nodes)
+    precursorsmarkers = self.precursors
+    prunedchildren = self.prunedchildren
+    while pendingnodes:
+        direct = set()
+        for current in pendingnodes:
+            direct.update(precursorsmarkers.get(current, ()))
+            direct.update(prunedchildren.get(current, ()))
+        direct -= seenmarkers
+        pendingnodes = set([m[0] for m in direct])
+        seenmarkers |= direct
+        pendingnodes -= seennodes
+        seennodes |= pendingnodes
+    return seenmarkers
+
+
+_pushkeyescape = getattr(obsolete, '_pushkeyescape', None)
+if _pushkeyescape is None:
+    def _pushkeyescape(markers):
+        """encode markers into a dict suitable for pushkey exchange
+
+        - binary data are base86 encoded
+        - splited in chunk less than 5300 bytes"""
+        parts = []
+        currentlen = _maxpayload * 2  # ensure we create a new part
+        for marker in markers:
+            nextdata = _encodeonemarker(marker)
+            if (len(nextdata) + currentlen > _maxpayload):
+                currentpart = []
+                currentlen = 0
+                parts.append(currentpart)
+            currentpart.append(nextdata)
+            currentlen += len(nextdata)
+        keys = {}
+        for idx, part in enumerate(reversed(parts)):
+            data = ''.join([_pack('>B', _fmversion)] + part)
+            keys['dump%i' % idx] = base85.b85encode(data)
+        return keys
+
+
+
+@eh.wrapfunction(exchange, '_pushobsolete')
+def _pushobsolete(orig, pushop):
+    """utility function to push obsolete markers to a remote"""
+    pushop.ui.debug('try to push obsolete markers to remote\n')
+    repo = pushop.repo
+    remote = pushop.remote
+    unfi = repo.unfiltered()
+    if (obsolete._enabled and repo.obsstore and
+        'obsolete' in remote.listkeys('namespaces')):
+        repo.ui.status("OBSEXC: computing relevant nodes\n")
+        nodes = [ctx.node() for ctx in unfi.set('::%ln', pushop.commonheads)]
+        repo.ui.status("OBSEXC: computing markers relevant to %i nodes\n"
+                       % len(nodes))
+        markers = repo.obsstore.relevantmarkers(nodes)
+        rslts = []
+        repo.ui.status("OBSEXC: encoding %i markers\n" % len(markers))
+        remotedata = obsolete._pushkeyescape(markers).items()
+        totalbytes = sum(len(d) for k,d in remotedata)
+        sentbytes = 0
+        repo.ui.status("OBSEXC: sending %i pushkey payload (%i bytes)\n"
+                        % (len(remotedata), totalbytes))
+        for key, data in remotedata:
+            repo.ui.progress('OBSEXC', sentbytes, item=key, unit="bytes",
+                             total=totalbytes)
+            rslts.append(remote.pushkey('obsolete', key, '', data))
+            sentbytes += len(data)
+            repo.ui.progress('OBSEXC', sentbytes, item=key, unit="bytes",
+                             total=totalbytes)
+        repo.ui.progress('OBSEXC', None)
+        if [r for r in rslts if not r]:
+            msg = _('failed to push some obsolete markers!\n')
+            repo.ui.warn(msg)
+        repo.ui.status("OBSEXC: DONE\n")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/hgfastobs.py	Mon Mar 03 19:28:43 2014 -0800
@@ -0,0 +1,124 @@
+"""Extension to try and speed up transfer of obsolete markers.
+
+Mercurial 2.6 transfers obsolete markers in the dumbest way possible:
+it simply transfers all of them to the server on every
+operation. While this /works/, it's not ideal because it's a large
+amount of extra data for users to pull down (1.9M for the 17k obsolete
+markers in hg-crew as of this writing in late July 2013). It's also
+frustrating because this transfer takes a nontrivial amount of time.
+
+You can specify a strategy with the config knob
+obsolete.syncstrategy. Current strategies are "stock" and
+"boxfill". Default strategy is presently boxfill.
+
+:stock: use the default strategy of mercurial explaned above
+
+:boxfill: transmit obsolete markers which list any of transmitted changesets as
+          a successor (transitively), as well as any kill markers for dead
+          nodes descended from any of the precursors of outgoing.missing.
+
+TODO(durin42): consider better names for sync strategies.
+"""
+import sys
+
+from mercurial import base85
+from mercurial import commands
+from mercurial import extensions
+from mercurial import node
+from mercurial import obsolete
+from mercurial import exchange
+from mercurial import revset
+from mercurial.i18n import _
+
+_strategies = {
+    'stock': exchange._pushobsolete,
+    }
+
+def _strategy(name, default=False):
+    def inner(func):
+        _strategies[name] = func
+        if default:
+            _strategies[None] = func
+        return func
+    return inner
+
+def _pushobsoletewrapper(orig, pushop):
+    stratfn = _strategies[pushop.repo.ui.config('obsolete', 'syncstrategy')]
+    return stratfn(pushop)
+
+extensions.wrapfunction(exchange, '_pushobsolete', _pushobsoletewrapper)
+
+def _precursors(repo, s):
+    """Precursor of a changeset"""
+    cs = set()
+    nm = repo.changelog.nodemap
+    markerbysubj = repo.obsstore.precursors
+    for r in s:
+        for p in markerbysubj.get(repo[r].node(), ()):
+            pr = nm.get(p[0])
+            if pr is not None:
+                cs.add(pr)
+    return cs
+
+def _revsetprecursors(repo, subset, x):
+    s = revset.getset(repo, revset.baseset(range(len(repo))), x)
+    cs = _precursors(repo, s)
+    return revset.baseset([r for r in subset if r in cs])
+
+revset.symbols['_fastobs_precursors'] = _revsetprecursors
+
+
+@_strategy('boxfill', default=True)
+def boxfill(pushop):
+    """The "fill in the box" strategy from the 2.6 sprint.
+
+    See the notes[0] from the 2.6 sprint for what "fill in the box"
+    means here. It's a fairly subtle algorithm, which may have
+    surprising behavior at times, but was the least-bad option
+    proposed at the sprint.
+
+    [0]: https://bitbucket.org/durin42/2.6sprint-notes/src/tip/mercurial26-obsstore-rev.1398.txt
+    """
+    repo = pushop.repo
+    remote = pushop.remote
+    outgoing = pushop.outgoing
+    urepo = pushop.repo.unfiltered()
+    # need to collect obsolete markers which list any of
+    # outgoing.missing as a successor (transitively), as well as any
+    # kill markers for dead nodes descended from any of the precursors
+    # of outgoing.missing.
+    boxedges = urepo.revs(
+        '(descendants(_fastobs_precursors(%ln)) or '
+        ' descendants(%ln)) and hidden()',
+        outgoing.missing, outgoing.missing)
+    transmit = []
+    for node in outgoing.missing:
+        transmit.extend(obsolete.precursormarkers(urepo[node]))
+    for rev in boxedges:
+        transmit.extend(obsolete.successormarkers(urepo[rev]))
+    transmit = list(set(transmit))
+    xmit, total = len(transmit), len(repo.obsstore._all)
+    repo.ui.status(
+        'boxpush: about to transmit %d obsolete markers (%d markers total)\n'
+        % (xmit, total))
+    parts, size, chunk = [], 0, 0
+    def transmitmarks():
+            repo.ui.note(
+                'boxpush: sending a chunk of obsolete markers\n')
+            data = ''.join([obsolete._pack('>B', obsolete._fmversion)] + parts)
+            remote.pushkey('obsolete', 'dump-%d' % chunk, '',
+                           base85.b85encode(data))
+
+    for marker in transmit:
+        enc = obsolete._encodeonemarker(_markertuple(marker))
+        parts.append(enc)
+        size += len(enc)
+        if size > obsolete._maxpayload:
+            transmitmarks()
+            parts, size = [], 0
+            chunk += 1
+    if parts:
+        transmitmarks()
+
+def _markertuple(marker):
+    return marker._data
--- a/hgext/qsync.py	Mon Mar 03 19:27:42 2014 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,266 +0,0 @@
-# Copyright 2011 Logilab SA <contact@logilab.fr>
-"""synchronize patches queues and evolving changesets"""
-
-import re
-from cStringIO import StringIO
-import json
-
-from mercurial.i18n import _
-from mercurial import commands
-from mercurial import patch
-from mercurial import util
-from mercurial.node import nullid, hex, short, bin
-from mercurial import cmdutil
-from mercurial import hg
-from mercurial import scmutil
-from mercurial import error
-from mercurial import extensions
-from mercurial import phases
-from mercurial import obsolete
-
-### old compat code
-#############################
-
-BRANCHNAME="qsubmit2"
-
-### new command
-#############################
-cmdtable = {}
-command = cmdutil.command(cmdtable)
-
-@command('^qsync|sync',
-    [
-     ('a', 'review-all', False, _('mark all touched patches ready for review (no editor)')),
-    ],
-    '')
-def cmdsync(ui, repo, **opts):
-    '''Export draft changeset as mq patch in a mq patches repository commit.
-
-    This command get all changesets in draft phase and create an mq changeset:
-
-        * on a "qsubmit2" branch (based on the last changeset)
-
-        * one patch per draft changeset
-
-        * a series files listing all generated patch
-
-        * qsubmitdata holding useful information
-
-    It does use obsolete relation to update patches that already existing in the qsubmit2 branch.
-
-    Already existing patch which became public, draft or got killed are remove from the mq repo.
-
-    Patch name are generated using the summary line for changeset description.
-
-    .. warning:: Series files is ordered topologically. So two series with
-                 interleaved changeset will appear interleaved.
-    '''
-
-    review = 'edit'
-    if opts['review_all']:
-        review = 'all'
-    mqrepo = repo.mq.qrepo()
-    if mqrepo is None:
-        raise util.Abort('No patches repository')
-
-    try:
-        parent = mqrepo[BRANCHNAME]
-    except error.RepoLookupError:
-        parent = initqsubmit(mqrepo)
-    store, data, touched = fillstore(repo, parent)
-    try:
-        if not touched:
-            raise util.Abort('Nothing changed')
-        files = ['qsubmitdata', 'series'] + touched
-        # mark some as ready for review
-        message = 'qsubmit commit\n\n'
-        review_list = []
-        applied_list = []
-        if review:
-            olddata = get_old_data(parent)
-            oldfiles = dict([(name, bin(ctxhex)) for ctxhex, name in olddata])
-
-            for patch_name in touched:
-                try:
-                    store.getfile(patch_name)
-                    review_list.append(patch_name)
-                except IOError:
-                    oldnode = oldfiles[patch_name]
-                    newnodes = obsolete.successorssets(repo, oldnode)
-                    if newnodes:
-                        newnodes = [n for n in newnodes if n and n[0] in repo] # remove killing
-                    if not newnodes:
-                        # changeset has been killed (eg. reject)
-                        pass
-                    else:
-                        assert len(newnodes) == 1 # conflict!!!
-                        newnode = newnodes[0]
-                        assert len(newnode) == 1 # split unsupported for now
-                        newnode = list(newnode)[0]
-                        # XXX unmanaged case where a cs is obsoleted by an unavailable one
-                        #if newnode.node() not in repo.changelog.nodemap:
-                        #    raise util.Abort('%s is obsoleted by an unknown node %s'% (oldnode, newnode))
-                        ctx = repo[newnode]
-                        if ctx.phase() == phases.public:
-                            # applied
-                            applied_list.append(patch_name)
-                        elif ctx.phase() == phases.secret:
-                            # already exported changeset is now secret
-                            repo.ui.warn("An already exported changeset is now secret!!!")
-                        else:
-                            # draft
-                            assert False, "Should be exported"
-
-        if review:
-            if applied_list:
-                message += '\n'.join('* applied %s' % x for x in applied_list) + '\n'
-            if review_list:
-                message += '\n'.join('* %s ready for review' % x for x in review_list) + '\n'
-        memctx = patch.makememctx(mqrepo, (parent.node(), nullid),
-                                  message,
-                                  None,
-                                  None,
-                                  parent.branch(), files, store,
-                                  editor=None)
-        if review == 'edit':
-            memctx._text = cmdutil.commitforceeditor(mqrepo, memctx, [])
-        mqrepo.savecommitmessage(memctx.description())
-        n = memctx.commit()
-    finally:
-        store.close()
-    return 0
-
-
-def makename(ctx):
-    """create a patch name form a changeset"""
-    descsummary = ctx.description().splitlines()[0]
-    descsummary = re.sub(r'\s+', '_', descsummary)
-    descsummary = re.sub(r'\W+', '', descsummary)
-    if len(descsummary) > 45:
-        descsummary = descsummary[:42] + '.'
-    return '%s-%s.diff' % (ctx.branch().upper(), descsummary)
-
-
-def get_old_data(mqctx):
-    """read qsubmit data to fetch previous export data
-
-    get old data from the content of an mq commit"""
-    try:
-        old_data = mqctx['qsubmitdata']
-        return json.loads(old_data.data())
-    except error.LookupError:
-        return []
-
-def get_current_data(repo):
-    """Return what would be exported if no previous data exists"""
-    data = []
-    for ctx in repo.set('draft() - (obsolete() + merge())'):
-        name = makename(ctx)
-        data.append([ctx.hex(), makename(ctx)])
-    merges = repo.revs('draft() and merge()')
-    if merges:
-        repo.ui.warn('ignoring %i merge\n' % len(merges))
-    return data
-
-
-def patchmq(repo, store, olddata, newdata):
-    """export the mq patches and return all useful data to be exported"""
-    finaldata = []
-    touched = set()
-    currentdrafts = set(d[0] for d in newdata)
-    usednew = set()
-    usedold = set()
-    evolve = extensions.find('evolve')
-    for oldhex, oldname in olddata:
-        if oldhex in usedold:
-            continue # no duplicate
-        usedold.add(oldhex)
-        oldname = str(oldname)
-        oldnode = bin(oldhex)
-        newnodes = obsolete.successorssets(repo, oldnode)
-        if newnodes:
-            newnodes = [n for n in newnodes if n and n[0] in repo] # remove killing
-            if len(newnodes) > 1:
-                newnodes = [short(nodes[0]) for nodes in newnodes]
-                raise util.Abort('%s have more than one newer version: %s'% (oldname, newnodes))
-            if newnodes:
-                # else, changeset have been killed
-                newnode = list(newnodes)[0][0]
-                ctx = repo[newnode]
-                if ctx.hex() != oldhex and ctx.phase():
-                    fp = StringIO()
-                    cmdutil.export(repo, [ctx.rev()], fp=fp)
-                    data = fp.getvalue()
-                    store.setfile(oldname, data, (None, None))
-                    finaldata.append([ctx.hex(), oldname])
-                    usednew.add(ctx.hex())
-                    touched.add(oldname)
-                    continue
-        if oldhex in currentdrafts:
-            # else changeset is now public or secret
-            finaldata.append([oldhex, oldname])
-            usednew.add(ctx.hex())
-            continue
-        touched.add(oldname)
-
-    for newhex, newname in newdata:
-        if newhex in usednew:
-            continue
-        newnode = bin(newhex)
-        ctx = repo[newnode]
-        fp = StringIO()
-        cmdutil.export(repo, [ctx.rev()], fp=fp)
-        data = fp.getvalue()
-        store.setfile(newname, data, (None, None))
-        finaldata.append([ctx.hex(), newname])
-        touched.add(newname)
-    # sort by branchrev number
-    finaldata.sort(key=lambda x: sort_key(repo[x[0]]))
-    # sort touched too (ease review list)
-    stouched = [f[1] for f in finaldata if f[1] in touched]
-    stouched += [x for x in touched if x not in stouched]
-    return finaldata, stouched
-
-def sort_key(ctx):
-    """ctx sort key: (branch, rev)"""
-    return (ctx.branch(), ctx.rev())
-
-
-def fillstore(repo, basemqctx):
-    """fill store with patch data"""
-    olddata = get_old_data(basemqctx)
-    newdata = get_current_data(repo)
-    store = patch.filestore()
-    try:
-        data, touched = patchmq(repo, store, olddata, newdata)
-        # put all name in the series
-        series ='\n'.join(d[1] for d in data) + '\n'
-        store.setfile('series', series, (False, False))
-
-        # export data to ease futur work
-        store.setfile('qsubmitdata', json.dumps(data, indent=True),
-                      (False, False))
-    except:
-        store.close()
-	raise
-    return store, data, touched
-
-
-def initqsubmit(mqrepo):
-    """create initial qsubmit branch"""
-    store = patch.filestore()
-    try:
-        files = set()
-        store.setfile('DO-NOT-EDIT-THIS-WORKING-COPY-BY-HAND', 'WE WARNED YOU!', (False, False))
-        store.setfile('.hgignore', '^status$\n', (False, False))
-        memctx = patch.makememctx(mqrepo, (nullid, nullid),
-                              'qsubmit init',
-                              None,
-                              None,
-                              BRANCHNAME, ('.hgignore',), store,
-                              editor=None)
-        mqrepo.savecommitmessage(memctx.description())
-        n = memctx.commit()
-    finally:
-        store.close()
-    return mqrepo[n]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-boxpush.t	Mon Mar 03 19:28:43 2014 -0800
@@ -0,0 +1,44 @@
+  $ fastobs="$TESTDIR"/../hgext/hgfastobs.py
+  $ echo 'from mercurial import obsolete ; obsolete._enabled = True' > enableobs.py
+  $ cat >> $HGRCPATH <<EOF
+  > [obsolete]
+  > syncstrategy = boxfill
+  > [extensions]
+  > EOF
+  $ echo "enable-obsolete = $PWD/enableobs.py" >> $HGRCPATH
+  $ echo "fastobs = $fastobs" >> $HGRCPATH
+
+  $ hg init alice
+  $ hg init bob
+  $ hg init trent
+  $ cd alice
+  $ echo a > a
+  $ hg addr && hg ci -m 'add a'
+  adding a
+  $ echo aa >> a
+  $ hg ci -m 'edit a'
+  $ echo aa > a
+  $ hg ci --amend -m 'edit a'
+  $ hg debugobsolete
+  e772e827cd64564621e7e5af15c9f848e3b92c8e efa8cd969bc37e6a1330c29f4234fe9e9be681b3 0 {'date': '* 0', 'user': 'test'} (glob)
+  5ccfcbc00f2a19cd7affedce5ff087e68e67c6cc 0 {'date': '* 0', 'user': 'test'} (glob)
+  $ hg push ../trent
+  pushing to ../trent
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
+  boxpush: about to transmit 2 obsolete markers (2 markers total)
+  $ cd ../bob
+  $ hg pull ../trent
+  pulling from ../trent
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
+  (run 'hg update' to get a working copy)
+  $ hg debugobsolete | sort
+  5ccfcbc00f2a19cd7affedce5ff087e68e67c6cc 0 {'date': '* 0', 'user': 'test'} (glob)
+  e772e827cd64564621e7e5af15c9f848e3b92c8e efa8cd969bc37e6a1330c29f4234fe9e9be681b3 0 {'date': '* 0', 'user': 'test'} (glob)
--- a/tests/test-corrupt.t	Mon Mar 03 19:27:42 2014 -0800
+++ b/tests/test-corrupt.t	Mon Mar 03 19:28:43 2014 -0800
@@ -111,6 +111,11 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  OBSEXC: computing relevant nodes
+  OBSEXC: computing markers relevant to 4 nodes
+  OBSEXC: encoding 2 markers
+  OBSEXC: sending 1 pushkey payload (184 bytes)
+  OBSEXC: DONE
   $ hg -R ../other verify
   checking changesets
   checking manifests
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-drop.t	Mon Mar 03 19:28:43 2014 -0800
@@ -0,0 +1,267 @@
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > hgext.rebase=
+  > hgext.graphlog=
+  > EOF
+  $ echo "drophack=$(echo $(dirname $TESTDIR))/hgext/drophack.py" >> $HGRCPATH
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+  $ mkcommit() {
+  >    echo "$1" > "$1"
+  >    hg add "$1"
+  >    hg ci -m "add $1"
+  > }
+  $ summary() {
+  > echo ============ graph ==============
+  > hg log -G
+  > echo ============ hidden =============
+  > hg log --hidden -G
+  > echo ============ obsmark ============
+  > hg debugobsolete
+  > }
+
+
+  $ hg init repo
+  $ cd repo
+  $ mkcommit base
+
+drop a single changeset without any rewrite
+================================================
+
+
+  $ mkcommit simple-single
+  $ summary
+  ============ graph ==============
+  @  changeset:   1:d4e7845543ff
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add simple-single
+  |
+  o  changeset:   0:b4952fcf48cf
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add base
+  
+  ============ hidden =============
+  @  changeset:   1:d4e7845543ff
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add simple-single
+  |
+  o  changeset:   0:b4952fcf48cf
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add base
+  
+  ============ obsmark ============
+  $ hg drop .
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory now at b4952fcf48cf
+  search obsmarker: wall * comb * user * sys * (glob)
+  0 obsmarkers found
+  search nodes: wall * comb * user * sys * (glob)
+  1 nodes found
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d4e7845543ff-drophack.hg
+  strip nodes: wall * comb * user * sys * (glob)
+  $ summary
+  ============ graph ==============
+  @  changeset:   0:b4952fcf48cf
+     tag:         tip
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add base
+  
+  ============ hidden =============
+  @  changeset:   0:b4952fcf48cf
+     tag:         tip
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add base
+  
+  ============ obsmark ============
+
+Try to drop a changeset with children
+================================================
+
+  $ mkcommit parent
+  $ mkcommit child
+  $ summary
+  ============ graph ==============
+  @  changeset:   2:34b6c051bf1f
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add child
+  |
+  o  changeset:   1:19509a42b0d0
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add parent
+  |
+  o  changeset:   0:b4952fcf48cf
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add base
+  
+  ============ hidden =============
+  @  changeset:   2:34b6c051bf1f
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add child
+  |
+  o  changeset:   1:19509a42b0d0
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add parent
+  |
+  o  changeset:   0:b4952fcf48cf
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add base
+  
+  ============ obsmark ============
+  $ hg drop 1
+  cannot drop revision with children (no-eol)
+  [1]
+  $ summary
+  ============ graph ==============
+  @  changeset:   2:34b6c051bf1f
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add child
+  |
+  o  changeset:   1:19509a42b0d0
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add parent
+  |
+  o  changeset:   0:b4952fcf48cf
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add base
+  
+  ============ hidden =============
+  @  changeset:   2:34b6c051bf1f
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add child
+  |
+  o  changeset:   1:19509a42b0d0
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add parent
+  |
+  o  changeset:   0:b4952fcf48cf
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add base
+  
+  ============ obsmark ============
+
+Try to drop a public changeset
+================================================
+
+  $ hg phase --public 2
+  $ hg drop 2
+  cannot drop public revision (no-eol)
+  [1]
+
+
+Try to drop a changeset with rewrite
+================================================
+
+  $ hg phase --force --draft 2
+  $ echo babar >> child
+  $ hg commit --amend
+  $ summary
+  ============ graph ==============
+  @  changeset:   4:a2c06c884bfe
+  |  tag:         tip
+  |  parent:      1:19509a42b0d0
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add child
+  |
+  o  changeset:   1:19509a42b0d0
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add parent
+  |
+  o  changeset:   0:b4952fcf48cf
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add base
+  
+  ============ hidden =============
+  @  changeset:   4:a2c06c884bfe
+  |  tag:         tip
+  |  parent:      1:19509a42b0d0
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add child
+  |
+  | x  changeset:   3:87ea30a976fd
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     temporary amend commit for 34b6c051bf1f
+  | |
+  | x  changeset:   2:34b6c051bf1f
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     add child
+  |
+  o  changeset:   1:19509a42b0d0
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add parent
+  |
+  o  changeset:   0:b4952fcf48cf
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add base
+  
+  ============ obsmark ============
+  34b6c051bf1f78db6aef400776de5cb964470207 a2c06c884bfe53d3840026248bd8a7eafa152df8 0 {'date': '* *', 'user': 'test'} (glob)
+  87ea30a976fdf235bf096f04899cb02a903873e2 0 {'date': '* *', 'user': 'test'} (glob)
+  $ hg drop .
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory now at 19509a42b0d0
+  search obsmarker: wall * comb * user * sys * (glob)
+  1 obsmarkers found
+  search nodes: wall * comb * user * sys * (glob)
+  2 nodes found
+  strip obsmarker: wall * comb * user * sys * (glob)
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/*-drophack.hg (glob)
+  strip nodes: wall * comb * user * sys * (glob)
+  $ summary
+  ============ graph ==============
+  @  changeset:   1:19509a42b0d0
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add parent
+  |
+  o  changeset:   0:b4952fcf48cf
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add base
+  
+  ============ hidden =============
+  @  changeset:   1:19509a42b0d0
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add parent
+  |
+  o  changeset:   0:b4952fcf48cf
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add base
+  
+  ============ obsmark ============
+  87ea30a976fdf235bf096f04899cb02a903873e2 0 {'date': '* *', 'user': 'test'} (glob)
--- a/tests/test-evolve.t	Mon Mar 03 19:27:42 2014 -0800
+++ b/tests/test-evolve.t	Mon Mar 03 19:28:43 2014 -0800
@@ -645,3 +645,30 @@
   4	: add 4 - test
   5	: add 3 - test
   11	: add 1 - test
+
+Test obsstore stat
+
+  $ hg debugobsstorestat
+  markers total:                     10
+      for known precursors:          10
+      with parents data:              0
+  markers with no successors:         0
+                1 successors:        10
+                2 successors:         0
+      more than 2 successors:         0
+  average meta length:               27
+      available  keys:
+                 date:               10
+                 user:               10
+  disconnected clusters:              1
+          any known node:             1
+          smallest length:           10
+          longer length:             10
+          median length:             10
+          mean length:               10
+      using parents data:             1
+          any known node:             1
+          smallest length:           10
+          longer length:             10
+          median length:             10
+          mean length:               10
--- a/tests/test-obsolete.t	Mon Mar 03 19:27:42 2014 -0800
+++ b/tests/test-obsolete.t	Mon Mar 03 19:28:43 2014 -0800
@@ -181,6 +181,11 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 5 files (+1 heads)
+  OBSEXC: computing relevant nodes
+  OBSEXC: computing markers relevant to 5 nodes
+  OBSEXC: encoding 2 markers
+  OBSEXC: sending 1 pushkey payload (154 bytes)
+  OBSEXC: DONE
   $ hg -R ../other-new verify
   checking changesets
   checking manifests
@@ -234,6 +239,11 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  OBSEXC: computing relevant nodes
+  OBSEXC: computing markers relevant to 5 nodes
+  OBSEXC: encoding 3 markers
+  OBSEXC: sending 1 pushkey payload (230 bytes)
+  OBSEXC: DONE
   $ qlog -R ../other-new
   5
   - 95de7fc6918d
@@ -255,6 +265,11 @@
   pushing to ../other-new
   searching for changes
   no changes found
+  OBSEXC: computing relevant nodes
+  OBSEXC: computing markers relevant to 5 nodes
+  OBSEXC: encoding 3 markers
+  OBSEXC: sending 1 pushkey payload (230 bytes)
+  OBSEXC: DONE
   [1]
 
   $ hg up --hidden -q .^ # 3
@@ -526,6 +541,11 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to [12] files (re)
+  OBSEXC: computing relevant nodes
+  OBSEXC: computing markers relevant to 5 nodes
+  OBSEXC: encoding 7 markers
+  OBSEXC: sending 1 pushkey payload (565 bytes)
+  OBSEXC: DONE
   $ hg up -q 10
   $ mkcommit "obsol_d'''"
   created new head
@@ -537,6 +557,11 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  OBSEXC: computing relevant nodes
+  OBSEXC: computing markers relevant to 5 nodes
+  OBSEXC: encoding 8 markers
+  OBSEXC: sending 1 pushkey payload (642 bytes)
+  OBSEXC: DONE
   $ cd ..
 
 check bumped detection
@@ -637,7 +662,7 @@
 #no produced by 2.3
 33d458d86621f3186c40bfccd77652f4a122743e 3734a65252e69ddcced85901647a4f335d40de1e 0 {'date': '* *', 'user': 'test'} (glob)
 
-Check conflict detection
+Check divergence detection
 
   $ hg up 9468a5f5d8b2 #  add obsol_d''
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -650,7 +675,7 @@
   update: (2|9|11) new changesets, (3|9|10) branch heads \(merge\) (re)
   bumped: 1 changesets
   $ hg debugobsolete `getid a7a6f2b5d8a5` `getid 50f11e5e3a63`
-  $ hg log -r 'conflicting()'
+  $ hg log -r 'divergent()'
   changeset:   12:6db5e282cb91
   parent:      10:2033b4e49474
   user:        test
@@ -678,3 +703,141 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     add c
   
+
+Check import reports new unstable changeset:
+
+  $ hg up --hidden 2
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory parent is obsolete!
+  $ hg export 9468a5f5d8b2 | hg import -
+  applying patch from stdin
+  1 new unstable changesets
+
+
+Relevant marker computation
+==============================
+
+  $ hg log -G --hidden
+  @  changeset:   17:a5f7a21fe7bc
+  |  tag:         tip
+  |  parent:      2:4538525df7e2
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add obsol_d''
+  |
+  | o  changeset:   16:50f11e5e3a63
+  | |  parent:      11:9468a5f5d8b2
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     add obsolet_conflicting_d
+  | |
+  | | o  changeset:   15:705ab2a6b72e
+  | | |  parent:      10:2033b4e49474
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  summary:     add f
+  | | |
+  | | | x  changeset:   14:33d458d86621
+  | | | |  user:        test
+  | | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | | |  summary:     temporary amend commit for 0b1b6dd009c0
+  | | | |
+  | | | x  changeset:   13:0b1b6dd009c0
+  | | |/   parent:      10:2033b4e49474
+  | | |    user:        test
+  | | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |    summary:     add f
+  | | |
+  | | | o  changeset:   12:6db5e282cb91
+  | | |/   parent:      10:2033b4e49474
+  | | |    user:        test
+  | | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |    summary:     add obsol_d'''
+  | | |
+  | o |  changeset:   11:9468a5f5d8b2
+  | |/   user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    summary:     add obsol_d''
+  | |
+  | o  changeset:   10:2033b4e49474
+  | |  parent:      4:725c380fe99b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     add obsol_c
+  | |
+  | | x  changeset:   9:83b5778897ad
+  | |    parent:      -1:000000000000
+  | |    user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    summary:     add toto
+  | |
+  | | x  changeset:   8:159dfc9fa5d3
+  | | |  parent:      3:0d3f46688ccc
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  summary:     add obsol_d''
+  | | |
+  | | | x  changeset:   7:909a0fb57e5d
+  | | |/   parent:      3:0d3f46688ccc
+  | | |    user:        test
+  | | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |    summary:     add obsol_d'
+  | | |
+  | | | x  changeset:   6:95de7fc6918d
+  | | |/   parent:      3:0d3f46688ccc
+  | | |    user:        test
+  | | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |    summary:     add obsol_d
+  | | |
+  | | | x  changeset:   5:a7a6f2b5d8a5
+  | | |/   parent:      3:0d3f46688ccc
+  | | |    user:        test
+  | | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |    summary:     add d
+  | | |
+  | o |  changeset:   4:725c380fe99b
+  | | |  parent:      1:7c3bad9141dc
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  summary:     add obsol_c'
+  | | |
+  | | x  changeset:   3:0d3f46688ccc
+  | |/   parent:      1:7c3bad9141dc
+  | |    user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    summary:     add obsol_c
+  | |
+  x |  changeset:   2:4538525df7e2
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     add c
+  |
+  o  changeset:   1:7c3bad9141dc
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add b
+  |
+  o  changeset:   0:1f0dee641bb7
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add a
+  
+
+Simple rewrite
+
+  $ hg  --hidden debugobsoleterelevant 3
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0d3f46688ccc6e756c7e96cf64c391c411309597 0 {'date': '', 'user': 'test'}
+
+simple rewrite with a prune attached to it
+
+  $ hg debugobsoleterelevant 15
+  0b1b6dd009c037985363e2290a0b579819f659db 705ab2a6b72e2cd86edb799ebe15f2695f86143e 0 {'date': '* *', 'user': 'test'} (glob)
+  33d458d86621f3186c40bfccd77652f4a122743e 0 {'date': '* *', 'p1': '0b1b6dd009c037985363e2290a0b579819f659db', 'user': 'test'} (glob)
+
+Transitive rewrite
+
+  $ hg --hidden debugobsoleterelevant 8
+  909a0fb57e5d909f353d89e394ffd7e0890fec88 159dfc9fa5d334d7e03a0aecfc7f7ab4c3431fea 0 {'date': '', 'user': 'test'}
+  95de7fc6918dea4c9c8d5382f50649794b474c4a 909a0fb57e5d909f353d89e394ffd7e0890fec88 0 {'date': '', 'user': 'test'}
+  a7a6f2b5d8a54b81bc7aa2fba2934ad6d700a79e 95de7fc6918dea4c9c8d5382f50649794b474c4a 0 {'date': '', 'user': 'test'}
+
--- a/tests/test-prune.t	Mon Mar 03 19:27:42 2014 -0800
+++ b/tests/test-prune.t	Mon Mar 03 19:28:43 2014 -0800
@@ -43,7 +43,7 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   working directory now at 47d2a3944de8
   $ hg debugobsolete
-  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', 'user': 'blah'} (glob)
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', ('p1': '47d2a3944de8b013de3be9578e8e344ea2e6c097', )?'user': 'blah'} (re)
 
 prune leaving unstability behind
 
@@ -51,8 +51,8 @@
   1 changesets pruned
   2 new unstable changesets
   $ hg debugobsolete
-  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', 'user': 'blah'} (glob)
-  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '*', 'user': 'test'} (glob)
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', ('p1': '47d2a3944de8b013de3be9578e8e344ea2e6c097', )?'user': 'blah'} (re)
+  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '\d+ \d+', ('p1': '1f0dee641bb7258c56bd60e93edfa2405381c41e', )?'user': 'test'} (re)
 
 pruning multiple changeset at once
 
@@ -61,10 +61,10 @@
   0 files updated, 0 files merged, 3 files removed, 0 files unresolved
   working directory now at 1f0dee641bb7
   $ hg debugobsolete
-  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', 'user': 'blah'} (glob)
-  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '*', 'user': 'test'} (glob)
-  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '*', 'user': 'test'} (glob)
-  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '*', 'user': 'test'} (glob)
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', ('p1': '47d2a3944de8b013de3be9578e8e344ea2e6c097', )?'user': 'blah'} (re)
+  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '\d+ \d+', ('p1': '1f0dee641bb7258c56bd60e93edfa2405381c41e', )?'user': 'test'} (re)
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '\d+ \d+', ('p1': '7c3bad9141dcb46ff89abf5f61856facd56e476c', )?'user': 'test'} (re)
+  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '\d+ \d+', ('p1': '4538525df7e2b9f09423636c61ef63a4cb872a2d', )?'user': 'test'} (re)
 
 cannot prune public changesets
 
@@ -73,10 +73,10 @@
   (see "hg help phases" for details)
   [255]
   $ hg debugobsolete
-  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', 'user': 'blah'} (glob)
-  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '*', 'user': 'test'} (glob)
-  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '*', 'user': 'test'} (glob)
-  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '*', 'user': 'test'} (glob)
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', ('p1': '47d2a3944de8b013de3be9578e8e344ea2e6c097', )?'user': 'blah'} (re)
+  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '\d+ \d+', ('p1': '1f0dee641bb7258c56bd60e93edfa2405381c41e', )?'user': 'test'} (re)
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '\d+ \d+', ('p1': '7c3bad9141dcb46ff89abf5f61856facd56e476c', )?'user': 'test'} (re)
+  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '\d+ \d+', ('p1': '4538525df7e2b9f09423636c61ef63a4cb872a2d', )?'user': 'test'} (re)
 
 Check successors addition
 ----------------------------
@@ -118,11 +118,11 @@
   $ hg prune 'desc("add ee")' -s 'desc("add nE")'
   1 changesets pruned
   $ hg debugobsolete
-  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', 'user': 'blah'} (glob)
-  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '*', 'user': 'test'} (glob)
-  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '*', 'user': 'test'} (glob)
-  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '*', 'user': 'test'} (glob)
-  bb5e90a7ea1f3b4b38b23150a4a597b6146d70ef 6e8148413dd541855b72a920a90c06fca127c7e7 0 {'date': '*', 'user': 'test'} (glob)
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', ('p1': '47d2a3944de8b013de3be9578e8e344ea2e6c097', )?'user': 'blah'} (re)
+  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '\d+ \d+', ('p1': '1f0dee641bb7258c56bd60e93edfa2405381c41e', )?'user': 'test'} (re)
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '\d+ \d+', ('p1': '7c3bad9141dcb46ff89abf5f61856facd56e476c', )?'user': 'test'} (re)
+  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '\d+ \d+', ('p1': '4538525df7e2b9f09423636c61ef63a4cb872a2d', )?'user': 'test'} (re)
+  bb5e90a7ea1f3b4b38b23150a4a597b6146d70ef 6e8148413dd541855b72a920a90c06fca127c7e7 0 {'date': '\d+ \d+', 'user': 'test'} (re)
   $ hg log -G
   @  12:6e8148413dd5[] (stable/draft) add nE
   |
@@ -146,10 +146,10 @@
   $ hg prune 'desc("add dd")' -s 'desc("add nD")' -s 'desc("add nC")'
   1 changesets pruned
   $ hg debugobsolete
-  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', 'user': 'blah'} (glob)
-  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '*', 'user': 'test'} (glob)
-  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '*', 'user': 'test'} (glob)
-  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '*', 'user': 'test'} (glob)
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', ('p1': '47d2a3944de8b013de3be9578e8e344ea2e6c097', )?'user': 'blah'} (re)
+  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '\d+ \d+', ('p1': '1f0dee641bb7258c56bd60e93edfa2405381c41e', )?'user': 'test'} (re)
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '\d+ \d+', ('p1': '7c3bad9141dcb46ff89abf5f61856facd56e476c', )?'user': 'test'} (re)
+  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '\d+ \d+', ('p1': '4538525df7e2b9f09423636c61ef63a4cb872a2d', )?'user': 'test'} (re)
   bb5e90a7ea1f3b4b38b23150a4a597b6146d70ef 6e8148413dd541855b72a920a90c06fca127c7e7 0 {'date': '*', 'user': 'test'} (glob)
   00ded550b1e28bba454bd34cec1269d22cf3ef25 aa96dc3f04c2c2341fe6880aeb6dc9fbffff9ef9 8ee176ff1d4b2034ce51e3efc579c2de346b631d 0 {'date': '**', 'user': 'test'} (glob)
   $ hg log -G
@@ -174,10 +174,10 @@
   abort: Can't use multiple successors for multiple precursors
   [255]
   $ hg debugobsolete
-  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', 'user': 'blah'} (glob)
-  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '*', 'user': 'test'} (glob)
-  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '*', 'user': 'test'} (glob)
-  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '*', 'user': 'test'} (glob)
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', ('p1': '47d2a3944de8b013de3be9578e8e344ea2e6c097', )?'user': 'blah'} (re)
+  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '\d+ \d+', ('p1': '1f0dee641bb7258c56bd60e93edfa2405381c41e', )?'user': 'test'} (re)
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '\d+ \d+', ('p1': '7c3bad9141dcb46ff89abf5f61856facd56e476c', )?'user': 'test'} (re)
+  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '\d+ \d+', ('p1': '4538525df7e2b9f09423636c61ef63a4cb872a2d', )?'user': 'test'} (re)
   bb5e90a7ea1f3b4b38b23150a4a597b6146d70ef 6e8148413dd541855b72a920a90c06fca127c7e7 0 {'date': '*', 'user': 'test'} (glob)
   00ded550b1e28bba454bd34cec1269d22cf3ef25 aa96dc3f04c2c2341fe6880aeb6dc9fbffff9ef9 8ee176ff1d4b2034ce51e3efc579c2de346b631d 0 {'date': '**', 'user': 'test'} (glob)
 
@@ -186,15 +186,39 @@
   $ hg prune 'desc("add cc")' 'desc("add bb")' -s 'desc("add nB")'
   2 changesets pruned
   $ hg debugobsolete
-  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', 'user': 'blah'} (glob)
-  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '*', 'user': 'test'} (glob)
-  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '*', 'user': 'test'} (glob)
-  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '*', 'user': 'test'} (glob)
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', ('p1': '47d2a3944de8b013de3be9578e8e344ea2e6c097', )?'user': 'blah'} (re)
+  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '\d+ \d+', ('p1': '1f0dee641bb7258c56bd60e93edfa2405381c41e', )?'user': 'test'} (re)
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '\d+ \d+', ('p1': '7c3bad9141dcb46ff89abf5f61856facd56e476c', )?'user': 'test'} (re)
+  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '\d+ \d+', ('p1': '4538525df7e2b9f09423636c61ef63a4cb872a2d', )?'user': 'test'} (re)
   bb5e90a7ea1f3b4b38b23150a4a597b6146d70ef 6e8148413dd541855b72a920a90c06fca127c7e7 0 {'date': '*', 'user': 'test'} (glob)
   00ded550b1e28bba454bd34cec1269d22cf3ef25 aa96dc3f04c2c2341fe6880aeb6dc9fbffff9ef9 8ee176ff1d4b2034ce51e3efc579c2de346b631d 0 {'date': '**', 'user': 'test'} (glob)
   814c38b95e72dfe2cbf675b1649ea9d780c89a80 6f6f25e4f748d8f7571777e6e168aedf50350ce8 0 {'date': '*', 'user': 'test'} (glob)
   354011cd103f58bbbd9091a3cee6d6a6bd0dddf7 6f6f25e4f748d8f7571777e6e168aedf50350ce8 0 {'date': '*', 'user': 'test'} (glob)
 
+two old, two new with --biject
+
+  $ hg up 0
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  $ mkcommit n1
+  created new head
+  $ mkcommit n2
+
+  $ hg prune 'desc("add n1")::desc("add n2")' -s 'desc("add nD")::desc("add nE")' --biject
+  2 changesets pruned
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  working directory now at 1f0dee641bb7
+  $ hg debugobsolete
+  9d206ffc875e1bc304590549be293be36821e66c 0 {'date': '314064000 0', ('p1': '47d2a3944de8b013de3be9578e8e344ea2e6c097', )?'user': 'blah'} (re)
+  7c3bad9141dcb46ff89abf5f61856facd56e476c 0 {'date': '\d+ \d+', ('p1': '1f0dee641bb7258c56bd60e93edfa2405381c41e', )?'user': 'test'} (re)
+  4538525df7e2b9f09423636c61ef63a4cb872a2d 0 {'date': '\d+ \d+', ('p1': '7c3bad9141dcb46ff89abf5f61856facd56e476c', )?'user': 'test'} (re)
+  47d2a3944de8b013de3be9578e8e344ea2e6c097 0 {'date': '\d+ \d+', ('p1': '4538525df7e2b9f09423636c61ef63a4cb872a2d', )?'user': 'test'} (re)
+  bb5e90a7ea1f3b4b38b23150a4a597b6146d70ef 6e8148413dd541855b72a920a90c06fca127c7e7 0 {'date': '*', 'user': 'test'} (glob)
+  00ded550b1e28bba454bd34cec1269d22cf3ef25 aa96dc3f04c2c2341fe6880aeb6dc9fbffff9ef9 8ee176ff1d4b2034ce51e3efc579c2de346b631d 0 {'date': '**', 'user': 'test'} (glob)
+  814c38b95e72dfe2cbf675b1649ea9d780c89a80 6f6f25e4f748d8f7571777e6e168aedf50350ce8 0 {'date': '* *', 'user': 'test'} (glob)
+  354011cd103f58bbbd9091a3cee6d6a6bd0dddf7 6f6f25e4f748d8f7571777e6e168aedf50350ce8 0 {'date': '* *', 'user': 'test'} (glob)
+  cb7f8f706a6532967b98cf8583a81baab79a0fa7 8ee176ff1d4b2034ce51e3efc579c2de346b631d 0 {'date': '* *', 'user': 'test'} (glob)
+  21b6f2f1cece8c10326e575dd38239189d467190 6e8148413dd541855b72a920a90c06fca127c7e7 0 {'date': '* *', 'user': 'test'} (glob)
+
 test hg prune -B bookmark
 yoinked from test-mq-strip.t
 
@@ -232,3 +256,28 @@
   abort: unknown revision '2702dd0c91e7'!
   [255]
 
+  $ hg debugobsstorestat
+  markers total:                      4
+      for known precursors:           4
+      with parents data:              [04] (re)
+  markers with no successors:         4
+                1 successors:         0
+                2 successors:         0
+      more than 2 successors:         0
+  average meta length:               (27|71) (re)
+      available  keys:
+                 date:                4
+                   p1:                [04] (re)
+                 user:                4
+  disconnected clusters:              4
+          any known node:             4
+          smallest length:            1
+          longer length:              1
+          median length:              1
+          mean length:                1
+      using parents data:             [42] (re)
+          any known node:             4
+          smallest length:            1
+          longer length:              [13] (re)
+          median length:              [13] (re)
+          mean length:                [12] (re)
--- a/tests/test-qsync.t	Mon Mar 03 19:27:42 2014 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,239 +0,0 @@
-  $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [phases]
-  > publish = False
-  > [alias]
-  > qlog = log --template='{rev} - {node|short} {desc} ({phase})\n'
-  > mqlog = log --mq --template='{rev} - {desc}\n'
-  > [diff]
-  > git = 1
-  > unified = 0
-  > [extensions]
-  > hgext.rebase=
-  > hgext.graphlog=
-  > hgext.mq=
-  > EOF
-  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
-  $ echo "qsync=$(echo $(dirname $TESTDIR))/hgext/qsync.py" >> $HGRCPATH
-  $ mkcommit() {
-  >    echo "$1" > "$1"
-  >    hg add "$1"
-  >    hg ci -m "add $1"
-  > }
-
-basic sync
-
-  $ hg init local
-  $ cd local
-  $ hg qinit -c
-  $ hg qci -m "initial commit"
-  $ mkcommit a
-  $ mkcommit b
-  $ hg qlog
-  1 - 7c3bad9141dc add b (draft)
-  0 - 1f0dee641bb7 add a (draft)
-  $ hg qsync -a
-  $ hg mqlog
-  2 - qsubmit commit
-  
-  * DEFAULT-add_a.diff ready for review
-  * DEFAULT-add_b.diff ready for review
-  1 - qsubmit init
-  0 - initial commit
-
-basic sync II
-
-  $ hg init local
-  $ cd local
-  $ hg qinit -c
-  $ hg qci -m "initial commit"
-  $ mkcommit a
-  $ mkcommit b
-  $ hg qlog
-  1 - 7c3bad9141dc add b (draft)
-  0 - 1f0dee641bb7 add a (draft)
-  $ hg qsync -a
-  $ hg mqlog
-  2 - qsubmit commit
-  
-  * DEFAULT-add_a.diff ready for review
-  * DEFAULT-add_b.diff ready for review
-  1 - qsubmit init
-  0 - initial commit
-
-  $ echo "b" >> b
-  $ hg amend
-  $ hg qsync -a
-  $ hg mqlog
-  3 - qsubmit commit
-  
-  * DEFAULT-add_b.diff ready for review
-  2 - qsubmit commit
-  
-  * DEFAULT-add_a.diff ready for review
-  * DEFAULT-add_b.diff ready for review
-  1 - qsubmit init
-  0 - initial commit
-
-  $ hg up -r 0
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ echo "a" >> a
-  $ hg amend
-  1 new unstable changesets
-  $ hg graft -O 3
-  grafting revision 3
-  $ hg qsync -a
-  $ hg mqlog
-  4 - qsubmit commit
-  
-  * DEFAULT-add_a.diff ready for review
-  * DEFAULT-add_b.diff ready for review
-  3 - qsubmit commit
-  
-  * DEFAULT-add_b.diff ready for review
-  2 - qsubmit commit
-  
-  * DEFAULT-add_a.diff ready for review
-  * DEFAULT-add_b.diff ready for review
-  1 - qsubmit init
-  0 - initial commit
-
-sync with published changeset
-
-  $ hg init local
-  $ cd local
-  $ hg qinit -c
-  $ hg qci -m "initial commit"
-  $ mkcommit a
-  $ mkcommit b
-  $ hg qlog
-  1 - 7c3bad9141dc add b (draft)
-  0 - 1f0dee641bb7 add a (draft)
-  $ hg qsync -a
-  $ hg mqlog
-  2 - qsubmit commit
-  
-  * DEFAULT-add_a.diff ready for review
-  * DEFAULT-add_b.diff ready for review
-  1 - qsubmit init
-  0 - initial commit
-
-  $ hg phase -p 0
-  $ hg qsync -a
-  $ hg mqlog
-  3 - qsubmit commit
-  
-  * applied DEFAULT-add_a.diff
-  2 - qsubmit commit
-  
-  * DEFAULT-add_a.diff ready for review
-  * DEFAULT-add_b.diff ready for review
-  1 - qsubmit init
-  0 - initial commit
-
-  $ mkcommit c
-  $ mkcommit d
-  $ hg qsync -a
-  $ hg mqlog
-  4 - qsubmit commit
-  
-  * DEFAULT-add_c.diff ready for review
-  * DEFAULT-add_d.diff ready for review
-  3 - qsubmit commit
-  
-  * applied DEFAULT-add_a.diff
-  2 - qsubmit commit
-  
-  * DEFAULT-add_a.diff ready for review
-  * DEFAULT-add_b.diff ready for review
-  1 - qsubmit init
-  0 - initial commit
-
-  $ cd ..
-  $ hg qclone -U local local2
-  $ cd local2
-  $ hg qlog
-  3 - 47d2a3944de8 add d (draft)
-  2 - 4538525df7e2 add c (draft)
-  1 - 7c3bad9141dc add b (draft)
-  0 - 1f0dee641bb7 add a (public)
-  $ hg strip -n 1 --no-backup
-  $ hg up
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg up --mq 4
-  6 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg qseries
-  DEFAULT-add_b.diff
-  DEFAULT-add_c.diff
-  DEFAULT-add_d.diff
-  $ hg qpush
-  applying DEFAULT-add_b.diff
-  now at: DEFAULT-add_b.diff
-  $ hg qfinish -a
-  $ hg phase -p .
-  $ hg qci -m "applied DEFAULT-add_b.diff"
-  $ cd ../local
-  $ hg pull ../local2
-  pulling from ../local2
-  searching for changes
-  no changes found
-  $ hg pull --mq ../local2/.hg/patches
-  pulling from ../local2/.hg/patches
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 1 changesets with 1 changes to 1 files
-  (run 'hg update' to get a working copy)
-  $ hg qlog
-  3 - 47d2a3944de8 add d (draft)
-  2 - 4538525df7e2 add c (draft)
-  1 - 7c3bad9141dc add b (public)
-  0 - 1f0dee641bb7 add a (public)
-  $ hg mqlog -l 1
-  5 - applied DEFAULT-add_b.diff
-  $ hg status --mq --rev tip:-2
-  M series
-  A DEFAULT-add_b.diff
-  $ hg qsync -a
-  $ hg status --mq --rev tip:-2
-  M qsubmitdata
-  $ hg mqlog -l 1
-  6 - qsubmit commit
-  
-  * applied DEFAULT-add_b.diff
-  $ hg qsync -a
-  abort: Nothing changed
-  [255]
-
-mixed sync
-
-  $ hg init local
-  $ cd local
-  $ hg qinit -c
-  $ mkcommit a
-  $ mkcommit b
-  $ hg qlog
-  1 - 7c3bad9141dc add b (draft)
-  0 - 1f0dee641bb7 add a (draft)
-  $ hg qsync -a
-  $ hg mqlog
-  1 - qsubmit commit
-  
-  * DEFAULT-add_a.diff ready for review
-  * DEFAULT-add_b.diff ready for review
-  0 - qsubmit init
-  $ hg phase -p 0
-  $ echo "b" >> b
-  $ hg amend
-  $ hg qsync -a
-  $ hg mqlog -l 1
-  2 - qsubmit commit
-  
-  * applied DEFAULT-add_a.diff
-  * DEFAULT-add_b.diff ready for review
-
--- a/tests/test-stabilize-result.t	Mon Mar 03 19:27:42 2014 -0800
+++ b/tests/test-stabilize-result.t	Mon Mar 03 19:28:43 2014 -0800
@@ -171,7 +171,7 @@
   o  0:07f494440405@default(public) bk:[] adda
   
 
-Stabilize conflicting changesets with same parent
+Stabilize divergenent changesets with same parent
 =================================================
 
   $ rm a.orig
@@ -291,7 +291,7 @@
   +conflict
   +babar
 
-Check conflicting during conflicting resolution
+Check conflict during divergence resolution
 -------------------------------------------------
 
   $ hg up --hidden 15
--- a/tests/test-tutorial.t	Mon Mar 03 19:27:42 2014 -0800
+++ b/tests/test-tutorial.t	Mon Mar 03 19:28:43 2014 -0800
@@ -402,6 +402,11 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  OBSEXC: computing relevant nodes
+  OBSEXC: computing markers relevant to 5 nodes
+  OBSEXC: encoding 6 markers
+  OBSEXC: sending 1 pushkey payload (609 bytes)
+  OBSEXC: DONE
 
 for simplicity sake we get the bathroom change in line again
 
@@ -712,6 +717,11 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files (+1 heads)
+  OBSEXC: computing relevant nodes
+  OBSEXC: computing markers relevant to 7 nodes
+  OBSEXC: encoding 10 markers
+  OBSEXC: sending 1 pushkey payload (1004 bytes)
+  OBSEXC: DONE
 
 remote get a warning that current working directory is based on an obsolete changeset
 
@@ -826,7 +836,7 @@
 Handling Divergent amend
 ----------------------------------------------
 
-We can detect that multiple diverging/conflicting amendments have been made.
+We can detect that multiple diverging amendments have been made.
 The `evolve` command can solve this situation. But all corner case are not
 handled now.