changeset 589:8945a62f9096

merge with stable
author Pierre-Yves David <pierre-yves.david@logilab.fr>
date Tue, 23 Oct 2012 16:53:11 +0200
parents 95089805c3fc (current diff) 89c8550019d0 (diff)
children 02cadd3dc9f4
files hgext/evolve.py
diffstat 6 files changed, 696 insertions(+), 93 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/evolve.py	Sun Oct 14 16:23:25 2012 +0200
+++ b/hgext/evolve.py	Tue Oct 23 16:53:11 2012 +0200
@@ -19,6 +19,10 @@
     - improves some aspect of the early implementation in 2.3
 '''
 
+testedwith = '2.3 2.3.1 2.3.2'
+buglink = 'https://bitbucket.org/marmoute/mutable-history/issues'
+
+
 import random
 
 from mercurial import util
@@ -28,7 +32,16 @@
     if not obsolete._enabled:
         obsolete._enabled = True
 except ImportError:
-    raise util.Abort('Obsolete extension requires Mercurial 2.3 (or later)')
+    raise util.Abort('Evolve extension requires Mercurial 2.3 (or later)')
+
+try:
+    getattr(obsolete, 'getrevs') # 2.4 specific
+    raise util.Abort('Your version of Mercurial is too recent for this '
+                     'version of evolve',
+                     hint="upgrade your evolve")
+except AttributeError:
+    pass
+
 
 from mercurial import bookmarks
 from mercurial import cmdutil
@@ -324,7 +337,7 @@
 
 # there is two kind of trouble not handled by core right now:
 # - latecomer: (successors for public changeset)
-# - conflicting: (two changeset try to succeed to the same precursors)
+# - divergent: (two changeset try to succeed to the same precursors)
 #
 # This section add support for those two addition trouble
 #
@@ -343,25 +356,25 @@
     query = '%ld - obsolete() - public()'
     return set(repo.revs(query, candidates))
 
-@cachefor('conflicting')
-def _computeconflictingset(repo):
+@cachefor('divergent')
+def _computedivergentset(repo):
     """the set of rev trying to obsolete public revision"""
-    conflicting = set()
+    divergent = set()
     obsstore = repo.obsstore
     newermap = {}
     for ctx in repo.set('(not public()) - obsolete()'):
-        prec = obsstore.successors.get(ctx.node(), ())
-        toprocess = set(prec)
+        mark = obsstore.successors.get(ctx.node(), ())
+        toprocess = set(mark)
         while toprocess:
             prec = toprocess.pop()[0]
             if prec not in newermap:
-                newermap[prec] = newerversion(repo, prec)
-            newer = [n for n in newermap[prec] if n] # filter kill
+                successorssets(repo, prec, newermap)
+            newer = [n for n in newermap[prec] if n]
             if len(newer) > 1:
-                conflicting.add(ctx.rev())
+                divergent.add(ctx.rev())
                 break
-        toprocess.update(obsstore.successors.get(prec, ()))
-    return conflicting
+            toprocess.update(obsstore.successors.get(prec, ()))
+    return divergent
 
 ### changectx method
 
@@ -373,11 +386,12 @@
     return ctx.rev() in getobscache(ctx._repo, 'latecomer')
 
 @eh.addattr(context.changectx, 'conflicting')
-def conflicting(ctx):
-    """is the changeset conflicting (Try to succeed to public change)"""
+@eh.addattr(context.changectx, 'divergent')
+def divergent(ctx):
+    """is the changeset divergent (Try to succeed to public change)"""
     if ctx.node() is None:
         return False
-    return ctx.rev() in getobscache(ctx._repo, 'conflicting')
+    return ctx.rev() in getobscache(ctx._repo, 'divergent')
 
 ### revset symbol
 
@@ -391,24 +405,17 @@
     return [r for r in subset if r in lates]
 
 @eh.revset('conflicting')
-def revsetconflicting(repo, subset, x):
-    """``conflicting()``
-    Changesets marked as successors of a same changeset.
-    """
-    args = revset.getargs(x, 0, 0, 'conflicting takes no arguments')
-    conf = getobscache(repo, 'conflicting')
-    return [r for r in subset if r in conf]
-
 @eh.revset('divergent')
 def revsetdivergent(repo, subset, x):
     """``divergent()``
     Changesets marked as successors of a same changeset.
     """
     args = revset.getargs(x, 0, 0, 'divergent takes no arguments')
-    conf = getobscache(repo, 'conflicting')
+    conf = getobscache(repo, 'divergent')
     return [r for r in subset if r in conf]
 
 
+
 ### Discovery wrapping
 
 @eh.wrapfunction(discovery, 'checkheads')
@@ -425,8 +432,8 @@
         if ctx.latecomer():
             raise util.Abort(_("push includes a latecomer changeset: %s!")
                              % ctx)
-        if ctx.conflicting():
-            raise util.Abort(_("push includes a conflicting changeset: %s!")
+        if ctx.divergent():
+            raise util.Abort(_("push includes a divergent changeset: %s!")
                              % ctx)
     return orig(repo, remote, outgoing, *args, **kwargs)
 
@@ -498,15 +505,15 @@
 def troubles(ctx):
     """Return a tuple listing all the troubles that affect a changeset
 
-    Troubles may be "unstable", "latecomer" or "conflicting".
+    Troubles may be "unstable", "latecomer" or "divergent".
     """
     troubles = []
     if ctx.unstable():
         troubles.append('unstable')
     if ctx.latecomer():
         troubles.append('latecomer')
-    if ctx.conflicting():
-        troubles.append('conflicting')
+    if ctx.divergent():
+        troubles.append('divergent')
     return tuple(troubles)
 
 ### Troubled revset symbol
@@ -517,7 +524,7 @@
     Changesets with troubles.
     """
     _ = revset.getargs(x, 0, 0, 'troubled takes no arguments')
-    return repo.revs('%ld and (unstable() + latecomer() + conflicting())',
+    return repo.revs('%ld and (unstable() + latecomer() + divergent())',
                      subset)
 
 
@@ -597,29 +604,123 @@
             cs.add(sr)
     return cs
 
+nodemod = node
+def successorssets(repo, initialnode, cache=None):
+    """Return the newer version of an obsolete changeset"""
+
+    # prec -> markers mapping
+    markersfor = repo.obsstore.precursors
+
+    # Stack of node need to know the last successors set
+    toproceed = [initialnode]
+    # set version of toproceed for fast loop detection
+    stackedset = set(toproceed)
+    if cache is None:
+        cache = {}
+    while toproceed:
+        # work on the last node of the stack
+        node = toproceed[-1]
+        if node in cache:
+            # We already have a value for it.
+            # Keep working on something else.
+            stackedset.remove(toproceed.pop())
+        elif node not in markersfor:
+            # The node is not obsolete.
+            # This mean it is its own last successors.
+            if node in repo:
+                # We have a valid last successors.
+                cache[node] = [(node,)]
+            else:
+                # final obsolete version is unknown locally.
+                # Do not count that as a valid successors
+                cache[node] = []
+        else:
+            # <lss> stand for Last Successors Sets
+            # it contains the list of all last successors for the current node.
+            lss = []
+            for mark in markersfor[node]:
+                # <mlss> stand for Marker Last Successors Sets
+                # it contains the list of last successors set introduced by
+                # this marker.
+                mlss = [[]]
+                # iterate over possible multiple successors
+                for suc in mark[1]:
+                    if suc not in cache:
+                        # We do not know the last successors of that yet.
+                        if suc in stackedset:
+                            # Loop detected!
+                            #
+                            # we won't be able to ever compute a proper last
+                            # successors the naive and simple approve is to
+                            # consider it killed
+                            cache[suc] = []
+                        else:
+                            # Add the successor to the stack and break the next
+                            # iteration will work on this successors and the
+                            # algorithm will eventually process the current
+                            # node again.
+                            toproceed.append(suc)
+                            stackedset.add(suc)
+                            break
+                    # if we did not break, we can extend the possible set of
+                    # last successors.
+                    #
+                    # I say "extends" because if the marker have multiple
+                    # successors we have to generate
+                    #
+                    # if successors have multiple successors set (when ther are
+                    # divergent themself), we do a cartesian product of
+                    # possible successors set of already processed successors
+                    # and newly obtains successors set.
+                    newmlss = []
+                    for prefix in mlss:
+                        for suffix in cache[suc]:
+                            newss = list(prefix)
+                            for part in suffix:
+                                # do not duplicated entry in successors set.
+                                # first entry win.
+                                if part not in newss:
+                                    newss.append(part)
+                            newmlss.append(newss)
+                    mlss = newmlss
+                else:
+                    # note: mlss is still empty if the marker was a bare killing
+                    # of this changeset
+                    #
+                    # We extends the list of all possible successors sets with
+                    # successors set continuted by this marker
+                    lss.extend(mlss)
+                    # we use continue here to skip the break right bellow
+                    continue
+                # propagate "nested for" break.
+                # if the nested for exited on break, it did not ran the else
+                # clause and didn't "continue
+                break
+            else:
+                # computation was succesful for *all* marker.
+                # Add computed successors set to the cache
+                # (will be poped from to proceeed) on the new iteration
+                #
+                # We remove successors set that are subset of another one
+                # this fil
+                candsucset = sorted(((len(ss), set(ss), ss) for ss in lss),
+                                    reverse=True)
+                finalsucset = []
+                for cl, cs, css in candsucset:
+                    if not css:
+                        # remove empty successors set
+                        continue
+                    for fs, fss in finalsucset:
+                        if cs.issubset(fs):
+                            break
+                    else:
+                        finalsucset.append((cs, css))
+                finalsucset = [s[1] for s in finalsucset]
+                finalsucset.reverse()
+                cache[node] = finalsucset
+    return cache[initialnode]
 
 
-def newerversion(repo, obs):
-    """Return the newer version of an obsolete changeset"""
-    toproceed = set([(obs,)])
-    # XXX known optimization available
-    newer = set()
-    objectrels = repo.obsstore.precursors
-    while toproceed:
-        current = toproceed.pop()
-        assert len(current) <= 1, 'splitting not handled yet. %r' % current
-        current = [n for n in current if n != nullid]
-        if current:
-            n, = current
-            if n in objectrels:
-                markers = objectrels[n]
-                for mark in markers:
-                    toproceed.add(tuple(mark[1]))
-            else:
-                newer.add(tuple(current))
-        else:
-            newer.add(())
-    return sorted(newer)
 
 
 #####################################################################
@@ -724,19 +825,19 @@
     """display warning is the command resulted in more instable changeset"""
     priorunstables = len(repo.revs('unstable()'))
     priorlatecomers = len(repo.revs('latecomer()'))
-    priorconflictings = len(repo.revs('conflicting()'))
+    priordivergents = len(repo.revs('divergent()'))
     ret = orig(ui, repo, *args, **kwargs)
     # workaround phase stupidity
     phases._filterunknown(ui, repo.changelog, repo._phasecache.phaseroots)
     newunstables = len(repo.revs('unstable()')) - priorunstables
     newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers
-    newconflictings = len(repo.revs('conflicting()')) - priorconflictings
+    newdivergents = len(repo.revs('divergent()')) - priordivergents
     if newunstables > 0:
         ui.warn(_('%i new unstable changesets\n') % newunstables)
     if newlatecomers > 0:
         ui.warn(_('%i new latecomer changesets\n') % newlatecomers)
-    if newconflictings > 0:
-        ui.warn(_('%i new conflicting changesets\n') % newconflictings)
+    if newdivergents > 0:
+        ui.warn(_('%i new divergent changesets\n') % newdivergents)
     return ret
 
 @eh.reposetup
@@ -776,10 +877,10 @@
     ret = orig(ui, repo, *args, **kwargs)
     nbunstable = len(getobscache(repo, 'unstable'))
     nblatecomer = len(getobscache(repo, 'latecomer'))
-    nbconflicting = len(getobscache(repo, 'unstable'))
+    nbdivergent = len(getobscache(repo, 'unstable'))
     write('unstable: %i changesets\n', nbunstable)
     write('latecomer: %i changesets\n', nblatecomer)
-    write('conflicting: %i changesets\n', nbconflicting)
+    write('divergent: %i changesets\n', nbdivergent)
     return ret
 
 
@@ -998,7 +1099,7 @@
 
     - rebase unstable changeset to make it stable again,
     - create proper diff from latecomer changeset,
-    - merge conflicting changeset.
+    - merge divergent changeset.
 
     By default, take the first troubles changeset that looks relevant.
 
@@ -1008,7 +1109,7 @@
       working directory parent revision or one of its descendants and rebase
       it.
 
-    - For conflicting this mean "." if applicable.
+    - For divergent this mean "." if applicable.
 
     With --any, evolve pick any troubled changeset to solve
 
@@ -1041,8 +1142,8 @@
         return _solveunstable(ui, repo, tr, opts['dry_run'])
     elif 'latecomer' in troubles:
         return _solvelatecomer(ui, repo, tr, opts['dry_run'])
-    elif 'conflicting' in troubles:
-        return _solveconflicting(ui, repo, tr, opts['dry_run'])
+    elif 'divergent' in troubles:
+        return _solvedivergent(ui, repo, tr, opts['dry_run'])
     else:
         assert False  # WHAT? unknown troubles
 
@@ -1051,14 +1152,14 @@
     tr = _stabilizableunstable(repo, repo['.'])
     if tr is None:
         wdp = repo['.']
-        if 'conflicting' in wdp.troubles():
+        if 'divergent' in wdp.troubles():
             tr = wdp
     if tr is None and pickany:
         troubled = list(repo.set('unstable()'))
         if not troubled:
             troubled = list(repo.set('latecomer()'))
         if not troubled:
-            troubled = list(repo.set('conflicting()'))
+            troubled = list(repo.set('divergent()'))
         if troubled:
             tr = troubled[0]
 
@@ -1089,13 +1190,13 @@
     if not obs.obsolete():
         obs = orig.parents()[1]
     assert obs.obsolete()
-    newer = newerversion(repo, obs.node())
+    newer = successorssets(repo, obs.node())
     # search of a parent which is not killed
-    while newer == [()]:
+    while not newer or newer == [()]:
         ui.debug("stabilize target %s is plain dead,"
                  " trying to stabilize on its parent")
         obs = obs.parents()[0]
-        newer = newerversion(repo, obs.node())
+        newer = successorssets(repo, obs.node())
     if len(newer) > 1:
         ui.write_err(_("conflict rewriting. can't choose destination\n"))
         return 2
@@ -1231,22 +1332,22 @@
     finally:
         wlock.release()
 
-def _solveconflicting(ui, repo, conflicting, dryrun=False):
-    base, others = conflictingdata(conflicting)
+def _solvedivergent(ui, repo, divergent, dryrun=False):
+    base, others = divergentdata(divergent)
     if len(others) > 1:
         raise util.Abort("We do not handle split yet")
     other = others[0]
-    if conflicting.phase() <= phases.public:
+    if divergent.phase() <= phases.public:
         raise util.Abort("We can't resolve this conflict from the public side")
     if len(other.parents()) > 1:
-        raise util.Abort("conflicting changeset can't be a merge (yet)")
-    if other.p1() not in conflicting.parents():
+        raise util.Abort("divergent changeset can't be a merge (yet)")
+    if other.p1() not in divergent.parents():
         raise util.Abort("parents are not common (not handled yet)")
 
     displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
     ui.status(_('merge:'))
     if not ui.quiet:
-        displayer.show(conflicting)
+        displayer.show(divergent)
     ui.status(_('with: '))
     if not ui.quiet:
         displayer.show(other)
@@ -1254,23 +1355,23 @@
     if not ui.quiet:
         displayer.show(base)
     if dryrun:
-        ui.write('hg update -c %s &&\n' % conflicting)
+        ui.write('hg update -c %s &&\n' % divergent)
         ui.write('hg merge %s &&\n' % other)
         ui.write('hg commit -m "auto merge resolving conflict between '
-                 '%s and %s"&&\n' % (conflicting, other))
+                 '%s and %s"&&\n' % (divergent, other))
         ui.write('hg up -C %s &&\n' % base)
         ui.write('hg revert --all --rev tip &&\n')
         ui.write('hg commit -m "`hg log -r %s --template={desc}`";\n'
-                 % conflicting)
+                 % divergent)
         return
     wlock = lock = None
     try:
         wlock = repo.wlock()
         lock = repo.lock()
-        if conflicting not in repo[None].parents():
+        if divergent not in repo[None].parents():
             repo.ui.status(_('updating to "local" conflict\n'))
-            hg.update(repo, conflicting.rev())
-        repo.ui.note(_('merging conflicting changeset\n'))
+            hg.update(repo, divergent.rev())
+        repo.ui.note(_('merging divergent changeset\n'))
         stats = merge.update(repo,
                              other.node(),
                              branchmerge=True,
@@ -1291,13 +1392,13 @@
 /!\ * hg ci -m "same message as the amended changeset" => new cset Y
 /!\ * hg kill -n Y W Z
 """)
-        tr = repo.transaction('stabilize-conflicting')
+        tr = repo.transaction('stabilize-divergent')
         try:
-            repo.dirstate.setparents(conflicting.node(), node.nullid)
+            repo.dirstate.setparents(divergent.node(), node.nullid)
             oldlen = len(repo)
             amend(ui, repo)
             if oldlen == len(repo):
-                new = conflicting
+                new = divergent
                 # no changes
             else:
                 new = repo['.']
@@ -1310,7 +1411,7 @@
         lockmod.release(lock, wlock)
 
 
-def conflictingdata(ctx):
+def divergentdata(ctx):
     """return base, other part of a conflict
 
     This only return the first one.
@@ -1318,7 +1419,7 @@
     XXX this woobly function won't survive XXX
     """
     for base in ctx._repo.set('reverse(precursors(%d))', ctx):
-        newer = newerversion(ctx._repo, base.node())
+        newer = successorssets(ctx._repo, base.node())
         # drop filter and solution including the original ctx
         newer = [n for n in newer if n and ctx.node() not in n]
         if newer:
@@ -1778,6 +1879,60 @@
     finally:
         lockmod.release(lock, wlock)
 
+if 'debugsuccessorssets' not in commands.table:
+
+    @command('debugsuccessorssets',
+        [],
+        _('[REV]'))
+    def debugsuccessorssets(ui, repo, *revs):
+        """show set of successors for revision
+
+        Successors set of changeset A are a consistent group of revision that
+        succeed to A. Successors set contains non-obsolete changeset only.
+
+        In most case a changeset A have zero (changeset pruned) or a single
+        successors set that contains a single successors (changeset A replacement
+        by A')
+
+        But splitted changeset will result with successors set containing more than
+        a single element. Divergent rewritting will result in multiple successor
+        set.
+
+        result is displayed as follows::
+
+            <rev1>
+                <successors-1A>
+            <rev2>
+                <successors-2A>
+                <successors-2B1> <successors-2B1> <successors-2B1>
+
+        here rev2 have two possible successors sets. One hold three elements.
+
+        add --debug if you want full size node id.
+        """
+        cache = {}
+        s = str
+        if ui.debug:
+            def s(ctx):
+                return ctx.hex()
+        for rev in scmutil.revrange(repo, revs):
+            ctx = repo[rev]
+            if ui.debug():
+                ui.write('%s\n'% ctx.hex())
+                s = node.hex
+            else:
+                ui.write('%s\n'% ctx)
+                s = node.short
+            for ss in successorssets(repo, ctx.node(), cache):
+                if ss:
+                    ui.write('    ')
+                    ui.write(s(ss[0]))
+                    for n in ss[1:]:
+                        ui.write(' ')
+                        ui.write(s(n))
+                ui.write('\n')
+        pass
+
 
 @eh.wrapcommand('graft')
 def graftwrapper(orig, ui, repo, *revs, **kwargs):
--- a/hgext/qsync.py	Sun Oct 14 16:23:25 2012 +0200
+++ b/hgext/qsync.py	Tue Oct 23 16:53:11 2012 +0200
@@ -85,7 +85,7 @@
             except IOError:
                 oldnode = oldfiles[patch_name]
                 evolve = extensions.find('evolve')
-                newnodes = evolve.newerversion(repo, oldnode)
+                newnodes = evolve.successorssets(repo, oldnode)
                 if newnodes:
                     newnodes = [n for n in newnodes if n and n[0] in repo] # remove killing
                 if not newnodes:
@@ -174,7 +174,7 @@
         usedold.add(oldhex)
         oldname = str(oldname)
         oldnode = bin(oldhex)
-        newnodes = evolve.newerversion(repo, oldnode)
+        newnodes = evolve.successorssets(repo, oldnode)
         if newnodes:
             newnodes = [n for n in newnodes if n and n[0] in repo] # remove killing
             if len(newnodes) > 1:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-obsolete-divergent.t	Tue Oct 23 16:53:11 2012 +0200
@@ -0,0 +1,439 @@
+Test file decicated to testing the divergent troubles from obsolete changeset.
+
+This is the most complexe troubles from far so we isolate it in a dedicated
+file.
+
+Enable obsolete
+
+  $ cat >> $HGRCPATH << EOF
+  > [ui]
+  > logtemplate = {rev}:{node|short} {desc}\n
+  > [alias]
+  > debugobsolete = debugobsolete -d '0 0'
+  > [phases]
+  > publish=False
+  > [extensions]
+  > rebase=
+  > EOF
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+
+
+  $ mkcommit() {
+  >    echo "$1" > "$1"
+  >    hg add "$1"
+  >    hg ci -m "$1"
+  > }
+  $ getid() {
+  >    hg id --debug -ir "desc('$1')"
+  > }
+
+setup repo
+
+  $ hg init reference
+  $ cd reference
+  $ mkcommit base
+  $ mkcommit A_0
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit A_1
+  created new head
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit A_2
+  created new head
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ cd ..
+
+
+  $ newcase() {
+  >    hg clone -u 0 -q reference $1
+  >    cd $1
+  > }
+
+direct divergence
+-----------------
+
+A_1 have two direct and divergent successors A_1 and A_1
+
+  $ newcase direct
+  $ hg debugobsolete `getid A_0` `getid A_1`
+  $ hg debugobsolete `getid A_0` `getid A_2`
+  $ hg log -G --hidden
+  o  3:392fd25390da A_2
+  |
+  | o  2:82623d38b9ba A_1
+  |/
+  | x  1:007dc284c1f8 A_0
+  |/
+  @  0:d20a80d4def3 base
+  
+  $ hg debugsuccessorssets 'all()'
+  d20a80d4def3
+      d20a80d4def3
+  007dc284c1f8
+      392fd25390da
+      82623d38b9ba
+  82623d38b9ba
+      82623d38b9ba
+  392fd25390da
+      392fd25390da
+  $ hg log -r 'divergent()'
+  2:82623d38b9ba A_1
+  3:392fd25390da A_2
+
+check that mercurial refuse to push
+
+  $ hg init ../other
+  $ hg push ../other
+  pushing to ../other
+  searching for changes
+  abort: push includes a divergent changeset: 82623d38b9ba!
+  (use 'hg evolve' to get a stable history or --force to ignore warnings)
+  [255]
+
+  $ cd ..
+
+
+indirect divergence with known changeset
+-------------------------------------------
+
+  $ newcase indirect_known
+  $ hg debugobsolete `getid A_0` `getid A_1`
+  $ hg debugobsolete `getid A_0` `getid A_2`
+  $ mkcommit A_3
+  created new head
+  $ hg debugobsolete `getid A_2` `getid A_3`
+  $ hg log -G --hidden
+  @  4:01f36c5a8fda A_3
+  |
+  | x  3:392fd25390da A_2
+  |/
+  | o  2:82623d38b9ba A_1
+  |/
+  | x  1:007dc284c1f8 A_0
+  |/
+  o  0:d20a80d4def3 base
+  
+  $ hg debugsuccessorssets 'all()'
+  d20a80d4def3
+      d20a80d4def3
+  007dc284c1f8
+      01f36c5a8fda
+      82623d38b9ba
+  82623d38b9ba
+      82623d38b9ba
+  392fd25390da
+      01f36c5a8fda
+  01f36c5a8fda
+      01f36c5a8fda
+  $ hg log -r 'divergent()'
+  2:82623d38b9ba A_1
+  4:01f36c5a8fda A_3
+  $ cd ..
+
+
+indirect divergence with known changeset
+-------------------------------------------
+
+  $ newcase indirect_unknown
+  $ hg debugobsolete `getid A_0` aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+  $ hg debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa `getid A_1`
+  $ hg debugobsolete `getid A_0` `getid A_2`
+  $ hg log -G --hidden
+  o  3:392fd25390da A_2
+  |
+  | o  2:82623d38b9ba A_1
+  |/
+  | x  1:007dc284c1f8 A_0
+  |/
+  @  0:d20a80d4def3 base
+  
+  $ hg debugsuccessorssets 'all()'
+  d20a80d4def3
+      d20a80d4def3
+  007dc284c1f8
+      392fd25390da
+      82623d38b9ba
+  82623d38b9ba
+      82623d38b9ba
+  392fd25390da
+      392fd25390da
+  $ hg log -r 'divergent()'
+  2:82623d38b9ba A_1
+  3:392fd25390da A_2
+  $ cd ..
+
+do not take unknown node in account if they are final
+-----------------------------------------------------
+
+  $ newcase final-unknown
+  $ hg debugobsolete `getid A_0` `getid A_1`
+  $ hg debugobsolete `getid A_1` `getid A_2`
+  $ hg debugobsolete `getid A_0` bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+  $ hg debugobsolete bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccccccccccccccccccccccc
+  $ hg debugobsolete `getid A_1` dddddddddddddddddddddddddddddddddddddddd
+
+  $ hg debugsuccessorssets 'desc('A_0')'
+  007dc284c1f8
+      392fd25390da
+
+  $ cd ..
+
+divergence that converge again is not divergence anymore
+-----------------------------------------------------
+
+  $ newcase converged_divergence
+  $ hg debugobsolete `getid A_0` `getid A_1`
+  $ hg debugobsolete `getid A_0` `getid A_2`
+  $ mkcommit A_3
+  created new head
+  $ hg debugobsolete `getid A_1` `getid A_3`
+  $ hg debugobsolete `getid A_2` `getid A_3`
+  $ hg log -G --hidden
+  @  4:01f36c5a8fda A_3
+  |
+  | x  3:392fd25390da A_2
+  |/
+  | x  2:82623d38b9ba A_1
+  |/
+  | x  1:007dc284c1f8 A_0
+  |/
+  o  0:d20a80d4def3 base
+  
+  $ hg debugsuccessorssets 'all()'
+  d20a80d4def3
+      d20a80d4def3
+  007dc284c1f8
+      01f36c5a8fda
+  82623d38b9ba
+      01f36c5a8fda
+  392fd25390da
+      01f36c5a8fda
+  01f36c5a8fda
+      01f36c5a8fda
+  $ hg log -r 'divergent()'
+  $ cd ..
+
+split is not divergences
+-----------------------------
+
+  $ newcase split
+  $ hg debugobsolete `getid A_0` `getid A_1` `getid A_2`
+  $ hg log -G --hidden
+  o  3:392fd25390da A_2
+  |
+  | o  2:82623d38b9ba A_1
+  |/
+  | x  1:007dc284c1f8 A_0
+  |/
+  @  0:d20a80d4def3 base
+  
+  $ hg debugsuccessorssets 'all()'
+  d20a80d4def3
+      d20a80d4def3
+  007dc284c1f8
+      82623d38b9ba 392fd25390da
+  82623d38b9ba
+      82623d38b9ba
+  392fd25390da
+      392fd25390da
+  $ hg log -r 'divergent()'
+
+Even when subsequente rewriting happen
+
+  $ mkcommit A_3
+  created new head
+  $ hg debugobsolete `getid A_1` `getid A_3`
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit A_4
+  created new head
+  $ hg debugobsolete `getid A_2` `getid A_4`
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit A_5
+  created new head
+  $ hg debugobsolete `getid A_4` `getid A_5`
+  $ hg log -G --hidden
+  @  6:e442cfc57690 A_5
+  |
+  | x  5:6a411f0d7a0a A_4
+  |/
+  | o  4:01f36c5a8fda A_3
+  |/
+  | x  3:392fd25390da A_2
+  |/
+  | x  2:82623d38b9ba A_1
+  |/
+  | x  1:007dc284c1f8 A_0
+  |/
+  o  0:d20a80d4def3 base
+  
+  $ hg debugsuccessorssets 'all()'
+  d20a80d4def3
+      d20a80d4def3
+  007dc284c1f8
+      01f36c5a8fda e442cfc57690
+  82623d38b9ba
+      01f36c5a8fda
+  392fd25390da
+      e442cfc57690
+  01f36c5a8fda
+      01f36c5a8fda
+  6a411f0d7a0a
+      e442cfc57690
+  e442cfc57690
+      e442cfc57690
+  $ hg log -r 'divergent()'
+
+Check more complexe obsolescence graft (with divergence)
+
+  $ mkcommit B_0; hg up 0
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg debugobsolete `getid B_0` `getid A_2`
+  $ mkcommit A_7; hg up 0
+  created new head
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit A_8; hg up 0
+  created new head
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg debugobsolete `getid A_5` `getid A_7` `getid A_8`
+  $ mkcommit A_9; hg up 0
+  created new head
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg debugobsolete `getid A_5` `getid A_9`
+  $ hg log -G --hidden
+  o  10:bed64f5d2f5a A_9
+  |
+  | o  9:14608b260df8 A_8
+  |/
+  | o  8:7ae126973a96 A_7
+  |/
+  | x  7:3750ebee865d B_0
+  | |
+  | x  6:e442cfc57690 A_5
+  |/
+  | x  5:6a411f0d7a0a A_4
+  |/
+  | o  4:01f36c5a8fda A_3
+  |/
+  | x  3:392fd25390da A_2
+  |/
+  | x  2:82623d38b9ba A_1
+  |/
+  | x  1:007dc284c1f8 A_0
+  |/
+  @  0:d20a80d4def3 base
+  
+  $ hg debugsuccessorssets 'all()'
+  d20a80d4def3
+      d20a80d4def3
+  007dc284c1f8
+      01f36c5a8fda bed64f5d2f5a
+      01f36c5a8fda 7ae126973a96 14608b260df8
+  82623d38b9ba
+      01f36c5a8fda
+  392fd25390da
+      bed64f5d2f5a
+      7ae126973a96 14608b260df8
+  01f36c5a8fda
+      01f36c5a8fda
+  6a411f0d7a0a
+      bed64f5d2f5a
+      7ae126973a96 14608b260df8
+  e442cfc57690
+      bed64f5d2f5a
+      7ae126973a96 14608b260df8
+  3750ebee865d
+      bed64f5d2f5a
+      7ae126973a96 14608b260df8
+  7ae126973a96
+      7ae126973a96
+  14608b260df8
+      14608b260df8
+  bed64f5d2f5a
+      bed64f5d2f5a
+  $ hg log -r 'divergent()'
+  4:01f36c5a8fda A_3
+  8:7ae126973a96 A_7
+  9:14608b260df8 A_8
+  10:bed64f5d2f5a A_9
+
+fix the divergence
+
+  $ mkcommit A_A; hg up 0
+  created new head
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg debugobsolete `getid A_9` `getid A_A`
+  $ hg debugobsolete `getid A_7` `getid A_A`
+  $ hg debugobsolete `getid A_8` `getid A_A`
+  $ hg log -G --hidden
+  o  11:a139f71be9da A_A
+  |
+  | x  10:bed64f5d2f5a A_9
+  |/
+  | x  9:14608b260df8 A_8
+  |/
+  | x  8:7ae126973a96 A_7
+  |/
+  | x  7:3750ebee865d B_0
+  | |
+  | x  6:e442cfc57690 A_5
+  |/
+  | x  5:6a411f0d7a0a A_4
+  |/
+  | o  4:01f36c5a8fda A_3
+  |/
+  | x  3:392fd25390da A_2
+  |/
+  | x  2:82623d38b9ba A_1
+  |/
+  | x  1:007dc284c1f8 A_0
+  |/
+  @  0:d20a80d4def3 base
+  
+  $ hg debugsuccessorssets 'all()'
+  d20a80d4def3
+      d20a80d4def3
+  007dc284c1f8
+      01f36c5a8fda a139f71be9da
+  82623d38b9ba
+      01f36c5a8fda
+  392fd25390da
+      a139f71be9da
+  01f36c5a8fda
+      01f36c5a8fda
+  6a411f0d7a0a
+      a139f71be9da
+  e442cfc57690
+      a139f71be9da
+  3750ebee865d
+      a139f71be9da
+  7ae126973a96
+      a139f71be9da
+  14608b260df8
+      a139f71be9da
+  bed64f5d2f5a
+      a139f71be9da
+  a139f71be9da
+      a139f71be9da
+  $ hg log -r 'divergent()'
+
+  $ cd ..
+
+
+Subset does not diverge
+------------------------------
+
+Do not report divergent successors-set if it is a subset of another
+successors-set. (report [A,B] not [A] + [A,B])
+
+  $ newcase subset
+  $ hg debugobsolete `getid A_0` `getid A_2`
+  $ hg debugobsolete `getid A_0` `getid A_1` `getid A_2`
+  $ hg debugsuccessorssets 'desc('A_0')'
+  007dc284c1f8
+      82623d38b9ba 392fd25390da
+
+  $ cd ..
--- a/tests/test-obsolete.t	Sun Oct 14 16:23:25 2012 +0200
+++ b/tests/test-obsolete.t	Tue Oct 23 16:53:11 2012 +0200
@@ -376,7 +376,7 @@
   commit: 1 deleted, 2 unknown (clean)
   update: 4 new changesets, 4 branch heads (merge)
   unstable: 1 changesets
-  conflicting: 1 changesets
+  divergent: 1 changesets
   $ qlog
   6
   - 909a0fb57e5d
@@ -497,7 +497,7 @@
   $ hg rebase -s 7 -d 4 2>&1 | grep -v 'whole rebase' 
   nothing to rebase
   $ hg rebase -b '3' -d 4 --traceback
-  2 new conflicting changesets
+  2 new divergent changesets
   $ hg log -G --template='{rev} - {node|short} {desc}\n'
   @  11 - 9468a5f5d8b2 add obsol_d''
   |
@@ -644,7 +644,13 @@
   latecomer: 1 changesets
   $ hg debugobsolete `getid a7a6f2b5d8a5` `getid 50f11e5e3a63`
   $ hg log -r 'conflicting()'
-  changeset:   1[46]:50f11e5e3a63 (re)
+  changeset:   12:6db5e282cb91
+  parent:      10:2033b4e49474
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     add obsol_d'''
+  
+  changeset:   14:50f11e5e3a63
   tag:         tip
   parent:      11:9468a5f5d8b2
   user:        test
