--- a/hgext/evolve.py Thu Nov 22 11:30:36 2012 +0100
+++ b/hgext/evolve.py Tue Nov 27 14:27:50 2012 +0100
@@ -19,7 +19,7 @@
- improves some aspect of the early implementation in 2.3
'''
-testedwith = '2.3 2.3.1 2.3.2'
+testedwith = '2.4'
buglink = 'https://bitbucket.org/marmoute/mutable-history/issues'
@@ -29,20 +29,20 @@
try:
from mercurial import obsolete
+ getattr(obsolete, 'getrevs') # 2.4 specific
if not obsolete._enabled:
obsolete._enabled = True
-except ImportError:
- raise util.Abort('Evolve extension requires Mercurial 2.3 (or later)')
-
+except (ImportError, AttributeError):
+ raise util.Abort('Evolve extension requires Mercurial 2.4.x')
try:
- getattr(obsolete, 'getrevs') # 2.4 specific
- raise util.Abort('Your version of Mercurial is too recent for this '
- 'version of evolve',
- hint="upgrade your evolve")
+ from mercurial import bookmarks
+ bookmarks.bmstore
+ raise util.Abort('This version of Evolve is too old for you mercurial version')
except AttributeError:
pass
+
from mercurial import bookmarks
from mercurial import cmdutil
from mercurial import commands
@@ -326,191 +326,16 @@
yield marker
-#####################################################################
-### Obsolescence Caching Logic ###
-#####################################################################
-
-# IN CORE fb72eec7efd8
-
-# 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 stores meaningful set of revisions related to obsolescence
-# (obsolete, unstable, etc.)
-#
-# Here is:
-#
-# - Computation of meaningful sets
-# - 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
-try:
- cachefuncs = obsolete.cachefuncs
- cachefor = obsolete.cachefor
- getobscache = obsolete.getobscache
- clearobscaches = obsolete.clearobscaches
-except AttributeError:
- 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]
+cachefuncs = obsolete.cachefuncs
+cachefor = obsolete.cachefor
+getrevs = obsolete.getrevs
+clearobscaches = obsolete.clearobscaches
#####################################################################
### Complete troubles computation logic ###
#####################################################################
-# there is two kind of trouble not handled by core right now:
-# - bumped: (successors for public changeset)
+# there is one kind of trouble not handled by core right now:
# - divergent: (two changeset try to succeed to the same precursors)
#
# This section add support for those two addition trouble
@@ -522,13 +347,6 @@
### Cache computation
latediff = 1 # flag to prevent taking late comer fix into account
-@cachefor('bumped')
-def _computebumpedset(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('divergent')
def _computedivergentset(repo):
@@ -537,7 +355,7 @@
obsstore = repo.obsstore
newermap = {}
for ctx in repo.set('(not public()) - obsolete()'):
- mark = obsstore.successors.get(ctx.node(), ())
+ mark = obsstore.precursors.get(ctx.node(), ())
toprocess = set(mark)
while toprocess:
prec = toprocess.pop()[0]
@@ -547,18 +365,15 @@
if len(newer) > 1:
divergent.add(ctx.rev())
break
- toprocess.update(obsstore.successors.get(prec, ()))
+ toprocess.update(obsstore.precursors.get(prec, ()))
return divergent
### changectx method
@eh.addattr(context.changectx, 'latecomer')
-@eh.addattr(context.changectx, 'bumped')
-def bumped(ctx):
+def latecomer(ctx):
"""is the changeset bumped (Try to succeed to public change)"""
- if ctx.node() is None:
- return False
- return ctx.rev() in getobscache(ctx._repo, 'bumped')
+ return ctx.bumped()
@eh.addattr(context.changectx, 'conflicting')
@eh.addattr(context.changectx, 'divergent')
@@ -566,19 +381,11 @@
"""is the changeset divergent (Try to succeed to public change)"""
if ctx.node() is None:
return False
- return ctx.rev() in getobscache(ctx._repo, 'divergent')
+ return ctx.rev() in getrevs(ctx._repo, 'divergent')
### revset symbol
-@eh.revset('latecomer')
-@eh.revset('bumped')
-def revsetbumped(repo, subset, x):
- """``bumped()``
- Changesets marked as successors of public changesets.
- """
- args = revset.getargs(x, 0, 0, 'bumped takes no arguments')
- lates = getobscache(repo, 'bumped')
- return [r for r in subset if r in lates]
+eh.revset('latecomer')(revset.symbols['bumped'])
@eh.revset('conflicting')
@eh.revset('divergent')
@@ -587,7 +394,7 @@
Changesets marked as successors of a same changeset.
"""
args = revset.getargs(x, 0, 0, 'divergent takes no arguments')
- conf = getobscache(repo, 'divergent')
+ conf = getrevs(repo, 'divergent')
return [r for r in subset if r in conf]
@@ -598,16 +405,13 @@
def wrapcheckheads(orig, repo, remote, outgoing, *args, **kwargs):
"""wrap mercurial.discovery.checkheads
- * prevent bumped and unstable to be pushed
+ * prevent divergent 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.bumped():
- raise util.Abort(_("push includes a bumped changeset: %s!")
- % ctx)
if ctx.divergent():
raise util.Abort(_("push includes a divergent changeset: %s!")
% ctx)
@@ -659,44 +463,7 @@
# - function to travel throught the obsolescence graph
# - function to find useful changeset to stabilize
-### Marker Create
-# NOW IN CORE f85816af6294
-try:
- createmarkers = obsolete.createmarkers
-except AttributeError:
- 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()
+createmarkers = obsolete.createmarkers
### Useful alias
@@ -732,7 +499,7 @@
### Troubled revset symbol
@eh.revset('troubled')
-def revsetbumped(repo, subset, x):
+def revsettroubled(repo, subset, x):
"""``troubled()``
Changesets with troubles.
"""
@@ -749,7 +516,7 @@
"""Precursor of a changeset"""
cs = set()
nm = repo.changelog.nodemap
- markerbysubj = repo.obsstore.successors
+ markerbysubj = repo.obsstore.precursors
for r in s:
for p in markerbysubj.get(repo[r].node(), ()):
pr = nm.get(p[0])
@@ -761,7 +528,7 @@
"""transitive precursors of a subset"""
toproceed = [repo[r].node() for r in s]
seen = set()
- allsubjects = repo.obsstore.successors
+ allsubjects = repo.obsstore.precursors
while toproceed:
nc = toproceed.pop()
for mark in allsubjects.get(nc, ()):
@@ -781,7 +548,7 @@
"""Successors of a changeset"""
cs = set()
nm = repo.changelog.nodemap
- markerbyobj = repo.obsstore.precursors
+ markerbyobj = repo.obsstore.successors
for r in s:
for p in markerbyobj.get(repo[r].node(), ()):
for sub in p[1]:
@@ -797,7 +564,7 @@
marker. """
toproceed = [repo[r].node() for r in s]
seen = set()
- allobjects = repo.obsstore.precursors
+ allobjects = repo.obsstore.successors
while toproceed:
nc = toproceed.pop()
for mark in allobjects.get(nc, ()):
@@ -822,7 +589,7 @@
"""Return the newer version of an obsolete changeset"""
# prec -> markers mapping
- markersfor = repo.obsstore.precursors
+ markersfor = repo.obsstore.successors
# Stack of node need to know the last successors set
toproceed = [initialnode]
@@ -944,16 +711,6 @@
# they are subject to changes
-if 'hidden' not in revset.symbols:
- # in 2.3+
- @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):
@@ -961,7 +718,7 @@
Obsolete changesets with non-obsolete descendants.
"""
args = revset.getargs(x, 0, 0, 'suspended takes no arguments')
- suspended = getobscache(repo, 'suspended')
+ suspended = getrevs(repo, 'suspended')
return [r for r in subset if r in suspended]
@@ -1098,9 +855,9 @@
ui.note(s)
ret = orig(ui, repo, *args, **kwargs)
- nbunstable = len(getobscache(repo, 'unstable'))
- nbbumped = len(getobscache(repo, 'bumped'))
- nbdivergent = len(getobscache(repo, 'unstable'))
+ nbunstable = len(getrevs(repo, 'unstable'))
+ nbbumped = len(getrevs(repo, 'bumped'))
+ nbdivergent = len(getrevs(repo, 'unstable'))
write('unstable: %i changesets\n', nbunstable)
write('bumped: %i changesets\n', nbbumped)
write('divergent: %i changesets\n', nbdivergent)
@@ -1111,134 +868,6 @@
### 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.
-
-# IN CORE 63e45aee46d4
-
-if getattr(cmdutil, 'obsolete', None) is None:
- @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 getattr(repo, '_rebasekeep', False)
- 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):
@@ -1246,14 +875,7 @@
try:
rebase = extensions.find('rebase')
if rebase:
- incore = getattr(rebase, 'obsolete', None) is not None
- if not incore:
- extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase)
extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors)
- if not incore:
- extensions.wrapfunction(rebase, 'buildstate', buildstate)
- extensions.wrapfunction(rebase, 'defineparents', defineparents)
- extensions.wrapfunction(rebase, 'concludenode', concludenode)
except KeyError:
pass # rebase not found