# HG changeset patch # User Pierre-Yves David # Date 1351003991 -7200 # Node ID 8945a62f9096a5911093cbb436ef2f17a0e4358a # Parent 95089805c3fc69f9f6f46439f42612680f9ca641# Parent 89c8550019d09f14374ce6e54ccde61e4aa4f041 merge with stable diff -r 95089805c3fc -r 8945a62f9096 hgext/evolve.py --- 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: + # stand for Last Successors Sets + # it contains the list of all last successors for the current node. + lss = [] + for mark in markersfor[node]: + # 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:: + + + + + + + + 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): diff -r 95089805c3fc -r 8945a62f9096 hgext/qsync.py --- 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: diff -r 95089805c3fc -r 8945a62f9096 tests/test-obsolete-divergent.t --- /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 .. diff -r 95089805c3fc -r 8945a62f9096 tests/test-obsolete.t --- 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 diff -r 95089805c3fc -r 8945a62f9096 tests/test-stabilize-result.t --- 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 && diff -r 95089805c3fc -r 8945a62f9096 tests/test-uncommit.t --- 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