Mercurial > evolve
changeset 491:6989d8fe4ed2
merge evolve and obsolete
author | Pierre-Yves David <pierre-yves.david@logilab.fr> |
---|---|
date | Fri, 24 Aug 2012 10:44:23 +0200 |
parents | 8096833e9226 |
children | 7ecd41520dae |
files | hgext/evolve.py hgext/obsolete.py hgext/qsync.py tests/test-amend.t tests/test-corrupt.t tests/test-evolve.t tests/test-obsolete-push.t tests/test-obsolete-rebase.t tests/test-obsolete.t tests/test-qsync.t tests/test-stabilize-order.t tests/test-stabilize-result.t tests/test-uncommit.t |
diffstat | 13 files changed, 1119 insertions(+), 1145 deletions(-) [+] |
line wrap: on
line diff
--- a/hgext/evolve.py Thu Aug 23 18:21:18 2012 +0200 +++ b/hgext/evolve.py Fri Aug 24 10:44:23 2012 +0200 @@ -7,25 +7,1096 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -'''a set of commands to handle changeset mutation''' +'''Extends Mercurial feature related to Changeset Evolution + +This extension Provide several command tommutate history and deal with issue it may raise. + +It also: + + - enable the "Changeset Obsolescence" feature of mercurial, + - alter core command and extension that rewrite history to use this feature, + - improve some aspect of the early implementation in 2.3 +''' import random +from mercurial import util + +try: + from mercurial import obsolete + if not obsolete._enabled: + obsolete._enabled = True +except ImportError: + raise util.Abort('Obsolete extension requires Mercurial 2.3 (or later)') + from mercurial import bookmarks from mercurial import cmdutil from mercurial import commands from mercurial import context from mercurial import copies +from mercurial import discovery from mercurial import error from mercurial import extensions +from mercurial import hg +from mercurial import localrepo from mercurial import merge from mercurial import node from mercurial import phases +from mercurial import revset from mercurial import scmutil -from mercurial import util +from mercurial import templatekw from mercurial.i18n import _ from mercurial.commands import walkopts, commitopts, commitopts2 -from mercurial import hg +from mercurial.node import nullid + + + +# This extension contains the following code +# +# - Extension Helper code +# - Obsolescence cache +# - ... +# - Older format compat + + + +##################################################################### +### Extension helper ### +##################################################################### + +class exthelper(object): + """Helper for modular extension setup + + A single helper should be instanciated for each extension. Helper + methods are then used as decorator for various purpose. + + All decorators return the original function and may be chained. + """ + + def __init__(self): + self._uicallables = [] + self._extcallables = [] + self._repocallables = [] + self._revsetsymbols = [] + self._templatekws = [] + self._commandwrappers = [] + self._extcommandwrappers = [] + self._functionwrappers = [] + self._duckpunchers = [] + + def final_uisetup(self, ui): + """Method to be used as the extension uisetup + + The following operations belong here: + + - Changes to ui.__class__ . The ui object that will be used to run the + command has not yet been created. Changes made here will affect ui + objects created after this, and in particular the ui that will be + passed to runcommand + - Command wraps (extensions.wrapcommand) + - Changes that need to be visible to other extensions: because + initialization occurs in phases (all extensions run uisetup, then all + run extsetup), a change made here will be visible to other extensions + during extsetup + - Monkeypatch or wrap function (extensions.wrapfunction) of dispatch + module members + - Setup of pre-* and post-* hooks + - pushkey setup + """ + for cont, funcname, func in self._duckpunchers: + setattr(cont, funcname, func) + for command, wrapper in self._commandwrappers: + extensions.wrapcommand(commands.table, command, wrapper) + for cont, funcname, wrapper in self._functionwrappers: + extensions.wrapfunction(cont, funcname, wrapper) + for c in self._uicallables: + c(ui) + + def final_extsetup(self, ui): + """Method to be used as a the extension extsetup + + The following operations belong here: + + - Changes depending on the status of other extensions. (if + extensions.find('mq')) + - Add a global option to all commands + - Register revset functions + """ + knownexts = {} + for name, symbol in self._revsetsymbols: + revset.symbols[name] = symbol + for name, kw in self._templatekws: + templatekw.keywords[name] = kw + for ext, command, wrapper in self._extcommandwrappers: + if ext not in knownexts: + e = extensions.find(ext) + if e is None: + raise util.Abort('extension %s not found' % ext) + knownexts[ext] = e.cmdtable + extensions.wrapcommand(knownexts[ext], commands, wrapper) + for c in self._extcallables: + c(ui) + + def final_reposetup(self, ui, repo): + """Method to be used as a the extension reposetup + + The following operations belong here: + + - All hooks but pre-* and post-* + - Modify configuration variables + - Changes to repo.__class__, repo.dirstate.__class__ + """ + for c in self._repocallables: + c(ui, repo) + + def uisetup(self, call): + """Decorated function will be executed during uisetup + + example:: + + @eh.uisetup + def setupbabar(ui): + print 'this is uisetup!' + """ + self._uicallables.append(call) + return call + + def extsetup(self, call): + """Decorated function will be executed during extsetup + + example:: + + @eh.extsetup + def setupcelestine(ui): + print 'this is extsetup!' + """ + self._uicallables.append(call) + return call + + def reposetup(self, call): + """Decorated function will be executed during reposetup + + example:: + + @eh.reposetup + def setupzephir(ui, repo): + print 'this is reposetup!' + """ + self._repocallables.append(call) + return call + + def revset(self, symbolname): + """Decorated function is a revset symbol + + The name of the symbol must be given as the decorator argument. + The symbol is added during `extsetup`. + + example:: + + @eh.revset('hidden') + def revsetbabar(repo, subset, x): + args = revset.getargs(x, 0, 0, 'babar accept no argument') + return [r for r in subset if 'babar' in repo[r].description()] + """ + def dec(symbol): + self._revsetsymbols.append((symbolname, symbol)) + return symbol + return dec + + + def templatekw(self, keywordname): + """Decorated function is a revset keyword + + The name of the keyword must be given as the decorator argument. + The symbol is added during `extsetup`. + + example:: + + @eh.templatekw('babar') + def kwbabar(ctx): + return 'babar' + """ + def dec(keyword): + self._templatekws.append((keywordname, keyword)) + return keyword + return dec + + def wrapcommand(self, command, extension=None): + """Decorated function is a command wrapper + + The name of the command must be given as the decorator argument. + The wrapping is installed during `uisetup`. + + If the second option `extension` argument is provided, the wrapping + will be applied in the extension commandtable. This argument must be a + string that will be searched using `extension.find` if not found and + Abort error is raised. If the wrapping applies to an extension, it is + installed during `extsetup` + + example:: + + @eh.wrapcommand('summary') + def wrapsummary(orig, ui, repo, *args, **kwargs): + ui.note('Barry!') + return orig(ui, repo, *args, **kwargs) + + """ + def dec(wrapper): + if extension is None: + self._commandwrappers.append((command, wrapper)) + else: + self._extcommandwrappers.append((extension, command, wrapper)) + return wrapper + return dec + + def wrapfunction(self, container, funcname): + """Decorated function is a function wrapper + + This function takes two arguments, the container and the name of the + function to wrap. The wrapping is performed during `uisetup`. + (there is no extension support) + + example:: + + @eh.function(discovery, 'checkheads') + def wrapfunction(orig, *args, **kwargs): + ui.note('His head smashed in and his heart cut out') + return orig(*args, **kwargs) + """ + def dec(wrapper): + self._functionwrappers.append((container, funcname, wrapper)) + return wrapper + return dec + + def addattr(self, container, funcname): + """Decorated function is to be added to the container + + This function takes two arguments, the container and the name of the + function to wrap. The wrapping is performed during `uisetup`. + + example:: + + @eh.function(context.changectx, 'babar') + def babar(ctx): + return 'babar' in ctx.description + """ + def dec(func): + self._duckpunchers.append((container, funcname, func)) + return func + return dec + +eh = exthelper() +uisetup = eh.final_uisetup +extsetup = eh.final_extsetup +reposetup = eh.final_reposetup + +##################################################################### +### Obsolescence Caching Logic ### +##################################################################### + +# Obsolescence related logic can be very slow if we don't have efficient cache. +# +# This section implements a cache mechanism that did not make it into core for +# time reason. It store meaningful set of revision related to obsolescence +# (obsolete, unstabletble ... +# +# Here is: +# +# - Computation of meaningful set, +# - Cache access logic, +# - Cache invalidation logic, +# - revset and ctx using this cache. +# + + +### Computation of meaningful set +# +# Most set can be computed with "simple" revset. + +#: { set name -> function to compute this set } mapping +#: function take a single "repo" argument. +#: +#: Use the `cachefor` decorator to register new cache function +cachefuncs = {} +def cachefor(name): + """Decorator to register a function as computing the cache for a set""" + def decorator(func): + assert name not in cachefuncs + cachefuncs[name] = func + return func + return decorator + +@cachefor('obsolete') +def _computeobsoleteset(repo): + """the set of obsolete revisions""" + obs = set() + nm = repo.changelog.nodemap + for prec in repo.obsstore.precursors: + rev = nm.get(prec) + if rev is not None: + obs.add(rev) + return set(repo.revs('%ld - public()', obs)) + +@cachefor('unstable') +def _computeunstableset(repo): + """the set of non obsolete revisions with obsolete parents""" + return set(repo.revs('(obsolete()::) - obsolete()')) + +@cachefor('suspended') +def _computesuspendedset(repo): + """the set of obsolete parents with non obsolete descendants""" + return set(repo.revs('obsolete() and obsolete()::unstable()')) + +@cachefor('extinct') +def _computeextinctset(repo): + """the set of obsolete parents without non obsolete descendants""" + return set(repo.revs('obsolete() - obsolete()::unstable()')) + +@eh.wrapfunction(obsolete.obsstore, '__init__') +def _initobsstorecache(orig, obsstore, *args, **kwargs): + """add a cache attribute to obsstore""" + obsstore.caches = {} + return orig(obsstore, *args, **kwargs) + +### Cache access + +def getobscache(repo, name): + """Return the set of revision that belong to the <name> set + + Such access may compute the set and cache it for future use""" + if not repo.obsstore: + return () + if name not in repo.obsstore.caches: + repo.obsstore.caches[name] = cachefuncs[name](repo) + return repo.obsstore.caches[name] + +### Cache clean up +# +# To be simple we need to invalidate obsolescence cache when: +# +# - new changeset is added: +# - public phase is changed +# - obsolescence marker are added +# - strip is used a repo + + +def clearobscaches(repo): + """Remove all obsolescence related cache from a repo + + This remove all cache in obsstore is the obsstore already exist on the + repo. + + (We could be smarter here)""" + if 'obsstore' in repo._filecache: + repo.obsstore.caches.clear() + +@eh.wrapfunction(localrepo.localrepository, 'addchangegroup') # new changeset +@eh.wrapfunction(phases, 'retractboundary') # phase movement +@eh.wrapfunction(phases, 'advanceboundary') # phase movement +@eh.wrapfunction(localrepo.localrepository, 'destroyed') # strip +def wrapclearcache(orig, repo, *args, **kwargs): + try: + return orig(repo, *args, **kwargs) + finally: + # we are a bit wide here + # we could restrict to: + # advanceboundary + phase==public + # retractboundary + phase==draft + clearobscaches(repo) + +@eh.wrapfunction(obsolete.obsstore, 'add') # new marker +def clearonadd(orig, obsstore, *args, **kwargs): + try: + return orig(obsstore, *args, **kwargs) + finally: + obsstore.caches.clear() + +### Use the case +# Function in core that could benefic from the cache are overwritten by cache using version + +# changectx method + +@eh.addattr(context.changectx, 'unstable') +def unstable(ctx): + """is the changeset unstable (have obsolete ancestor)""" + if ctx.node() is None: + return False + return ctx.rev() in getobscache(ctx._repo, 'unstable') + + +@eh.addattr(context.changectx, 'extinct') +def extinct(ctx): + """is the changeset extinct by other""" + if ctx.node() is None: + return False + return ctx.rev() in getobscache(ctx._repo, 'extinct') + +# revset + +@eh.revset('obsolete') +def revsetobsolete(repo, subset, x): + """``obsolete()`` + Changeset is obsolete. + """ + args = revset.getargs(x, 0, 0, 'obsolete takes no argument') + obsoletes = getobscache(repo, 'obsolete') + return [r for r in subset if r in obsoletes] + +@eh.revset('unstable') +def revsetunstable(repo, subset, x): + """``unstable()`` + Unstable changesets are non-obsolete with obsolete ancestors. + """ + args = revset.getargs(x, 0, 0, 'unstable takes no arguments') + unstables = getobscache(repo, 'unstable') + return [r for r in subset if r in unstables] + +@eh.revset('extinct') +def revsetextinct(repo, subset, x): + """``extinct()`` + Obsolete changesets with obsolete descendants only. + """ + args = revset.getargs(x, 0, 0, 'extinct takes no arguments') + extincts = getobscache(repo, 'extinct') + return [r for r in subset if r in extincts] + +##################################################################### +### Complete troubles computation logic ### +##################################################################### + +# 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) +# +# This section add support for those two addition trouble +# +# - Cache computation +# - revset and ctx method +# - push warning + +### Cache computation +latediff = 1 # flag to prevent taking late comer fix into account + +@cachefor('latecomer') +def _computelatecomerset(repo): + """the set of rev trying to obsolete public revision""" + candidates = _allsuccessors(repo, repo.revs('public()'), + haltonflags=latediff) + query = '%ld - obsolete() - public()' + return set(repo.revs(query, candidates)) + +@cachefor('conflicting') +def _computeconflictingset(repo): + """the set of rev trying to obsolete public revision""" + conflicting = set() + obsstore = repo.obsstore + newermap = {} + for ctx in repo.set('(not public()) - obsolete()'): + prec = obsstore.successors.get(ctx.node(), ()) + toprocess = set(prec) + 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 + if len(newer) > 1: + conflicting.add(ctx.rev()) + break + toprocess.update(obsstore.successors.get(prec, ())) + return conflicting + +### changectx method + +@eh.addattr(context.changectx, 'latecomer') +def latecomer(ctx): + """is the changeset latecomer (Try to succeed to public change)""" + if ctx.node() is None: + return False + 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)""" + if ctx.node() is None: + return False + return ctx.rev() in getobscache(ctx._repo, 'conflicting') + +### revset symbol + +@eh.revset('latecomer') +def revsetlatecomer(repo, subset, x): + """``latecomer()`` + Changesets marked as successors of public changesets. + """ + args = revset.getargs(x, 0, 0, 'latecomer takes no arguments') + lates = getobscache(repo, 'latecomer') + 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] + + +### Discovery wrapping + +@eh.wrapfunction(discovery, 'checkheads') +def wrapcheckheads(orig, repo, remote, outgoing, *args, **kwargs): + """wrap mercurial.discovery.checkheads + + * prevent latecomer and unstable to be pushed + """ + # do not push instability + for h in outgoing.missingheads: + # Checking heads is enough, obsolete descendants are either + # obsolete or unstable. + ctx = repo[h] + 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!") + % ctx) + return orig(repo, remote, outgoing, *args, **kwargs) + +##################################################################### +### Filter extinct changeset from common operation ### +##################################################################### + +@eh.wrapfunction(merge, 'update') +def wrapmergeupdate(orig, repo, node, *args, **kwargs): + """ensure we don't automatically update on hidden changeset""" + if node is None: + # tip of current branch + branch = repo[None].branch() + node = repo.revs('last((.:: and branch(%s)) - hidden())', branch)[0] + return orig(repo, node, *args, **kwargs) + +@eh.wrapfunction(localrepo.localrepository, 'branchtip') +def obsbranchtip(orig, repo, branch): + """ensure "stable" reference does not end on a hidden changeset""" + result = () + heads = repo.branchmap().get(branch, ()) + if heads: + result = list(repo.set('last(heads(branch(%n) - hidden()))', heads[0])) + if not result: + raise error.RepoLookupError(_("unknown branch '%s'") % branch) + return result[0].node() + + +##################################################################### +### Additional Utilities ### +##################################################################### + +# This section contains a lot of small utility function and method + +# - Function to create markers +# - useful alias pstatus and pdiff (should probably go in evolve) +# - "troubles" method on changectx +# - function to travel throught the obsolescence graph +# - function to find useful changeset to stabilize + +### Marker Create + +def createmarkers(repo, relations, metadata=None, flag=0): + """Add obsolete markers between changeset in a repo + + <relations> must be an iterable of (<old>, (<new>, ...)) tuple. + `old` and `news` are changectx. + + Current user and date are used except if specified otherwise in the + metadata attribute. + + /!\ assume the repo have been locked by the user /!\ + """ + # prepare metadata + if metadata is None: + metadata = {} + if 'date' not in metadata: + metadata['date'] = '%i %i' % util.makedate() + if 'user' not in metadata: + metadata['user'] = repo.ui.username() + # check future marker + tr = repo.transaction('add-obsolescence-marker') + try: + for prec, sucs in relations: + if not prec.mutable(): + raise util.Abort("Cannot obsolete immutable changeset: %s" % prec) + nprec = prec.node() + nsucs = tuple(s.node() for s in sucs) + if nprec in nsucs: + raise util.Abort("Changeset %s cannot obsolete himself" % prec) + repo.obsstore.create(tr, nprec, nsucs, flag, metadata) + clearobscaches(repo) + tr.close() + finally: + tr.release() + + +### Useful alias + +@eh.uisetup +def _installalias(ui): + if ui.config('alias', 'pstatus', None) is None: + ui.setconfig('alias', 'pstatus', 'status --rev .^') + if ui.config('alias', 'pdiff', None) is None: + ui.setconfig('alias', 'pdiff', 'diff --rev .^') + if ui.config('alias', 'olog', None) is None: + ui.setconfig('alias', 'olog', "log -r 'precursors(.)' --hidden") + +# - "troubles" method on changectx + +@eh.addattr(context.changectx, 'troubles') +def troubles(ctx): + """Return a tuple listing all the troubles that affect a changeset + + Troubles may be "unstable", "latecomer" or "conflicting". + """ + troubles = [] + if ctx.unstable(): + troubles.append('unstable') + if ctx.latecomer(): + troubles.append('latecomer') + if ctx.conflicting(): + troubles.append('conflicting') + return tuple(troubles) + +### Troubled revset symbol + +@eh.revset('troubled') +def revsetlatecomer(repo, subset, x): + """``troubled()`` + Changesets with troubles. + """ + _ = revset.getargs(x, 0, 0, 'troubled takes no arguments') + return list(repo.revs('%ld and (unstable() + latecomer() + conflicting())', + subset)) + + +### Obsolescence graph + +# XXX SOME MAJOR CLEAN UP TO DO HERE XXX + +def _precursors(repo, s): + """Precursor of a changeset""" + cs = set() + nm = repo.changelog.nodemap + markerbysubj = repo.obsstore.successors + 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 _allprecursors(repo, s): # XXX we need a better naming + """transitive precursors of a subset""" + toproceed = [repo[r].node() for r in s] + seen = set() + allsubjects = repo.obsstore.successors + while toproceed: + nc = toproceed.pop() + for mark in allsubjects.get(nc, ()): + np = mark[0] + if np not in seen: + seen.add(np) + toproceed.append(np) + nm = repo.changelog.nodemap + cs = set() + for p in seen: + pr = nm.get(p) + if pr is not None: + cs.add(pr) + return cs + +def _successors(repo, s): + """Successors of a changeset""" + cs = set() + nm = repo.changelog.nodemap + markerbyobj = repo.obsstore.precursors + for r in s: + for p in markerbyobj.get(repo[r].node(), ()): + for sub in p[1]: + sr = nm.get(sub) + if sr is not None: + cs.add(sr) + return cs + +def _allsuccessors(repo, s, haltonflags=0): # XXX we need a better naming + """transitive successors of a subset + + haltonflags allows to provide flags which prevent the evaluation of a + marker. """ + toproceed = [repo[r].node() for r in s] + seen = set() + allobjects = repo.obsstore.precursors + while toproceed: + nc = toproceed.pop() + for mark in allobjects.get(nc, ()): + if mark[2] & haltonflags: + continue + for sub in mark[1]: + if sub == nullid: + continue # should not be here! + if sub not in seen: + seen.add(sub) + toproceed.append(sub) + nm = repo.changelog.nodemap + cs = set() + for s in seen: + sr = nm.get(s) + if sr is not None: + cs.add(sr) + return cs + + + +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) + + +##################################################################### +### Extending revset and template ### +##################################################################### + +# this section add several useful revset symbol not yet in core. +# they are subject to changes + +### hidden revset is not in core yet + +@eh.revset('hidden') +def revsethidden(repo, subset, x): + """``hidden()`` + Changeset is hidden. + """ + args = revset.getargs(x, 0, 0, 'hidden takes no argument') + return [r for r in subset if r in repo.hiddenrevs] + +### XXX I'm not sure this revset is useful +@eh.revset('suspended') +def revsetsuspended(repo, subset, x): + """``suspended()`` + Obsolete changesets with non-obsolete descendants. + """ + args = revset.getargs(x, 0, 0, 'suspended takes no arguments') + suspended = getobscache(repo, 'suspended') + return [r for r in subset if r in suspended] + + +@eh.revset('precursors') +def revsetprecursors(repo, subset, x): + """``precursors(set)`` + Immediate precursors of changesets in set. + """ + s = revset.getset(repo, range(len(repo)), x) + cs = _precursors(repo, s) + return [r for r in subset if r in cs] + + +@eh.revset('allprecursors') +def revsetallprecursors(repo, subset, x): + """``allprecursors(set)`` + Transitive precursors of changesets in set. + """ + s = revset.getset(repo, range(len(repo)), x) + cs = _allprecursors(repo, s) + return [r for r in subset if r in cs] + + +@eh.revset('successors') +def revsetsuccessors(repo, subset, x): + """``successors(set)`` + Immediate successors of changesets in set. + """ + s = revset.getset(repo, range(len(repo)), x) + cs = _successors(repo, s) + return [r for r in subset if r in cs] + +@eh.revset('allsuccessors') +def revsetallsuccessors(repo, subset, x): + """``allsuccessors(set)`` + Transitive successors of changesets in set. + """ + s = revset.getset(repo, range(len(repo)), x) + cs = _allsuccessors(repo, s) + return [r for r in subset if r in cs] + +### template keywords +# XXX it does not handle troubles well :-/ + +@eh.templatekw('obsolete') +def obsoletekw(repo, ctx, templ, **args): + """:obsolete: String. The obsolescence level of the node, could be + ``stable``, ``unstable``, ``suspended`` or ``extinct``. + """ + rev = ctx.rev() + if ctx.obsolete(): + if ctx.extinct(): + return 'extinct' + else: + return 'suspended' + elif ctx.unstable(): + return 'unstable' + return 'stable' + +##################################################################### +### Various trouble warning ### +##################################################################### + +# This section take care of issue warning to the user when troubles appear + +@eh.wrapcommand("update") +@eh.wrapcommand("pull") +def wrapmayobsoletewc(origfn, ui, repo, *args, **opts): + """Warn that the working directory parent is an obsolete changeset""" + res = origfn(ui, repo, *args, **opts) + if repo['.'].obsolete(): + ui.warn(_('Working directory parent is obsolete\n')) + return res + +# XXX this could wrap transaction code +# XXX (but this is a bit a layer violation) +@eh.wrapcommand("commit") +@eh.wrapcommand("push") +@eh.wrapcommand("pull") +@eh.wrapcommand("graft") +@eh.wrapcommand("phase") +@eh.wrapcommand("unbundle") +def warnobserrors(orig, ui, repo, *args, **kwargs): + """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()')) + ret = orig(ui, repo, *args, **kwargs) + newunstables = len(repo.revs('unstable()')) - priorunstables + newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers + newconflictings = len(repo.revs('conflicting()')) - priorconflictings + 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) + return ret + +@eh.reposetup +def _repostabilizesetup(ui, repo): + """Add a hint for "hg stabilize" when troubles make push fails + """ + if not repo.local(): + return + + opush = repo.push + + class stabilizerrepo(repo.__class__): + def push(self, remote, *args, **opts): + """wrapper around pull that pull obsolete relation""" + try: + result = opush(remote, *args, **opts) + except util.Abort, ex: + hint = _("use 'hg stabilize' to get a stable history " + "or --force to ignore warnings") + if (len(ex.args) >= 1 + and ex.args[0].startswith('push includes ') + and ex.hint is None): + ex.hint = hint + raise + return result + repo.__class__ = stabilizerrepo + +@eh.wrapcommand("summary") +def obssummary(orig, ui, repo, *args, **kwargs): + ret = orig(ui, repo, *args, **kwargs) + nbunstable = len(getobscache(repo, 'unstable')) + nblatecomer = len(getobscache(repo, 'latecomer')) + nbconflicting = len(getobscache(repo, 'unstable')) + if nbunstable: + ui.write('unstable: %i changesets\n' % nbunstable) + else: + ui.note('unstable: 0 changesets\n') + if nblatecomer: + ui.write('latecomer: %i changesets\n' % nblatecomer) + else: + ui.note('latecomer: 0 changesets\n') + if nbconflicting: + ui.write('conflicting: %i changesets\n' % nbconflicting) + else: + ui.note('conflicting: 0 changesets\n') + return ret + + +##################################################################### +### Core Other extension compat ### +##################################################################### + +# This section make official history rewritter create obsolete marker + + +### commit --amend +# make commit --amend create obsolete marker +# +# The precursor is still strip from the repository. + +@eh.wrapfunction(cmdutil, 'amend') +def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs): + oldnode = old.node() + new = orig(ui, repo, commitfunc, old, *args, **kwargs) + if new != oldnode: + lock = repo.lock() + try: + tr = repo.transaction('post-amend-obst') + try: + meta = { + 'date': '%i %i' % util.makedate(), + 'user': ui.username(), + } + repo.obsstore.create(tr, oldnode, [new], 0, meta) + tr.close() + clearobscaches(repo) + finally: + tr.release() + finally: + lock.release() + return new + +### rebase +# +# - ignore obsolete changeset +# - create obsolete marker *instead of* striping + +def buildstate(orig, repo, dest, rebaseset, *ags, **kws): + """wrapper for rebase 's buildstate that exclude obsolete changeset""" + + rebaseset = repo.revs('%ld - extinct()', rebaseset) + if not rebaseset: + repo.ui.warn(_('whole rebase set is extinct and ignored.\n')) + return {} + root = min(rebaseset) + if not repo._rebasekeep and not repo[root].mutable(): + raise util.Abort(_("can't rebase immutable changeset %s") % repo[root], + hint=_('see hg help phases for details')) + return orig(repo, dest, rebaseset, *ags, **kws) + +def defineparents(orig, repo, rev, target, state, *args, **kwargs): + rebasestate = getattr(repo, '_rebasestate', None) + if rebasestate is not None: + repo._rebasestate = dict(state) + repo._rebasetarget = target + return orig(repo, rev, target, state, *args, **kwargs) + +def concludenode(orig, repo, rev, p1, *args, **kwargs): + """wrapper for rebase 's concludenode that set obsolete relation""" + newrev = orig(repo, rev, p1, *args, **kwargs) + rebasestate = getattr(repo, '_rebasestate', None) + if rebasestate is not None: + if newrev is not None: + nrev = repo[newrev].rev() + else: + nrev = p1 + repo._rebasestate[rev] = nrev + return newrev + +def cmdrebase(orig, ui, repo, *args, **kwargs): + + reallykeep = kwargs.get('keep', False) + kwargs = dict(kwargs) + kwargs['keep'] = True + repo._rebasekeep = reallykeep + + # We want to mark rebased revision as obsolete and set their + # replacements if any. Doing it in concludenode() prevents + # aborting the rebase, and is not called with all relevant + # revisions in --collapse case. Instead, we try to track the + # rebase state structure by sampling/updating it in + # defineparents() and concludenode(). The obsolete markers are + # added from this state after a successful call. + repo._rebasestate = {} + repo._rebasetarget = None + try: + l = repo.lock() + try: + res = orig(ui, repo, *args, **kwargs) + if not reallykeep: + # Filter nullmerge or unrebased entries + repo._rebasestate = dict(p for p in repo._rebasestate.iteritems() + if p[1] >= 0) + if not res and not kwargs.get('abort') and repo._rebasestate: + # Rebased revisions are assumed to be descendants of + # targetrev. If a source revision is mapped to targetrev + # or to another rebased revision, it must have been + # removed. + markers = [] + if kwargs.get('collapse'): + # collapse assume revision disapear because they are all + # in the created revision + newrevs = set(repo._rebasestate.values()) + newrevs.remove(repo._rebasetarget) + if newrevs: + # we create new revision. + # A single one by --collapse design + assert len(newrevs) == 1 + new = tuple(repo[n] for n in newrevs) + else: + # every body died. no new changeset created + new = (repo[repo._rebasetarget],) + for rev, newrev in sorted(repo._rebasestate.items()): + markers.append((repo[rev], new)) + else: + # no collapse assume revision disapear because they are + # contained in parent + for rev, newrev in sorted(repo._rebasestate.items()): + markers.append((repo[rev], (repo[newrev],))) + createmarkers(repo, markers) + return res + finally: + l.release() + finally: + delattr(repo, '_rebasestate') + delattr(repo, '_rebasetarget') + +@eh.extsetup +def _rebasewrapping(ui): + # warning about more obsolete + try: + rebase = extensions.find('rebase') + if rebase: + entry = extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors) + extensions.wrapfunction(rebase, 'buildstate', buildstate) + extensions.wrapfunction(rebase, 'defineparents', defineparents) + extensions.wrapfunction(rebase, 'concludenode', concludenode) + extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase) + except KeyError: + pass # rebase not found + + +##################################################################### +### Old Evolve extension content ### +##################################################################### + +# XXX need clean up and proper sorting in other section ### util function ############################# @@ -122,8 +1193,7 @@ # add evolution metadata markers = [(u, (new,)) for u in updates] markers.append((old, (new,))) - obsolete = extensions.find('obsolete') - obsolete.createmarkers(repo, markers) + createmarkers(repo, markers) else: # newid is an existing revision. It could make sense to # replace revisions with existing ones but probably not by @@ -164,14 +1234,13 @@ exc.__class__ = LocalMergeFailure raise oldbookmarks = repo.nodebookmarks(nodesrc) - obsolete = extensions.find('obsolete') if nodenew is not None: phases.retractboundary(repo, destphase, [nodenew]) - obsolete.createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))]) + createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))]) for book in oldbookmarks: repo._bookmarks[book] = nodenew else: - obsolete.createmarkers(repo, [(repo[nodesrc], ())]) + createmarkers(repo, [(repo[nodesrc], ())]) # Behave like rebase, move bookmarks to dest for book in oldbookmarks: repo._bookmarks[book] = dest.node() @@ -297,18 +1366,17 @@ def _solveunstable(ui, repo, orig, dryrun=False): """Stabilize a unstable changeset""" - obsolete = extensions.find('obsolete') obs = orig.parents()[0] if not obs.obsolete(): obs = orig.parents()[1] assert obs.obsolete() - newer = obsolete.newerversion(repo, obs.node()) + newer = newerversion(repo, obs.node()) # search of a parent which is not killed while newer == [()]: ui.debug("stabilize target %s is plain dead," " trying to stabilize on it's parent") obs = obs.parents()[0] - newer = obsolete.newerversion(repo, obs.node()) + newer = newerversion(repo, obs.node()) if len(newer) > 1: ui.write_err(_("conflict rewriting. can't choose destination\n")) return 2 @@ -361,7 +1429,6 @@ repo.ui.status(_('atop:')) if not ui.quiet: displayer.show(prec) - obsolete = extensions.find('obsolete') if dryrun: todo = 'hg rebase --rev %s --detach %s;\n' % (latecomer, prec.p1()) repo.ui.write(todo) @@ -386,7 +1453,7 @@ tmpid = relocate(repo, latecomer, prec.p1()) if tmpid is not None: tmpctx = repo[tmpid] - obsolete.createmarkers(repo, [(latecomer, (tmpctx,))]) + createmarkers(repo, [(latecomer, (tmpctx,))]) except MergeFailure: repo.opener.write('graftstate', latecomer.hex() + '\n') repo.ui.write_err(_('stabilize failed!\n')) @@ -427,12 +1494,12 @@ newid = repo.commitctx(new) if newid is None: - obsolete.createmarkers(repo, [(tmpctx, ())]) + createmarkers(repo, [(tmpctx, ())]) newid = prec.node() else: phases.retractboundary(repo, latecomer.phase(), [newid]) - obsolete.createmarkers(repo, [(tmpctx, (repo[newid],))], - flag=obsolete.latediff) + createmarkers(repo, [(tmpctx, (repo[newid],))], + flag=latediff) bmupdate(newid) tr.close() repo.ui.status(_('commited as %s\n') % node.short(newid)) @@ -446,7 +1513,6 @@ wlock.release() def _solveconflicting(ui, repo, conflicting, dryrun=False): - obsolete = extensions.find('obsolete') base, others = conflictingdata(conflicting) if len(others) > 1: raise util.Abort("We do not handle split yet") @@ -510,7 +1576,7 @@ # no changes else: new = repo['.'] - obsolete.createmarkers(repo, [(other, (new,))]) + createmarkers(repo, [(other, (new,))]) phases.retractboundary(repo, other.phase(), [new.node()]) tr.close() finally: @@ -528,9 +1594,8 @@ XXX this woobly function won't survive XXX """ - obsolete = extensions.find('obsolete') for base in ctx._repo.set('reverse(precursors(%d))', ctx): - newer = obsolete.newerversion(ctx._repo, base.node()) + newer = newerversion(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: @@ -616,8 +1681,7 @@ markers = [] for n in targetnodes: markers.append((repo[n], sucs)) - obsolete = extensions.find('obsolete') - obsolete.createmarkers(repo, markers) + createmarkers(repo, markers) # update to an unkilled parent wdp = repo['.'] @@ -722,8 +1786,7 @@ # the intermediate revision if any. No need to update # phases or parents. if tempid is not None: - obsolete = extensions.find('obsolete') - obsolete.createmarkers(repo, [(repo[tempid], ())]) + createmarkers(repo, [(repo[tempid], ())]) # XXX: need another message in collapse case. tr.close() raise error.Abort(_('no updates found')) @@ -859,8 +1922,7 @@ if newid is None: raise util.Abort(_('nothing to uncommit')) # Move local changes on filtered changeset - obsolete = extensions.find('obsolete') - obsolete.createmarkers(repo, [(old, (repo[newid],))]) + createmarkers(repo, [(old, (repo[newid],))]) phases.retractboundary(repo, oldphase, [newid]) repo.dirstate.setparents(newid, node.nullid) _uncommitdirstate(repo, old, match) @@ -873,8 +1935,12 @@ finally: lock.release() +@eh.wrapcommand('commit') def commitwrapper(orig, ui, repo, *arg, **kwargs): - lock = repo.lock() + if kwargs.get('amend', False): + lock = None + else: + lock = repo.lock() try: obsoleted = kwargs.get('obsolete', []) if obsoleted: @@ -883,20 +1949,20 @@ if not result: # commit successed new = repo['-1'] oldbookmarks = [] - obsolete = extensions.find('obsolete') markers = [] for old in obsoleted: oldbookmarks.extend(repo.nodebookmarks(old.node())) markers.append((old, (new,))) if markers: - obsolete.createmarkers(repo, markers) + createmarkers(repo, markers) for book in oldbookmarks: repo._bookmarks[book] = new.node() if oldbookmarks: bookmarks.write(repo) return result finally: - lock.release() + if lock is not None: + lock.release() @command('^touch', [('r', 'rev', [], 'revision to update'),], @@ -916,7 +1982,6 @@ return 1 if repo.revs('public() and %ld', revs): raise util.Abort("can't touch public revision") - obsolete = extensions.find('obsolete') wlock = repo.wlock() try: lock = repo.lock() @@ -930,7 +1995,7 @@ new, _ = rewrite(repo, ctx, [], ctx, [ctx.p1().node(), ctx.p2().node()], commitopts={'extra': extra}) - obsolete.createmarkers(repo, [(ctx, (repo[new],))]) + createmarkers(repo, [(ctx, (repo[new],))]) phases.retractboundary(repo, ctx.phase(), [new]) if ctx in repo[None].parents(): repo.dirstate.setparents(new, node.nullid) @@ -966,7 +2031,6 @@ if len(heads) > 1: raise util.Abort("set have multiple heads") head = repo[heads[0]] - obsolete = extensions.find('obsolete') wlock = repo.wlock() try: lock = repo.lock() @@ -980,7 +2044,7 @@ [root.p1().node(), root.p2().node()], commitopts={'message': msg}) phases.retractboundary(repo, targetphase, [newid]) - obsolete.createmarkers(repo, [(ctx, (repo[newid],)) + createmarkers(repo, [(ctx, (repo[newid],)) for ctx in allctx]) tr.close() finally: @@ -994,6 +2058,7 @@ wlock.release() +@eh.wrapcommand('graft') def graftwrapper(orig, ui, repo, *revs, **kwargs): kwargs = dict(kwargs) revs = list(revs) + kwargs.get('rev', []) @@ -1018,25 +2083,21 @@ finally: lock.release() -def extsetup(ui): - try: - obsolete = extensions.find('obsolete') - except KeyError: - raise error.Abort(_('evolution extension requires obsolete extension.')) +@eh.extsetup +def oldevolveextsetup(ui): try: rebase = extensions.find('rebase') except KeyError: - rebase = None raise error.Abort(_('evolution extension requires rebase extension.')) for cmd in ['amend', 'kill', 'uncommit']: entry = extensions.wrapcommand(cmdtable, cmd, - obsolete.warnobserrors) + warnobserrors) - entry = extensions.wrapcommand(commands.table, 'commit', commitwrapper) + entry = cmdutil.findcmd('commit', commands.table)[1] entry[1].append(('o', 'obsolete', [], _("make commit obsolete this revision"))) - entry = extensions.wrapcommand(commands.table, 'graft', graftwrapper) + entry = cmdutil.findcmd('graft', commands.table)[1] entry[1].append(('o', 'obsolete', [], _("make graft obsoletes this revision"))) entry[1].append(('O', 'old-obsolete', False,
--- a/hgext/obsolete.py Thu Aug 23 18:21:18 2012 +0200 +++ b/hgext/obsolete.py Fri Aug 24 10:44:23 2012 +0200 @@ -5,41 +5,13 @@ # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -"""Introduce the Obsolete concept to mercurial - -General concept -=============== - -This extension introduces the *obsolete* concept. The relation -``<changeset B> obsoletes <changeset A>`` denotes that ``<changeset B>`` -is a new version of ``<changeset A>``. - -The *obsolete* relations act as an history **orthogonal** to the regular -changesets history. Regular changesets history versions files. *Obsolete* -relations version changesets. +"""Deprecated extension that formely introduces "Changeset Obsolescence". -:obsolete: a changeset that has been replaced by another one. -:unstable: a changeset that is not obsolete but has an obsolete ancestor. -:suspended: an obsolete changeset with unstable descendants. -:extinct: an obsolete changeset without unstable descendants. - (subject to garbage collection) - -Another name for unstable could be out of sync. - +This concept is now partially in Mercurial core (starting with mercurial 2.3). The remaining logic have been grouped with the evolve extension. -Usage and Feature -================= - - -New commands ------------- - -Note that rebased changesets are now marked obsolete instead of being stripped. - +Some code cemains in this extensions to detect and convert prehistoric format of obsolete marker than early user may have create. Keep it enabled if you were such user. """ - - from mercurial import util try: @@ -50,1061 +22,11 @@ raise util.Abort('Obsolete extension requires Mercurial 2.3 (or later)') import sys -from mercurial.i18n import _ -from mercurial import cmdutil -from mercurial import commands -from mercurial import context -from mercurial import discovery -from mercurial import error -from mercurial import extensions -from mercurial import localrepo -from mercurial import merge -from mercurial import phases -from mercurial import revset -from mercurial import scmutil -from mercurial import templatekw -from mercurial.node import bin, short, nullid - -# This extension contains the following code -# -# - Extension Helper code -# - Obsolescence cache -# - ... -# - Older format compat - - - -##################################################################### -### Extension helper ### -##################################################################### - -class exthelper(object): - """Helper for modular extension setup - - A single helper should be instanciated for each extension. Helper - methods are then used as decorator for various purpose. - - All decorators return the original function and may be chained. - """ - - def __init__(self): - self._uicallables = [] - self._extcallables = [] - self._repocallables = [] - self._revsetsymbols = [] - self._templatekws = [] - self._commandwrappers = [] - self._extcommandwrappers = [] - self._functionwrappers = [] - self._duckpunchers = [] - - def final_uisetup(self, ui): - """Method to be used as the extension uisetup - - The following operations belong here: - - - Changes to ui.__class__ . The ui object that will be used to run the - command has not yet been created. Changes made here will affect ui - objects created after this, and in particular the ui that will be - passed to runcommand - - Command wraps (extensions.wrapcommand) - - Changes that need to be visible to other extensions: because - initialization occurs in phases (all extensions run uisetup, then all - run extsetup), a change made here will be visible to other extensions - during extsetup - - Monkeypatch or wrap function (extensions.wrapfunction) of dispatch - module members - - Setup of pre-* and post-* hooks - - pushkey setup - """ - for cont, funcname, func in self._duckpunchers: - setattr(cont, funcname, func) - for command, wrapper in self._commandwrappers: - extensions.wrapcommand(commands.table, command, wrapper) - for cont, funcname, wrapper in self._functionwrappers: - extensions.wrapfunction(cont, funcname, wrapper) - for c in self._uicallables: - c(ui) - - def final_extsetup(self, ui): - """Method to be used as a the extension extsetup - - The following operations belong here: - - - Changes depending on the status of other extensions. (if - extensions.find('mq')) - - Add a global option to all commands - - Register revset functions - """ - knownexts = {} - for name, symbol in self._revsetsymbols: - revset.symbols[name] = symbol - for name, kw in self._templatekws: - templatekw.keywords[name] = kw - for ext, command, wrapper in self._extcommandwrappers: - if ext not in knownexts: - e = extensions.find(ext) - if e is None: - raise util.Abort('extension %s not found' % ext) - knownexts[ext] = e.cmdtable - extensions.wrapcommand(knownexts[ext], commands, wrapper) - for c in self._extcallables: - c(ui) - - def final_reposetup(self, ui, repo): - """Method to be used as a the extension reposetup - - The following operations belong here: - - - All hooks but pre-* and post-* - - Modify configuration variables - - Changes to repo.__class__, repo.dirstate.__class__ - """ - for c in self._repocallables: - c(ui, repo) - - def uisetup(self, call): - """Decorated function will be executed during uisetup - - example:: - - @eh.uisetup - def setupbabar(ui): - print 'this is uisetup!' - """ - self._uicallables.append(call) - return call - - def extsetup(self, call): - """Decorated function will be executed during extsetup - - example:: - - @eh.extsetup - def setupcelestine(ui): - print 'this is extsetup!' - """ - self._uicallables.append(call) - return call - - def reposetup(self, call): - """Decorated function will be executed during reposetup - - example:: - - @eh.reposetup - def setupzephir(ui, repo): - print 'this is reposetup!' - """ - self._repocallables.append(call) - return call - - def revset(self, symbolname): - """Decorated function is a revset symbol - - The name of the symbol must be given as the decorator argument. - The symbol is added during `extsetup`. - - example:: - - @eh.revset('hidden') - def revsetbabar(repo, subset, x): - args = revset.getargs(x, 0, 0, 'babar accept no argument') - return [r for r in subset if 'babar' in repo[r].description()] - """ - def dec(symbol): - self._revsetsymbols.append((symbolname, symbol)) - return symbol - return dec - - - def templatekw(self, keywordname): - """Decorated function is a revset keyword - - The name of the keyword must be given as the decorator argument. - The symbol is added during `extsetup`. - - example:: - - @eh.templatekw('babar') - def kwbabar(ctx): - return 'babar' - """ - def dec(keyword): - self._templatekws.append((keywordname, keyword)) - return keyword - return dec - - def wrapcommand(self, command, extension=None): - """Decorated function is a command wrapper - - The name of the command must be given as the decorator argument. - The wrapping is installed during `uisetup`. - - If the second option `extension` argument is provided, the wrapping - will be applied in the extension commandtable. This argument must be a - string that will be searched using `extension.find` if not found and - Abort error is raised. If the wrapping applies to an extension, it is - installed during `extsetup` - - example:: - - @eh.wrapcommand('summary') - def wrapsummary(orig, ui, repo, *args, **kwargs): - ui.note('Barry!') - return orig(ui, repo, *args, **kwargs) - - """ - def dec(wrapper): - if extension is None: - self._commandwrappers.append((command, wrapper)) - else: - self._extcommandwrappers.append((extension, command, wrapper)) - return wrapper - return dec - - def wrapfunction(self, container, funcname): - """Decorated function is a function wrapper - - This function takes two arguments, the container and the name of the - function to wrap. The wrapping is performed during `uisetup`. - (there is no extension support) - - example:: - - @eh.function(discovery, 'checkheads') - def wrapfunction(orig, *args, **kwargs): - ui.note('His head smashed in and his heart cut out') - return orig(*args, **kwargs) - """ - def dec(wrapper): - self._functionwrappers.append((container, funcname, wrapper)) - return wrapper - return dec - - def addattr(self, container, funcname): - """Decorated function is to be added to the container - - This function takes two arguments, the container and the name of the - function to wrap. The wrapping is performed during `uisetup`. - - example:: - - @eh.function(context.changectx, 'babar') - def babar(ctx): - return 'babar' in ctx.description - """ - def dec(func): - self._duckpunchers.append((container, funcname, func)) - return func - return dec - -eh = exthelper() -uisetup = eh.final_uisetup -extsetup = eh.final_extsetup -reposetup = eh.final_reposetup - -##################################################################### -### Obsolescence Caching Logic ### -##################################################################### - -# Obsolescence related logic can be very slow if we don't have efficient cache. -# -# This section implements a cache mechanism that did not make it into core for -# time reason. It store meaningful set of revision related to obsolescence -# (obsolete, unstabletble ... -# -# Here is: -# -# - Computation of meaningful set, -# - Cache access logic, -# - Cache invalidation logic, -# - revset and ctx using this cache. -# - - -### Computation of meaningful set -# -# Most set can be computed with "simple" revset. - -#: { set name -> function to compute this set } mapping -#: function take a single "repo" argument. -#: -#: Use the `cachefor` decorator to register new cache function -cachefuncs = {} -def cachefor(name): - """Decorator to register a function as computing the cache for a set""" - def decorator(func): - assert name not in cachefuncs - cachefuncs[name] = func - return func - return decorator - -@cachefor('obsolete') -def _computeobsoleteset(repo): - """the set of obsolete revisions""" - obs = set() - nm = repo.changelog.nodemap - for prec in repo.obsstore.precursors: - rev = nm.get(prec) - if rev is not None: - obs.add(rev) - return set(repo.revs('%ld - public()', obs)) - -@cachefor('unstable') -def _computeunstableset(repo): - """the set of non obsolete revisions with obsolete parents""" - return set(repo.revs('(obsolete()::) - obsolete()')) - -@cachefor('suspended') -def _computesuspendedset(repo): - """the set of obsolete parents with non obsolete descendants""" - return set(repo.revs('obsolete() and obsolete()::unstable()')) - -@cachefor('extinct') -def _computeextinctset(repo): - """the set of obsolete parents without non obsolete descendants""" - return set(repo.revs('obsolete() - obsolete()::unstable()')) - -@eh.wrapfunction(obsolete.obsstore, '__init__') -def _initobsstorecache(orig, obsstore, *args, **kwargs): - """add a cache attribute to obsstore""" - obsstore.caches = {} - return orig(obsstore, *args, **kwargs) - -### Cache access - -def getobscache(repo, name): - """Return the set of revision that belong to the <name> set - - Such access may compute the set and cache it for future use""" - if not repo.obsstore: - return () - if name not in repo.obsstore.caches: - repo.obsstore.caches[name] = cachefuncs[name](repo) - return repo.obsstore.caches[name] - -### Cache clean up -# -# To be simple we need to invalidate obsolescence cache when: -# -# - new changeset is added: -# - public phase is changed -# - obsolescence marker are added -# - strip is used a repo - - -def clearobscaches(repo): - """Remove all obsolescence related cache from a repo - - This remove all cache in obsstore is the obsstore already exist on the - repo. - - (We could be smarter here)""" - if 'obsstore' in repo._filecache: - repo.obsstore.caches.clear() - -@eh.wrapfunction(localrepo.localrepository, 'addchangegroup') # new changeset -@eh.wrapfunction(phases, 'retractboundary') # phase movement -@eh.wrapfunction(phases, 'advanceboundary') # phase movement -@eh.wrapfunction(localrepo.localrepository, 'destroyed') # strip -def wrapclearcache(orig, repo, *args, **kwargs): - try: - return orig(repo, *args, **kwargs) - finally: - # we are a bit wide here - # we could restrict to: - # advanceboundary + phase==public - # retractboundary + phase==draft - clearobscaches(repo) - -@eh.wrapfunction(obsolete.obsstore, 'add') # new marker -def clearonadd(orig, obsstore, *args, **kwargs): - try: - return orig(obsstore, *args, **kwargs) - finally: - obsstore.caches.clear() - -### Use the case -# Function in core that could benefic from the cache are overwritten by cache using version - -# changectx method - -@eh.addattr(context.changectx, 'unstable') -def unstable(ctx): - """is the changeset unstable (have obsolete ancestor)""" - if ctx.node() is None: - return False - return ctx.rev() in getobscache(ctx._repo, 'unstable') - - -@eh.addattr(context.changectx, 'extinct') -def extinct(ctx): - """is the changeset extinct by other""" - if ctx.node() is None: - return False - return ctx.rev() in getobscache(ctx._repo, 'extinct') - -# revset - -@eh.revset('obsolete') -def revsetobsolete(repo, subset, x): - """``obsolete()`` - Changeset is obsolete. - """ - args = revset.getargs(x, 0, 0, 'obsolete takes no argument') - obsoletes = getobscache(repo, 'obsolete') - return [r for r in subset if r in obsoletes] - -@eh.revset('unstable') -def revsetunstable(repo, subset, x): - """``unstable()`` - Unstable changesets are non-obsolete with obsolete ancestors. - """ - args = revset.getargs(x, 0, 0, 'unstable takes no arguments') - unstables = getobscache(repo, 'unstable') - return [r for r in subset if r in unstables] - -@eh.revset('extinct') -def revsetextinct(repo, subset, x): - """``extinct()`` - Obsolete changesets with obsolete descendants only. - """ - args = revset.getargs(x, 0, 0, 'extinct takes no arguments') - extincts = getobscache(repo, 'extinct') - return [r for r in subset if r in extincts] - -##################################################################### -### Complete troubles computation logic ### -##################################################################### - -# 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) -# -# This section add support for those two addition trouble -# -# - Cache computation -# - revset and ctx method -# - push warning - -### Cache computation -latediff = 1 # flag to prevent taking late comer fix into account - -@cachefor('latecomer') -def _computelatecomerset(repo): - """the set of rev trying to obsolete public revision""" - candidates = _allsuccessors(repo, repo.revs('public()'), - haltonflags=latediff) - query = '%ld - obsolete() - public()' - return set(repo.revs(query, candidates)) - -@cachefor('conflicting') -def _computeconflictingset(repo): - """the set of rev trying to obsolete public revision""" - conflicting = set() - obsstore = repo.obsstore - newermap = {} - for ctx in repo.set('(not public()) - obsolete()'): - prec = obsstore.successors.get(ctx.node(), ()) - toprocess = set(prec) - 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 - if len(newer) > 1: - conflicting.add(ctx.rev()) - break - toprocess.update(obsstore.successors.get(prec, ())) - return conflicting - -### changectx method - -@eh.addattr(context.changectx, 'latecomer') -def latecomer(ctx): - """is the changeset latecomer (Try to succeed to public change)""" - if ctx.node() is None: - return False - 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)""" - if ctx.node() is None: - return False - return ctx.rev() in getobscache(ctx._repo, 'conflicting') - -### revset symbol - -@eh.revset('latecomer') -def revsetlatecomer(repo, subset, x): - """``latecomer()`` - Changesets marked as successors of public changesets. - """ - args = revset.getargs(x, 0, 0, 'latecomer takes no arguments') - lates = getobscache(repo, 'latecomer') - 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] - - -### Discovery wrapping - -@eh.wrapfunction(discovery, 'checkheads') -def wrapcheckheads(orig, repo, remote, outgoing, *args, **kwargs): - """wrap mercurial.discovery.checkheads - - * prevent latecomer and unstable to be pushed - """ - # do not push instability - for h in outgoing.missingheads: - # Checking heads is enough, obsolete descendants are either - # obsolete or unstable. - ctx = repo[h] - 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!") - % ctx) - return orig(repo, remote, outgoing, *args, **kwargs) +import json -##################################################################### -### Filter extinct changeset from common operation ### -##################################################################### - -@eh.wrapfunction(merge, 'update') -def wrapmergeupdate(orig, repo, node, *args, **kwargs): - """ensure we don't automatically update on hidden changeset""" - if node is None: - # tip of current branch - branch = repo[None].branch() - node = repo.revs('last((.:: and branch(%s)) - hidden())', branch)[0] - return orig(repo, node, *args, **kwargs) - -@eh.wrapfunction(localrepo.localrepository, 'branchtip') -def obsbranchtip(orig, repo, branch): - """ensure "stable" reference does not end on a hidden changeset""" - result = () - heads = repo.branchmap().get(branch, ()) - if heads: - result = list(repo.set('last(heads(branch(%n) - hidden()))', heads[0])) - if not result: - raise error.RepoLookupError(_("unknown branch '%s'") % branch) - return result[0].node() - - -##################################################################### -### Additional Utilities ### -##################################################################### - -# This section contains a lot of small utility function and method - -# - Function to create markers -# - useful alias pstatus and pdiff (should probably go in evolve) -# - "troubles" method on changectx -# - function to travel throught the obsolescence graph -# - function to find useful changeset to stabilize - -### Marker Create - -def createmarkers(repo, relations, metadata=None, flag=0): - """Add obsolete markers between changeset in a repo - - <relations> must be an iterable of (<old>, (<new>, ...)) tuple. - `old` and `news` are changectx. - - Current user and date are used except if specified otherwise in the - metadata attribute. - - /!\ assume the repo have been locked by the user /!\ - """ - # prepare metadata - if metadata is None: - metadata = {} - if 'date' not in metadata: - metadata['date'] = '%i %i' % util.makedate() - if 'user' not in metadata: - metadata['user'] = repo.ui.username() - # check future marker - tr = repo.transaction('add-obsolescence-marker') - try: - for prec, sucs in relations: - if not prec.mutable(): - raise util.Abort("Cannot obsolete immutable changeset: %s" % prec) - nprec = prec.node() - nsucs = tuple(s.node() for s in sucs) - if nprec in nsucs: - raise util.Abort("Changeset %s cannot obsolete himself" % prec) - repo.obsstore.create(tr, nprec, nsucs, flag, metadata) - clearobscaches(repo) - tr.close() - finally: - tr.release() - - -### Useful alias - -@eh.uisetup -def _installalias(ui): - if ui.config('alias', 'pstatus', None) is None: - ui.setconfig('alias', 'pstatus', 'status --rev .^') - if ui.config('alias', 'pdiff', None) is None: - ui.setconfig('alias', 'pdiff', 'diff --rev .^') - if ui.config('alias', 'olog', None) is None: - ui.setconfig('alias', 'olog', "log -r 'precursors(.)' --hidden") - -# - "troubles" method on changectx - -@eh.addattr(context.changectx, 'troubles') -def troubles(ctx): - """Return a tuple listing all the troubles that affect a changeset - - Troubles may be "unstable", "latecomer" or "conflicting". - """ - troubles = [] - if ctx.unstable(): - troubles.append('unstable') - if ctx.latecomer(): - troubles.append('latecomer') - if ctx.conflicting(): - troubles.append('conflicting') - return tuple(troubles) - -### Troubled revset symbol - -@eh.revset('troubled') -def revsetlatecomer(repo, subset, x): - """``troubled()`` - Changesets with troubles. - """ - _ = revset.getargs(x, 0, 0, 'troubled takes no arguments') - return list(repo.revs('%ld and (unstable() + latecomer() + conflicting())', - subset)) - - -### Obsolescence graph - -# XXX SOME MAJOR CLEAN UP TO DO HERE XXX - -def _precursors(repo, s): - """Precursor of a changeset""" - cs = set() - nm = repo.changelog.nodemap - markerbysubj = repo.obsstore.successors - 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 _allprecursors(repo, s): # XXX we need a better naming - """transitive precursors of a subset""" - toproceed = [repo[r].node() for r in s] - seen = set() - allsubjects = repo.obsstore.successors - while toproceed: - nc = toproceed.pop() - for mark in allsubjects.get(nc, ()): - np = mark[0] - if np not in seen: - seen.add(np) - toproceed.append(np) - nm = repo.changelog.nodemap - cs = set() - for p in seen: - pr = nm.get(p) - if pr is not None: - cs.add(pr) - return cs - -def _successors(repo, s): - """Successors of a changeset""" - cs = set() - nm = repo.changelog.nodemap - markerbyobj = repo.obsstore.precursors - for r in s: - for p in markerbyobj.get(repo[r].node(), ()): - for sub in p[1]: - sr = nm.get(sub) - if sr is not None: - cs.add(sr) - return cs - -def _allsuccessors(repo, s, haltonflags=0): # XXX we need a better naming - """transitive successors of a subset - - haltonflags allows to provide flags which prevent the evaluation of a - marker. """ - toproceed = [repo[r].node() for r in s] - seen = set() - allobjects = repo.obsstore.precursors - while toproceed: - nc = toproceed.pop() - for mark in allobjects.get(nc, ()): - if mark[2] & haltonflags: - continue - for sub in mark[1]: - if sub == nullid: - continue # should not be here! - if sub not in seen: - seen.add(sub) - toproceed.append(sub) - nm = repo.changelog.nodemap - cs = set() - for s in seen: - sr = nm.get(s) - if sr is not None: - cs.add(sr) - return cs - - - -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) - - -##################################################################### -### Extending revset and template ### -##################################################################### - -# this section add several useful revset symbol not yet in core. -# they are subject to changes - -### hidden revset is not in core yet - -@eh.revset('hidden') -def revsethidden(repo, subset, x): - """``hidden()`` - Changeset is hidden. - """ - args = revset.getargs(x, 0, 0, 'hidden takes no argument') - return [r for r in subset if r in repo.hiddenrevs] - -### XXX I'm not sure this revset is useful -@eh.revset('suspended') -def revsetsuspended(repo, subset, x): - """``suspended()`` - Obsolete changesets with non-obsolete descendants. - """ - args = revset.getargs(x, 0, 0, 'suspended takes no arguments') - suspended = getobscache(repo, 'suspended') - return [r for r in subset if r in suspended] - - -@eh.revset('precursors') -def revsetprecursors(repo, subset, x): - """``precursors(set)`` - Immediate precursors of changesets in set. - """ - s = revset.getset(repo, range(len(repo)), x) - cs = _precursors(repo, s) - return [r for r in subset if r in cs] - - -@eh.revset('allprecursors') -def revsetallprecursors(repo, subset, x): - """``allprecursors(set)`` - Transitive precursors of changesets in set. - """ - s = revset.getset(repo, range(len(repo)), x) - cs = _allprecursors(repo, s) - return [r for r in subset if r in cs] - - -@eh.revset('successors') -def revsetsuccessors(repo, subset, x): - """``successors(set)`` - Immediate successors of changesets in set. - """ - s = revset.getset(repo, range(len(repo)), x) - cs = _successors(repo, s) - return [r for r in subset if r in cs] - -@eh.revset('allsuccessors') -def revsetallsuccessors(repo, subset, x): - """``allsuccessors(set)`` - Transitive successors of changesets in set. - """ - s = revset.getset(repo, range(len(repo)), x) - cs = _allsuccessors(repo, s) - return [r for r in subset if r in cs] - -### template keywords -# XXX it does not handle troubles well :-/ - -@eh.templatekw('obsolete') -def obsoletekw(repo, ctx, templ, **args): - """:obsolete: String. The obsolescence level of the node, could be - ``stable``, ``unstable``, ``suspended`` or ``extinct``. - """ - rev = ctx.rev() - if ctx.obsolete(): - if ctx.extinct(): - return 'extinct' - else: - return 'suspended' - elif ctx.unstable(): - return 'unstable' - return 'stable' - -##################################################################### -### Various trouble warning ### -##################################################################### - -# This section take care of issue warning to the user when troubles appear - -@eh.wrapcommand("update") -@eh.wrapcommand("pull") -def wrapmayobsoletewc(origfn, ui, repo, *args, **opts): - """Warn that the working directory parent is an obsolete changeset""" - res = origfn(ui, repo, *args, **opts) - if repo['.'].obsolete(): - ui.warn(_('Working directory parent is obsolete\n')) - return res - -# XXX this could wrap transaction code -# XXX (but this is a bit a layer violation) -@eh.wrapcommand("commit") -@eh.wrapcommand("push") -@eh.wrapcommand("pull") -@eh.wrapcommand("graft") -@eh.wrapcommand("phase") -@eh.wrapcommand("unbundle") -def warnobserrors(orig, ui, repo, *args, **kwargs): - """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()')) - try: - return orig(ui, repo, *args, **kwargs) - finally: - newunstables = len(repo.revs('unstable()')) - priorunstables - newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers - newconflictings = len(repo.revs('conflicting()')) - priorconflictings - 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) - -@eh.reposetup -def _repostabilizesetup(ui, repo): - """Add a hint for "hg stabilize" when troubles make push fails - """ - if not repo.local(): - return - - opush = repo.push - - class stabilizerrepo(repo.__class__): - def push(self, remote, *args, **opts): - """wrapper around pull that pull obsolete relation""" - try: - result = opush(remote, *args, **opts) - except util.Abort, ex: - hint = _("use 'hg stabilize' to get a stable history " - "or --force to ignore warnings") - if (len(ex.args) >= 1 - and ex.args[0].startswith('push includes ') - and ex.hint is None): - ex.hint = hint - raise - return result - repo.__class__ = stabilizerrepo - -@eh.wrapcommand("summary") -def obssummary(orig, ui, repo, *args, **kwargs): - ret = orig(ui, repo, *args, **kwargs) - nbunstable = len(getobscache(repo, 'unstable')) - nblatecomer = len(getobscache(repo, 'latecomer')) - nbconflicting = len(getobscache(repo, 'unstable')) - if nbunstable: - ui.write('unstable: %i changesets\n' % nbunstable) - else: - ui.note('unstable: 0 changesets\n') - if nblatecomer: - ui.write('latecomer: %i changesets\n' % nblatecomer) - else: - ui.note('latecomer: 0 changesets\n') - if nbconflicting: - ui.write('conflicting: %i changesets\n' % nbconflicting) - else: - ui.note('conflicting: 0 changesets\n') - return ret - - -##################################################################### -### Core Other extension compat ### -##################################################################### - -# This section make official history rewritter create obsolete marker - - -### commit --amend -# make commit --amend create obsolete marker -# -# The precursor is still strip from the repository. - -@eh.wrapfunction(cmdutil, 'amend') -def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs): - oldnode = old.node() - new = orig(ui, repo, commitfunc, old, *args, **kwargs) - if new != oldnode: - lock = repo.lock() - try: - tr = repo.transaction('post-amend-obst') - try: - meta = { - 'date': '%i %i' % util.makedate(), - 'user': ui.username(), - } - repo.obsstore.create(tr, oldnode, [new], 0, meta) - tr.close() - clearobscaches(repo) - finally: - tr.release() - finally: - lock.release() - return new - -### rebase -# -# - ignore obsolete changeset -# - create obsolete marker *instead of* striping - -def buildstate(orig, repo, dest, rebaseset, *ags, **kws): - """wrapper for rebase 's buildstate that exclude obsolete changeset""" - - rebaseset = repo.revs('%ld - extinct()', rebaseset) - if not rebaseset: - repo.ui.warn(_('whole rebase set is extinct and ignored.\n')) - return {} - root = min(rebaseset) - if not repo._rebasekeep and not repo[root].mutable(): - raise util.Abort(_("can't rebase immutable changeset %s") % repo[root], - hint=_('see hg help phases for details')) - return orig(repo, dest, rebaseset, *ags, **kws) - -def defineparents(orig, repo, rev, target, state, *args, **kwargs): - rebasestate = getattr(repo, '_rebasestate', None) - if rebasestate is not None: - repo._rebasestate = dict(state) - repo._rebasetarget = target - return orig(repo, rev, target, state, *args, **kwargs) - -def concludenode(orig, repo, rev, p1, *args, **kwargs): - """wrapper for rebase 's concludenode that set obsolete relation""" - newrev = orig(repo, rev, p1, *args, **kwargs) - rebasestate = getattr(repo, '_rebasestate', None) - if rebasestate is not None: - if newrev is not None: - nrev = repo[newrev].rev() - else: - nrev = p1 - repo._rebasestate[rev] = nrev - return newrev - -def cmdrebase(orig, ui, repo, *args, **kwargs): - - reallykeep = kwargs.get('keep', False) - kwargs = dict(kwargs) - kwargs['keep'] = True - repo._rebasekeep = reallykeep - - # We want to mark rebased revision as obsolete and set their - # replacements if any. Doing it in concludenode() prevents - # aborting the rebase, and is not called with all relevant - # revisions in --collapse case. Instead, we try to track the - # rebase state structure by sampling/updating it in - # defineparents() and concludenode(). The obsolete markers are - # added from this state after a successful call. - repo._rebasestate = {} - repo._rebasetarget = None - try: - l = repo.lock() - try: - res = orig(ui, repo, *args, **kwargs) - if not reallykeep: - # Filter nullmerge or unrebased entries - repo._rebasestate = dict(p for p in repo._rebasestate.iteritems() - if p[1] >= 0) - if not res and not kwargs.get('abort') and repo._rebasestate: - # Rebased revisions are assumed to be descendants of - # targetrev. If a source revision is mapped to targetrev - # or to another rebased revision, it must have been - # removed. - markers = [] - if kwargs.get('collapse'): - # collapse assume revision disapear because they are all - # in the created revision - newrevs = set(repo._rebasestate.values()) - newrevs.remove(repo._rebasetarget) - if newrevs: - # we create new revision. - # A single one by --collapse design - assert len(newrevs) == 1 - new = tuple(repo[n] for n in newrevs) - else: - # every body died. no new changeset created - new = (repo[repo._rebasetarget],) - for rev, newrev in sorted(repo._rebasestate.items()): - markers.append((repo[rev], new)) - else: - # no collapse assume revision disapear because they are - # contained in parent - for rev, newrev in sorted(repo._rebasestate.items()): - markers.append((repo[rev], (repo[newrev],))) - createmarkers(repo, markers) - return res - finally: - l.release() - finally: - delattr(repo, '_rebasestate') - delattr(repo, '_rebasetarget') - -@eh.extsetup -def _rebasewrapping(ui): - # warning about more obsolete - try: - rebase = extensions.find('rebase') - if rebase: - entry = extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors) - extensions.wrapfunction(rebase, 'buildstate', buildstate) - extensions.wrapfunction(rebase, 'defineparents', defineparents) - extensions.wrapfunction(rebase, 'concludenode', concludenode) - extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase) - except KeyError: - pass # rebase not found +from mercurial import cmdutil +from mercurial import error +from mercurial.node import bin, nullid ##################################################################### @@ -1114,10 +36,8 @@ # Code related to detection and management of older legacy format never # handled by core -import json -@eh.reposetup -def _checkoldobsolete(ui, repo): +def reposetup(ui, repo): """Detect that a repo still contains some old obsolete format """ if not repo.local():
--- a/hgext/qsync.py Thu Aug 23 18:21:18 2012 +0200 +++ b/hgext/qsync.py Fri Aug 24 10:44:23 2012 +0200 @@ -83,8 +83,8 @@ review_list.append(patch_name) except IOError: oldnode = oldfiles[patch_name] - obsolete = extensions.find('obsolete') - newnodes = obsolete.newerversion(repo, oldnode) + evolve = extensions.find('evolve') + newnodes = evolve.newerversion(repo, oldnode) if newnodes: newnodes = [n for n in newnodes if n and n[0] in repo] # remove killing if not newnodes: @@ -166,14 +166,14 @@ currentdrafts = set(d[0] for d in newdata) usednew = set() usedold = set() - obsolete = extensions.find('obsolete') + 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.newerversion(repo, oldnode) + newnodes = evolve.newerversion(repo, oldnode) if newnodes: newnodes = [n for n in newnodes if n and n[0] in repo] # remove killing if len(newnodes) > 1:
--- a/tests/test-amend.t Thu Aug 23 18:21:18 2012 +0200 +++ b/tests/test-amend.t Fri Aug 24 10:44:23 2012 +0200 @@ -5,7 +5,6 @@ > hgext.rebase= > hgext.graphlog= > EOF - $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH $ glog() {
--- a/tests/test-corrupt.t Thu Aug 23 18:21:18 2012 +0200 +++ b/tests/test-corrupt.t Fri Aug 24 10:44:23 2012 +0200 @@ -16,7 +16,6 @@ > hgext.rebase= > hgext.graphlog= > EOF - $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH $ mkcommit() { > echo "$1" >> "$1"
--- a/tests/test-evolve.t Thu Aug 23 18:21:18 2012 +0200 +++ b/tests/test-evolve.t Fri Aug 24 10:44:23 2012 +0200 @@ -15,7 +15,6 @@ > hgext.rebase= > hgext.graphlog= > EOF - $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH $ mkcommit() { > echo "$1" > "$1"
--- a/tests/test-obsolete-push.t Thu Aug 23 18:21:18 2012 +0200 +++ b/tests/test-obsolete-push.t Fri Aug 24 10:44:23 2012 +0200 @@ -5,7 +5,6 @@ > hgext.rebase= > hgext.graphlog= > EOF - $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH $ template='{rev}:{node|short}@{branch}({obsolete}/{phase}) {desc|firstline}\n'
--- a/tests/test-obsolete-rebase.t Thu Aug 23 18:21:18 2012 +0200 +++ b/tests/test-obsolete-rebase.t Fri Aug 24 10:44:23 2012 +0200 @@ -5,7 +5,7 @@ > hgext.rebase= > hgext.graphlog= > EOF - $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH + $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH $ glog() { > hg glog --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n'\
--- a/tests/test-obsolete.t Thu Aug 23 18:21:18 2012 +0200 +++ b/tests/test-obsolete.t Fri Aug 24 10:44:23 2012 +0200 @@ -8,8 +8,9 @@ > odiff=diff --rev 'limit(precursors(.),1)' --rev . > [extensions] > hgext.graphlog= + > hgext.rebase= > EOF - $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH + $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH $ mkcommit() { > echo "$1" > "$1" > hg add "$1"
--- a/tests/test-qsync.t Thu Aug 23 18:21:18 2012 +0200 +++ b/tests/test-qsync.t Fri Aug 24 10:44:23 2012 +0200 @@ -17,7 +17,6 @@ > hgext.graphlog= > hgext.mq= > EOF - $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH $ echo "qsync=$(echo $(dirname $TESTDIR))/hgext/qsync.py" >> $HGRCPATH $ mkcommit() {
--- a/tests/test-stabilize-order.t Thu Aug 23 18:21:18 2012 +0200 +++ b/tests/test-stabilize-order.t Fri Aug 24 10:44:23 2012 +0200 @@ -5,7 +5,6 @@ > hgext.rebase= > hgext.graphlog= > EOF - $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH $ glog() {
--- a/tests/test-stabilize-result.t Thu Aug 23 18:21:18 2012 +0200 +++ b/tests/test-stabilize-result.t Fri Aug 24 10:44:23 2012 +0200 @@ -5,7 +5,6 @@ > hgext.rebase= > hgext.graphlog= > EOF - $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH $ glog() {
--- a/tests/test-uncommit.t Thu Aug 23 18:21:18 2012 +0200 +++ b/tests/test-uncommit.t Fri Aug 24 10:44:23 2012 +0200 @@ -3,7 +3,6 @@ > hgext.rebase= > hgext.graphlog= > EOF - $ echo "obsolete=$(echo $(dirname $TESTDIR))/hgext/obsolete.py" >> $HGRCPATH $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH $ glog() {