Mercurial > evolve
diff hgext/obsolete.py @ 491:6989d8fe4ed2
merge evolve and obsolete
author | Pierre-Yves David <pierre-yves.david@logilab.fr> |
---|---|
date | Fri, 24 Aug 2012 10:44:23 +0200 |
parents | 8096833e9226 |
children | 9825c7da5b54 |
line wrap: on
line diff
--- 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():