--- a/tests/test-stabilize-result.t	Sun Oct 14 16:23:25 2012 +0200
+++ b/tests/test-stabilize-result.t	Tue Oct 23 16:53:11 2012 +0200
@@ -210,7 +210,7 @@
   $ cat a.old >> a
   $ rm a.old
   $ hg amend
-  2 new conflicting changesets
+  2 new divergent changesets
   $ glog
   @  19:3883461cc228@default(draft) bk:[] More addition
   |
@@ -240,7 +240,7 @@
   merge:[19] More addition
   with: [17] More addition
   base: [15] More addition
-  merging conflicting changeset
+  merging divergent changeset
   resolving manifests
   merging a
   0 files updated, 1 files merged, 0 files removed, 0 files unresolved
@@ -297,7 +297,10 @@
   Working directory parent is obsolete
   $ echo 'gotta break' >> a
   $ hg amend
-  1 new conflicting changesets
+  2 new divergent changesets
+  $ hg phase 'divergent()'
+  22: draft
+  24: draft
   $ hg evolve -qn
   hg update -c c956a4b140b6 &&
   hg merge ac6d600735a4 &&
--- a/tests/test-uncommit.t	Sun Oct 14 16:23:25 2012 +0200
+++ b/tests/test-uncommit.t	Tue Oct 23 16:53:11 2012 +0200
@@ -241,7 +241,7 @@
   Working directory parent is obsolete
   $ hg --config extensions.purge= purge
   $ hg uncommit -I 'set:added() and e'
-  2 new conflicting changesets
+  2 new divergent changesets
   $ hg st --copies
   A e
   $ hg st --copies --change .
@@ -285,7 +285,7 @@
   Working directory parent is obsolete
   $ hg --config extensions.purge= purge
   $ hg uncommit --all -X e
-  1 new conflicting changesets
+  1 new divergent changesets
   $ hg st --copies
   M b
   M d