--- 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():