# HG changeset patch # User Pierre-Yves David # Date 1351002989 -7200 # Node ID 8152fedbac65de224b9fb9eed989599769cd6778 # Parent f017211615321085f5bac2cf6b4ea7db4ea86538 evolve: smarter code for divergent changeset This newer version properly handle split now. Code and test copied for future upstream one. diff -r f01721161532 -r 8152fedbac65 hgext/evolve.py --- a/hgext/evolve.py Tue Oct 23 15:44:24 2012 +0200 +++ b/hgext/evolve.py Tue Oct 23 16:36:29 2012 +0200 @@ -537,17 +537,17 @@ 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: divergent.add(ctx.rev()) break - toprocess.update(obsstore.successors.get(prec, ())) + toprocess.update(obsstore.successors.get(prec, ())) return divergent ### changectx method @@ -815,6 +815,122 @@ 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): @@ -1681,7 +1797,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: @@ -2141,6 +2257,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 f01721161532 -r 8152fedbac65 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:36:29 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 f01721161532 -r 8152fedbac65 tests/test-obsolete.t --- a/tests/test-obsolete.t Tue Oct 23 15:44:24 2012 +0200 +++ b/tests/test-obsolete.t Tue Oct 23 16:36:29 2012 +0200 @@ -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 f01721161532 -r 8152fedbac65 tests/test-stabilize-result.t --- a/tests/test-stabilize-result.t Tue Oct 23 15:44:24 2012 +0200 +++ b/tests/test-stabilize-result.t Tue Oct 23 16:36:29 2012 +0200 @@ -297,7 +297,10 @@ Working directory parent is obsolete $ echo 'gotta break' >> a $ hg amend - 1 new divergent changesets + 2 new divergent changesets + $ hg phase 'divergent()' + 22: draft + 24: draft $ hg evolve -qn hg update -c c956a4b140b6 && hg merge ac6d600735a4 &&