--- a/hgext/obsolete.py Tue Aug 07 11:35:45 2012 +0200
+++ b/hgext/obsolete.py Tue Aug 07 14:08:31 2012 +0200
@@ -76,46 +76,253 @@
obsolete._enabled = True
+### Extension helper
+#############################
+
+### setup code
+
+class exthelper(object):
+ """Helper for modular extension setup
+
+ A single helper should be intanciated for each extension. Helper method are
+ then used as decorator for various purpose.
+
+ All decorator returns the original function and may be chained.
+ """
+
+ def __init__(self):
+ self._uicallables = []
+ self._extcallables = []
+ self._repocallables = []
+ self._revsetsymbols = []
+ self._commandwrappers = []
+ self._extcommandwrappers = []
+ self._functionwrappers = []
+ self._duckpunchers = []
+
+ def final_uisetup(self, ui):
+ """Method to be used as a 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 by other extensions: because
+ initialization occurs in phases (all extensions run uisetup, then all
+ run extsetup), a change made here will be visible by other extensions
+ during extsetup
+ - Monkeypatches or function wraps (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
+ - Extend revsets
+ """
+ knownexts = {}
+ for name, symbol in self._revsetsymbols:
+ revset.symbols[name] = symbol
+ for ext, command, wrapper in self._extcommandwrappers:
+ if ext not in knownexts:
+ e = extensions.find('rebase')
+ if e is None:
+ raise util.Abort('extension %s not found' %e)
+ 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 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 apply 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 take two argument, the container and the name of the
+ function to wrap. The wrapping is performed during `uisetup`.
+ (there is don't support extension)
+
+ 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 take two argument, 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
+
### Patch changectx
#############################
+@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 ctx._repo._unstableset
-context.changectx.unstable = 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 ctx._repo._extinctset
-context.changectx.extinct = extinct
-
+@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 ctx._repo._latecomerset
-context.changectx.latecomer = 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 ctx._repo._conflictingset
-context.changectx.conflicting = conflicting
-
### revset
#############################
+@eh.revset('hidden')
def revsethidden(repo, subset, x):
"""``hidden()``
Changeset is hidden.
@@ -123,6 +330,7 @@
args = revset.getargs(x, 0, 0, 'hidden takes no argument')
return [r for r in subset if r in repo.hiddenrevs]
+@eh.revset('obsolete')
def revsetobsolete(repo, subset, x):
"""``obsolete()``
Changeset is obsolete.
@@ -130,6 +338,7 @@
args = revset.getargs(x, 0, 0, 'obsolete takes no argument')
return [r for r in subset if r in repo._obsoleteset and repo._phasecache.phase(repo, r) > 0]
+@eh.revset('unstable')
def revsetunstable(repo, subset, x):
"""``unstable()``
Unstable changesets are non-obsolete with obsolete ancestors.
@@ -137,6 +346,7 @@
args = revset.getargs(x, 0, 0, 'unstable takes no arguments')
return [r for r in subset if r in repo._unstableset]
+@eh.revset('suspended')
def revsetsuspended(repo, subset, x):
"""``suspended()``
Obsolete changesets with non-obsolete descendants.
@@ -144,6 +354,7 @@
args = revset.getargs(x, 0, 0, 'suspended takes no arguments')
return [r for r in subset if r in repo._suspendedset]
+@eh.revset('extinct')
def revsetextinct(repo, subset, x):
"""``extinct()``
Obsolete changesets with obsolete descendants only.
@@ -151,6 +362,7 @@
args = revset.getargs(x, 0, 0, 'extinct takes no arguments')
return [r for r in subset if r in repo._extinctset]
+@eh.revset('latecomer')
def revsetlatecomer(repo, subset, x):
"""``latecomer()``
Changesets marked as successors of public changesets.
@@ -158,6 +370,7 @@
args = revset.getargs(x, 0, 0, 'latecomer takes no arguments')
return [r for r in subset if r in repo._latecomerset]
+@eh.revset('conflicting')
def revsetconflicting(repo, subset, x):
"""``conflicting()``
Changesets marked as successors of a same changeset.
@@ -177,6 +390,8 @@
cs.add(pr)
return cs
+@eh.revset('obsparents')
+@eh.revset('precursors')
def revsetprecursors(repo, subset, x):
"""``precursors(set)``
Immediate precursors of changesets in set.
@@ -205,6 +420,8 @@
cs.add(pr)
return cs
+@eh.revset('obsancestors')
+@eh.revset('allprecursors')
def revsetallprecursors(repo, subset, x):
"""``allprecursors(set)``
Transitive precursors of changesets in set.
@@ -226,6 +443,8 @@
cs.add(sr)
return cs
+@eh.revset('obschildrend')
+@eh.revset('successors')
def revsetsuccessors(repo, subset, x):
"""``successors(set)``
Immediate successors of changesets in set.
@@ -254,6 +473,8 @@
cs.add(sr)
return cs
+@eh.revset('obsdescendants')
+@eh.revset('allsuccessors')
def revsetallsuccessors(repo, subset, x):
"""``allsuccessors(set)``
Transitive successors of changesets in set.
@@ -279,6 +500,10 @@
return 'unstable'
return 'stable'
+@eh.extsetup
+def addkeyword(ui):
+ templatekw.keywords['obsolete'] = obsoletekw
+
### Other Extension compat
############################
@@ -362,28 +587,9 @@
delattr(repo, '_rebasestate')
delattr(repo, '_rebasetarget')
-
-def extsetup(ui):
-
- revset.symbols["hidden"] = revsethidden
- revset.symbols["obsolete"] = revsetobsolete
- revset.symbols["unstable"] = revsetunstable
- revset.symbols["suspended"] = revsetsuspended
- revset.symbols["extinct"] = revsetextinct
- revset.symbols["latecomer"] = revsetlatecomer
- revset.symbols["conflicting"] = revsetconflicting
- revset.symbols["obsparents"] = revsetprecursors # DEPR
- revset.symbols["precursors"] = revsetprecursors
- revset.symbols["obsancestors"] = revsetallprecursors # DEPR
- revset.symbols["allprecursors"] = revsetallprecursors # bad name
- revset.symbols["successors"] = revsetsuccessors
- revset.symbols["allsuccessors"] = revsetallsuccessors # bad name
-
- templatekw.keywords['obsolete'] = obsoletekw
-
+@eh.extsetup
+def _rebasewrapping(ui):
# warning about more obsolete
- for cmd in ['commit', 'push', 'pull', 'graft', 'phase', 'unbundle']:
- entry = extensions.wrapcommand(commands.table, cmd, warnobserrors)
try:
rebase = extensions.find('rebase')
if rebase:
@@ -398,7 +604,7 @@
### Discovery wrapping
#############################
-
+@eh.wrapfunction(discovery, 'checkheads')
def wrapcheckheads(orig, repo, remote, outgoing, *args, **kwargs):
"""wrap mercurial.discovery.checkheads
@@ -418,6 +624,7 @@
% ctx)
return orig(repo, remote, outgoing, *args, **kwargs)
+@eh.wrapfunction(phases, 'advanceboundary')
def wrapclearcache(orig, repo, *args, **kwargs):
try:
return orig(repo, *args, **kwargs)
@@ -432,6 +639,331 @@
command = cmdutil.command(cmdtable)
+
+@command('debugsuccessors', [], '')
+def cmddebugsuccessors(ui, repo):
+ """dump obsolete changesets and their successors
+
+ Each line matches an existing marker, the first identifier is the
+ obsolete changeset identifier, followed by it successors.
+ """
+ lock = repo.lock()
+ try:
+ allsuccessors = repo.obsstore.precursors
+ for old in sorted(allsuccessors):
+ successors = [sorted(m[1]) for m in allsuccessors[old]]
+ for i, group in enumerate(sorted(successors)):
+ ui.write('%s' % short(old))
+ for new in group:
+ ui.write(' %s' % short(new))
+ ui.write('\n')
+ finally:
+ lock.release()
+
+### Altering existing command
+#############################
+
+@eh.wrapcommand("update")
+@eh.wrapcommand("pull")
+def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
+ res = origfn(ui, repo, *args, **opts)
+ if repo['.'].obsolete():
+ ui.warn(_('Working directory parent is obsolete\n'))
+ return res
+
+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()'))
+ #print orig, priorunstables
+ #print len(repo.revs('secret() - obsolete()'))
+ 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
+ #print orig, newunstables
+ #print len(repo.revs('secret() - obsolete()'))
+ if newunstables > 0:
+ ui.warn(_('%i new unstables changesets\n') % newunstables)
+ if newlatecomers > 0:
+ ui.warn(_('%i new latecomers changesets\n') % newlatecomers)
+ if newconflictings > 0:
+ ui.warn(_('%i new conflictings changesets\n') % newconflictings)
+
+@eh.extsetup
+def _coreobserrorwrapping(ui):
+ # warning about more obsolete
+ for cmd in ['commit', 'push', 'pull', 'graft', 'phase', 'unbundle']:
+ entry = extensions.wrapcommand(commands.table, cmd, warnobserrors)
+
+@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()
+ repo._clearobsoletecache()
+ finally:
+ tr.release()
+ finally:
+ lock.release()
+ return new
+
+
+### diagnostique tools
+#############################
+
+def unstables(repo):
+ """Return all unstable changeset"""
+ return scmutil.revrange(repo, ['obsolete():: and (not obsolete())'])
+
+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)
+
+### repo subclassing
+#############################
+
+@eh.reposetup
+def _reposetup(ui, repo):
+ if not repo.local():
+ return
+
+ if not util.safehasattr(repo.opener, 'tryread'):
+ raise util.Abort('Obsolete extension requires Mercurial 2.2 (or later)')
+ opush = repo.push
+ o_updatebranchcache = repo.updatebranchcache
+
+ # /!\ api change in Hg 2.2 (97efd26eb9576f39590812ea9) /!\
+ if util.safehasattr(repo, '_journalfiles'): # Hg 2.2
+ o_journalfiles = repo._journalfiles
+ o_writejournal = repo._writejournal
+ o_hook = repo.hook
+
+
+ class obsoletingrepo(repo.__class__):
+
+ # workaround
+ def hook(self, name, throw=False, **args):
+ if 'pushkey' in name:
+ args.pop('new')
+ args.pop('old')
+ return o_hook(name, throw=False, **args)
+
+ ### Public method
+ # XXX Kill me
+ def obsoletedby(self, node):
+ """return the set of node that make <node> obsolete (obj)"""
+ others = set()
+ for marker in self.obsstore.precursors.get(node, []):
+ others.update(marker[1])
+ return others
+
+ # XXX Kill me
+ def obsolete(self, node):
+ """return the set of node that <node> make obsolete (sub)"""
+ return set(marker[0] for marker in self.obsstore.successors.get(node, []))
+
+ # XXX move me on obssotre
+ @util.propertycache
+ def _obsoleteset(self):
+ """the set of obsolete revision"""
+ obs = set()
+ nm = self.changelog.nodemap
+ for prec in self.obsstore.precursors:
+ rev = nm.get(prec)
+ if rev is not None:
+ obs.add(rev)
+ return obs
+
+ # XXX move me on obssotre
+ @util.propertycache
+ def _unstableset(self):
+ """the set of non obsolete revision with obsolete parent"""
+ return set(self.revs('(obsolete()::) - obsolete()'))
+
+ # XXX move me on obssotre
+ @util.propertycache
+ def _suspendedset(self):
+ """the set of obsolete parent with non obsolete descendant"""
+ return set(self.revs('obsolete() and obsolete()::unstable()'))
+
+ # XXX move me on obssotre
+ @util.propertycache
+ def _extinctset(self):
+ """the set of obsolete parent without non obsolete descendant"""
+ return set(self.revs('obsolete() - obsolete()::unstable()'))
+
+ # XXX move me on obssotre
+ @util.propertycache
+ def _latecomerset(self):
+ """the set of rev trying to obsolete public revision"""
+ query = 'allsuccessors(public()) - obsolete() - public()'
+ return set(self.revs(query))
+
+ # XXX move me on obssotre
+ @util.propertycache
+ def _conflictingset(self):
+ """the set of rev trying to obsolete public revision"""
+ conflicting = set()
+ obsstore = self.obsstore
+ newermap = {}
+ for ctx in self.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(self, 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
+
+ def _clearobsoletecache(self):
+ if '_obsoleteset' in vars(self):
+ del self._obsoleteset
+ self._clearunstablecache()
+
+ def updatebranchcache(self):
+ o_updatebranchcache()
+ self._clearunstablecache()
+
+ def _clearunstablecache(self):
+ if '_unstableset' in vars(self):
+ del self._unstableset
+ if '_suspendedset' in vars(self):
+ del self._suspendedset
+ if '_extinctset' in vars(self):
+ del self._extinctset
+ if '_latecomerset' in vars(self):
+ del self._latecomerset
+ if '_conflictingset' in vars(self):
+ del self._conflictingset
+
+ # XXX kill me
+ def addobsolete(self, sub, obj):
+ """Add a relation marking that node <sub> is a new version of <obj>"""
+ assert sub != obj
+ if not repo[obj].phase():
+ if sub is None:
+ self.ui.warn(
+ _("trying to kill immutable changeset %(obj)s\n")
+ % {'obj': short(obj)})
+ if sub is not None:
+ self.ui.warn(
+ _("%(sub)s try to obsolete immutable changeset %(obj)s\n")
+ % {'sub': short(sub), 'obj': short(obj)})
+ lock = self.lock()
+ try:
+ tr = self.transaction('add-obsolete')
+ try:
+ meta = {
+ 'date': '%i %i' % util.makedate(),
+ 'user': ui.username(),
+ }
+ subs = (sub == nullid) and [] or [sub]
+ mid = self.obsstore.create(tr, obj, subs, 0, meta)
+ tr.close()
+ self._clearobsoletecache()
+ return mid
+ finally:
+ tr.release()
+ finally:
+ lock.release()
+
+ # XXX kill me
+ def addcollapsedobsolete(self, oldnodes, newnode):
+ """Mark oldnodes as collapsed into newnode."""
+ # Assume oldnodes are all descendants of a single rev
+ rootrevs = self.revs('roots(%ln)', oldnodes)
+ assert len(rootrevs) == 1, rootrevs
+ #rootnode = self[rootrevs[0]].node()
+ for n in oldnodes:
+ self.addobsolete(newnode, n)
+
+ ### pull // push support
+
+ 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__ = obsoletingrepo
+
+@eh.reposetup
+def _checkoldobsolete(ui, repo):
+ if not repo.local():
+ return
+ for arg in sys.argv:
+ if 'debugc' in arg:
+ break
+ else:
+ data = repo.opener.tryread('obsolete-relations')
+ if not data:
+ data = repo.sopener.tryread('obsoletemarkers')
+ if data:
+ raise util.Abort('old format of obsolete marker detected!\n'
+ 'run `hg debugconvertobsolete` once.')
+
+### serialisation
+#############################
+
+def _obsdeserialise(flike):
+ """read a file like object serialised with _obsserialise
+
+ this desierialize into a {subject -> objects} mapping"""
+ rels = {}
+ for line in flike:
+ subhex, objhex = line.split()
+ subnode = bin(subhex)
+ if subnode == nullid:
+ subnode = None
+ rels.setdefault( subnode, set()).add(bin(objhex))
+ return rels
+
@command('debugconvertobsolete', [], '')
def cmddebugconvertobsolete(ui, repo):
"""import markers from an .hg/obsolete-relations file"""
@@ -519,322 +1051,3 @@
ui.status('%i obsolete marker converted\n' % cnt)
if err:
ui.write_err('%i conversion failed. check you graph!\n' % err)
-
-@command('debugsuccessors', [], '')
-def cmddebugsuccessors(ui, repo):
- """dump obsolete changesets and their successors
-
- Each line matches an existing marker, the first identifier is the
- obsolete changeset identifier, followed by it successors.
- """
- lock = repo.lock()
- try:
- allsuccessors = repo.obsstore.precursors
- for old in sorted(allsuccessors):
- successors = [sorted(m[1]) for m in allsuccessors[old]]
- for i, group in enumerate(sorted(successors)):
- ui.write('%s' % short(old))
- for new in group:
- ui.write(' %s' % short(new))
- ui.write('\n')
- finally:
- lock.release()
-
-### Altering existing command
-#############################
-
-def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
- res = origfn(ui, repo, *args, **opts)
- if repo['.'].obsolete():
- ui.warn(_('Working directory parent is obsolete\n'))
- return res
-
-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()'))
- #print orig, priorunstables
- #print len(repo.revs('secret() - obsolete()'))
- 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
- #print orig, newunstables
- #print len(repo.revs('secret() - obsolete()'))
- if newunstables > 0:
- ui.warn(_('%i new unstables changesets\n') % newunstables)
- if newlatecomers > 0:
- ui.warn(_('%i new latecomers changesets\n') % newlatecomers)
- if newconflictings > 0:
- ui.warn(_('%i new conflictings changesets\n') % newconflictings)
-
-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()
- repo._clearobsoletecache()
- finally:
- tr.release()
- finally:
- lock.release()
- return new
-
-def uisetup(ui):
- extensions.wrapcommand(commands.table, "update", wrapmayobsoletewc)
- extensions.wrapcommand(commands.table, "pull", wrapmayobsoletewc)
- extensions.wrapfunction(cmdutil, 'amend', wrapcmdutilamend)
- extensions.wrapfunction(discovery, 'checkheads', wrapcheckheads)
- extensions.wrapfunction(phases, 'advanceboundary', wrapclearcache)
-
-### serialisation
-#############################
-
-def _obsserialise(obssubrels, flike):
- """serialise an obsolete relation mapping in a plain text one
-
- this is for subject -> [objects] mapping
-
- format is::
-
- <subject-full-hex> <object-full-hex>\n"""
- for sub, objs in obssubrels.iteritems():
- for obj in objs:
- if sub is None:
- sub = nullid
- flike.write('%s %s\n' % (hex(sub), hex(obj)))
-
-def _obsdeserialise(flike):
- """read a file like object serialised with _obsserialise
-
- this desierialize into a {subject -> objects} mapping"""
- rels = {}
- for line in flike:
- subhex, objhex = line.split()
- subnode = bin(subhex)
- if subnode == nullid:
- subnode = None
- rels.setdefault( subnode, set()).add(bin(objhex))
- return rels
-
-### diagnostique tools
-#############################
-
-def unstables(repo):
- """Return all unstable changeset"""
- return scmutil.revrange(repo, ['obsolete():: and (not obsolete())'])
-
-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)
-
-### repo subclassing
-#############################
-
-def reposetup(ui, repo):
- if not repo.local():
- return
-
- if not util.safehasattr(repo.opener, 'tryread'):
- raise util.Abort('Obsolete extension requires Mercurial 2.2 (or later)')
- opush = repo.push
- o_updatebranchcache = repo.updatebranchcache
-
- # /!\ api change in Hg 2.2 (97efd26eb9576f39590812ea9) /!\
- if util.safehasattr(repo, '_journalfiles'): # Hg 2.2
- o_journalfiles = repo._journalfiles
- o_writejournal = repo._writejournal
- o_hook = repo.hook
-
-
- class obsoletingrepo(repo.__class__):
-
- # workaround
- def hook(self, name, throw=False, **args):
- if 'pushkey' in name:
- args.pop('new')
- args.pop('old')
- return o_hook(name, throw=False, **args)
-
- ### Public method
- def obsoletedby(self, node):
- """return the set of node that make <node> obsolete (obj)"""
- others = set()
- for marker in self.obsstore.precursors.get(node, []):
- others.update(marker[1])
- return others
-
- def obsolete(self, node):
- """return the set of node that <node> make obsolete (sub)"""
- return set(marker[0] for marker in self.obsstore.successors.get(node, []))
-
- @util.propertycache
- def _obsoleteset(self):
- """the set of obsolete revision"""
- obs = set()
- nm = self.changelog.nodemap
- for prec in self.obsstore.precursors:
- rev = nm.get(prec)
- if rev is not None:
- obs.add(rev)
- return obs
-
- @util.propertycache
- def _unstableset(self):
- """the set of non obsolete revision with obsolete parent"""
- return set(self.revs('(obsolete()::) - obsolete()'))
-
- @util.propertycache
- def _suspendedset(self):
- """the set of obsolete parent with non obsolete descendant"""
- return set(self.revs('obsolete() and obsolete()::unstable()'))
-
- @util.propertycache
- def _extinctset(self):
- """the set of obsolete parent without non obsolete descendant"""
- return set(self.revs('obsolete() - obsolete()::unstable()'))
-
- @util.propertycache
- def _latecomerset(self):
- """the set of rev trying to obsolete public revision"""
- query = 'allsuccessors(public()) - obsolete() - public()'
- return set(self.revs(query))
-
- @util.propertycache
- def _conflictingset(self):
- """the set of rev trying to obsolete public revision"""
- conflicting = set()
- obsstore = self.obsstore
- newermap = {}
- for ctx in self.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(self, 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
-
- def _clearobsoletecache(self):
- if '_obsoleteset' in vars(self):
- del self._obsoleteset
- self._clearunstablecache()
-
- def updatebranchcache(self):
- o_updatebranchcache()
- self._clearunstablecache()
-
- def _clearunstablecache(self):
- if '_unstableset' in vars(self):
- del self._unstableset
- if '_suspendedset' in vars(self):
- del self._suspendedset
- if '_extinctset' in vars(self):
- del self._extinctset
- if '_latecomerset' in vars(self):
- del self._latecomerset
- if '_conflictingset' in vars(self):
- del self._conflictingset
-
- def addobsolete(self, sub, obj):
- """Add a relation marking that node <sub> is a new version of <obj>"""
- assert sub != obj
- if not repo[obj].phase():
- if sub is None:
- self.ui.warn(
- _("trying to kill immutable changeset %(obj)s\n")
- % {'obj': short(obj)})
- if sub is not None:
- self.ui.warn(
- _("%(sub)s try to obsolete immutable changeset %(obj)s\n")
- % {'sub': short(sub), 'obj': short(obj)})
- lock = self.lock()
- try:
- tr = self.transaction('add-obsolete')
- try:
- meta = {
- 'date': '%i %i' % util.makedate(),
- 'user': ui.username(),
- }
- subs = (sub == nullid) and [] or [sub]
- mid = self.obsstore.create(tr, obj, subs, 0, meta)
- tr.close()
- self._clearobsoletecache()
- return mid
- finally:
- tr.release()
- finally:
- lock.release()
-
- def addcollapsedobsolete(self, oldnodes, newnode):
- """Mark oldnodes as collapsed into newnode."""
- # Assume oldnodes are all descendants of a single rev
- rootrevs = self.revs('roots(%ln)', oldnodes)
- assert len(rootrevs) == 1, rootrevs
- #rootnode = self[rootrevs[0]].node()
- for n in oldnodes:
- self.addobsolete(newnode, n)
-
- ### pull // push support
-
- 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__ = obsoletingrepo
- for arg in sys.argv:
- if 'debugc' in arg:
- break
- else:
- data = repo.opener.tryread('obsolete-relations')
- if not data:
- data = repo.sopener.tryread('obsoletemarkers')
- if data:
- raise util.Abort('old format of obsolete marker detected!\n'
- 'run `hg debugconvertobsolete` once.')