hgext/obsolete.py
changeset 429 079b231b8ea4
parent 428 1c82147e9395
child 430 07db1d511faf
equal deleted inserted replaced
428:1c82147e9395 429:079b231b8ea4
    74 from mercurial import obsolete
    74 from mercurial import obsolete
    75 from mercurial.localrepo import storecache
    75 from mercurial.localrepo import storecache
    76 obsolete._enabled = True
    76 obsolete._enabled = True
    77 
    77 
    78 
    78 
       
    79 ### Extension helper
       
    80 #############################
       
    81 
       
    82 ### setup code
       
    83 
       
    84 class exthelper(object):
       
    85     """Helper for modular extension setup
       
    86 
       
    87     A single helper should be intanciated for each extension. Helper method are
       
    88     then used as decorator for various purpose.
       
    89 
       
    90     All decorator returns the original function and may be chained.
       
    91     """
       
    92 
       
    93     def __init__(self):
       
    94         self._uicallables = []
       
    95         self._extcallables = []
       
    96         self._repocallables = []
       
    97         self._revsetsymbols = []
       
    98         self._commandwrappers = []
       
    99         self._extcommandwrappers = []
       
   100         self._functionwrappers = []
       
   101         self._duckpunchers = []
       
   102 
       
   103     def final_uisetup(self, ui):
       
   104         """Method to be used as a the extension uisetup
       
   105 
       
   106         The following operations belong here:
       
   107 
       
   108         - Changes to ui.__class__ . The ui object that will be used to run the
       
   109           command has not yet been created. Changes made here will affect ui
       
   110           objects created after this, and in particular the ui that will be
       
   111           passed to runcommand
       
   112         - Command wraps (extensions.wrapcommand)
       
   113         - Changes that need to be visible by other extensions: because
       
   114           initialization occurs in phases (all extensions run uisetup, then all
       
   115           run extsetup), a change made here will be visible by other extensions
       
   116           during extsetup
       
   117         - Monkeypatches or function wraps (extensions.wrapfunction) of dispatch
       
   118           module members
       
   119         - Setup of pre-* and post-* hooks
       
   120         - pushkey setup
       
   121         """
       
   122         for cont, funcname, func in self._duckpunchers:
       
   123             setattr(cont, funcname, func)
       
   124         for command, wrapper in self._commandwrappers:
       
   125             extensions.wrapcommand(commands.table, command, wrapper)
       
   126         for cont, funcname, wrapper in self._functionwrappers:
       
   127             extensions.wrapfunction(cont, funcname, wrapper)
       
   128         for c in self._uicallables:
       
   129             c(ui)
       
   130 
       
   131     def final_extsetup(self, ui):
       
   132         """Method to be used as a the extension extsetup
       
   133 
       
   134         The following operations belong here:
       
   135 
       
   136         - Changes depending on the status of other extensions. (if extensions.find('mq'))
       
   137         - Add a global option to all commands
       
   138         - Extend revsets 
       
   139         """
       
   140         knownexts = {}
       
   141         for name, symbol in self._revsetsymbols:
       
   142             revset.symbols[name] = symbol
       
   143         for ext, command, wrapper in self._extcommandwrappers:
       
   144             if ext not in knownexts:
       
   145                 e = extensions.find('rebase')
       
   146                 if e is None:
       
   147                     raise util.Abort('extension %s not found' %e)
       
   148                 knownexts[ext] = e.cmdtable
       
   149             extensions.wrapcommand(knownexts[ext], commands, wrapper)
       
   150         for c in self._extcallables:
       
   151             c(ui)
       
   152 
       
   153     def final_reposetup(self, ui, repo):
       
   154         """Method to be used as a the extension reposetup
       
   155 
       
   156         The following operations belong here:
       
   157 
       
   158         - All hooks but pre-* and post-*
       
   159         - Modify configuration variables
       
   160         - Changes to repo.__class__, repo.dirstate.__class__
       
   161         """
       
   162         for c in self._repocallables:
       
   163             c(ui, repo)
       
   164 
       
   165     def uisetup(self, call):
       
   166         """Decorated function will be executed during uisetup
       
   167 
       
   168         example::
       
   169 
       
   170             @eh.uisetup
       
   171             def setupbabar(ui):
       
   172                 print 'this is uisetup!'
       
   173         """
       
   174         self._uicallables.append(call)
       
   175         return call
       
   176 
       
   177     def extsetup(self, call):
       
   178         """Decorated function will be executed during extsetup
       
   179 
       
   180         example::
       
   181 
       
   182             @eh.extsetup
       
   183             def setupcelestine(ui):
       
   184                 print 'this is extsetup!'
       
   185         """
       
   186         self._uicallables.append(call)
       
   187         return call
       
   188 
       
   189     def reposetup(self, call):
       
   190         """Decorated function will be executed during reposetup
       
   191 
       
   192         example::
       
   193 
       
   194             @eh.reposetup
       
   195             def setupzephir(ui, repo):
       
   196                 print 'this is reposetup!'
       
   197         """
       
   198         self._repocallables.append(call)
       
   199         return call
       
   200 
       
   201     def revset(self, symbolname):
       
   202         """Decorated function is a revset symbol
       
   203 
       
   204         The name of the symbol must be given as the decorator argument.
       
   205         The symbol is added during `extsetup`.
       
   206 
       
   207         example::
       
   208 
       
   209             @eh.revset('hidden')
       
   210             def revsetbabar(repo, subset, x):
       
   211                 args = revset.getargs(x, 0, 0, 'babar accept no argument')
       
   212                 return [r for r in subset if 'babar' in repo[r].description()]
       
   213         """
       
   214         def dec(symbol):
       
   215             self._revsetsymbols.append((symbolname, symbol))
       
   216             return symbol
       
   217         return dec
       
   218 
       
   219     def wrapcommand(self, command, extension=None):
       
   220         """Decorated function is a command wrapper
       
   221 
       
   222         The name of the command must be given as the decorator argument.
       
   223         The wrapping is installed during `uisetup`.
       
   224 
       
   225         If the second option `extension` argument is provided, the wrapping
       
   226         will be applied in the extension commandtable. This argument must be a
       
   227         string that will be searched using `extension.find` if not found and
       
   228         Abort error is raised. If the wrapping apply to an extension, it is
       
   229         installed during `extsetup`
       
   230 
       
   231         example::
       
   232 
       
   233             @eh.wrapcommand('summary')
       
   234             def wrapsummary(orig, ui, repo, *args, **kwargs):
       
   235                 ui.note('Barry!')
       
   236                 return orig(ui, repo, *args, **kwargs)
       
   237 
       
   238         """
       
   239         def dec(wrapper):
       
   240             if extension is None:
       
   241                 self._commandwrappers.append((command, wrapper))
       
   242             else:
       
   243                 self._extcommandwrappers.append((extension, command, wrapper))
       
   244             return wrapper
       
   245         return dec
       
   246 
       
   247     def wrapfunction(self, container, funcname):
       
   248         """Decorated function is a function wrapper
       
   249 
       
   250         This function take two argument, the container and the name of the
       
   251         function to wrap. The wrapping is performed during `uisetup`.
       
   252         (there is don't support extension)
       
   253 
       
   254         example::
       
   255 
       
   256             @eh.function(discovery, 'checkheads')
       
   257             def wrapfunction(orig, *args, **kwargs):
       
   258                 ui.note('His head smashed in and his heart cut out')
       
   259                 return orig(*args, **kwargs)
       
   260         """
       
   261         def dec(wrapper):
       
   262             self._functionwrappers.append((container, funcname, wrapper))
       
   263             return wrapper
       
   264         return dec
       
   265 
       
   266     def addattr(self, container, funcname):
       
   267         """Decorated function is to be added to the container
       
   268 
       
   269         This function take two argument, the container and the name of the
       
   270         function to wrap. The wrapping is performed during `uisetup`.
       
   271 
       
   272         example::
       
   273 
       
   274             @eh.function(context.changectx, 'babar')
       
   275             def babar(ctx):
       
   276                 return 'babar' in ctx.description
       
   277         """
       
   278         def dec(func):
       
   279             self._duckpunchers.append((container, funcname, func))
       
   280             return func
       
   281         return dec
       
   282 
       
   283 eh = exthelper()
       
   284 uisetup = eh.final_uisetup
       
   285 extsetup = eh.final_extsetup
       
   286 reposetup = eh.final_reposetup
       
   287 
    79 
   288 
    80 ### Patch changectx
   289 ### Patch changectx
    81 #############################
   290 #############################
    82 
   291 
       
   292 @eh.addattr(context.changectx, 'unstable')
    83 def unstable(ctx):
   293 def unstable(ctx):
    84     """is the changeset unstable (have obsolete ancestor)"""
   294     """is the changeset unstable (have obsolete ancestor)"""
    85     if ctx.node() is None:
   295     if ctx.node() is None:
    86         return False
   296         return False
    87     return ctx.rev() in ctx._repo._unstableset
   297     return ctx.rev() in ctx._repo._unstableset
    88 
   298 
    89 context.changectx.unstable = unstable
   299 
    90 
   300 @eh.addattr(context.changectx, 'extinct')
    91 def extinct(ctx):
   301 def extinct(ctx):
    92     """is the changeset extinct by other"""
   302     """is the changeset extinct by other"""
    93     if ctx.node() is None:
   303     if ctx.node() is None:
    94         return False
   304         return False
    95     return ctx.rev() in ctx._repo._extinctset
   305     return ctx.rev() in ctx._repo._extinctset
    96 
   306 
    97 context.changectx.extinct = extinct
   307 @eh.addattr(context.changectx, 'latecomer')
    98 
       
    99 def latecomer(ctx):
   308 def latecomer(ctx):
   100     """is the changeset latecomer (Try to succeed to public change)"""
   309     """is the changeset latecomer (Try to succeed to public change)"""
   101     if ctx.node() is None:
   310     if ctx.node() is None:
   102         return False
   311         return False
   103     return ctx.rev() in ctx._repo._latecomerset
   312     return ctx.rev() in ctx._repo._latecomerset
   104 
   313 
   105 context.changectx.latecomer = latecomer
   314 @eh.addattr(context.changectx, 'conflicting')
   106 
       
   107 def conflicting(ctx):
   315 def conflicting(ctx):
   108     """is the changeset conflicting (Try to succeed to public change)"""
   316     """is the changeset conflicting (Try to succeed to public change)"""
   109     if ctx.node() is None:
   317     if ctx.node() is None:
   110         return False
   318         return False
   111     return ctx.rev() in ctx._repo._conflictingset
   319     return ctx.rev() in ctx._repo._conflictingset
   112 
   320 
   113 context.changectx.conflicting = conflicting
       
   114 
       
   115 
   321 
   116 ### revset
   322 ### revset
   117 #############################
   323 #############################
   118 
   324 
       
   325 @eh.revset('hidden')
   119 def revsethidden(repo, subset, x):
   326 def revsethidden(repo, subset, x):
   120     """``hidden()``
   327     """``hidden()``
   121     Changeset is hidden.
   328     Changeset is hidden.
   122     """
   329     """
   123     args = revset.getargs(x, 0, 0, 'hidden takes no argument')
   330     args = revset.getargs(x, 0, 0, 'hidden takes no argument')
   124     return [r for r in subset if r in repo.hiddenrevs]
   331     return [r for r in subset if r in repo.hiddenrevs]
   125 
   332 
       
   333 @eh.revset('obsolete')
   126 def revsetobsolete(repo, subset, x):
   334 def revsetobsolete(repo, subset, x):
   127     """``obsolete()``
   335     """``obsolete()``
   128     Changeset is obsolete.
   336     Changeset is obsolete.
   129     """
   337     """
   130     args = revset.getargs(x, 0, 0, 'obsolete takes no argument')
   338     args = revset.getargs(x, 0, 0, 'obsolete takes no argument')
   131     return [r for r in subset if r in repo._obsoleteset and repo._phasecache.phase(repo, r) > 0]
   339     return [r for r in subset if r in repo._obsoleteset and repo._phasecache.phase(repo, r) > 0]
   132 
   340 
       
   341 @eh.revset('unstable')
   133 def revsetunstable(repo, subset, x):
   342 def revsetunstable(repo, subset, x):
   134     """``unstable()``
   343     """``unstable()``
   135     Unstable changesets are non-obsolete with obsolete ancestors.
   344     Unstable changesets are non-obsolete with obsolete ancestors.
   136     """
   345     """
   137     args = revset.getargs(x, 0, 0, 'unstable takes no arguments')
   346     args = revset.getargs(x, 0, 0, 'unstable takes no arguments')
   138     return [r for r in subset if r in repo._unstableset]
   347     return [r for r in subset if r in repo._unstableset]
   139 
   348 
       
   349 @eh.revset('suspended')
   140 def revsetsuspended(repo, subset, x):
   350 def revsetsuspended(repo, subset, x):
   141     """``suspended()``
   351     """``suspended()``
   142     Obsolete changesets with non-obsolete descendants.
   352     Obsolete changesets with non-obsolete descendants.
   143     """
   353     """
   144     args = revset.getargs(x, 0, 0, 'suspended takes no arguments')
   354     args = revset.getargs(x, 0, 0, 'suspended takes no arguments')
   145     return [r for r in subset if r in repo._suspendedset]
   355     return [r for r in subset if r in repo._suspendedset]
   146 
   356 
       
   357 @eh.revset('extinct')
   147 def revsetextinct(repo, subset, x):
   358 def revsetextinct(repo, subset, x):
   148     """``extinct()``
   359     """``extinct()``
   149     Obsolete changesets with obsolete descendants only.
   360     Obsolete changesets with obsolete descendants only.
   150     """
   361     """
   151     args = revset.getargs(x, 0, 0, 'extinct takes no arguments')
   362     args = revset.getargs(x, 0, 0, 'extinct takes no arguments')
   152     return [r for r in subset if r in repo._extinctset]
   363     return [r for r in subset if r in repo._extinctset]
   153 
   364 
       
   365 @eh.revset('latecomer')
   154 def revsetlatecomer(repo, subset, x):
   366 def revsetlatecomer(repo, subset, x):
   155     """``latecomer()``
   367     """``latecomer()``
   156     Changesets marked as successors of public changesets.
   368     Changesets marked as successors of public changesets.
   157     """
   369     """
   158     args = revset.getargs(x, 0, 0, 'latecomer takes no arguments')
   370     args = revset.getargs(x, 0, 0, 'latecomer takes no arguments')
   159     return [r for r in subset if r in repo._latecomerset]
   371     return [r for r in subset if r in repo._latecomerset]
   160 
   372 
       
   373 @eh.revset('conflicting')
   161 def revsetconflicting(repo, subset, x):
   374 def revsetconflicting(repo, subset, x):
   162     """``conflicting()``
   375     """``conflicting()``
   163     Changesets marked as successors of a same changeset.
   376     Changesets marked as successors of a same changeset.
   164     """
   377     """
   165     args = revset.getargs(x, 0, 0, 'conflicting takes no arguments')
   378     args = revset.getargs(x, 0, 0, 'conflicting takes no arguments')
   175             pr = nm.get(p[0])
   388             pr = nm.get(p[0])
   176             if pr is not None:
   389             if pr is not None:
   177                 cs.add(pr)
   390                 cs.add(pr)
   178     return cs
   391     return cs
   179 
   392 
       
   393 @eh.revset('obsparents')
       
   394 @eh.revset('precursors')
   180 def revsetprecursors(repo, subset, x):
   395 def revsetprecursors(repo, subset, x):
   181     """``precursors(set)``
   396     """``precursors(set)``
   182     Immediate precursors of changesets in set.
   397     Immediate precursors of changesets in set.
   183     """
   398     """
   184     s = revset.getset(repo, range(len(repo)), x)
   399     s = revset.getset(repo, range(len(repo)), x)
   203         pr = nm.get(p)
   418         pr = nm.get(p)
   204         if pr is not None:
   419         if pr is not None:
   205             cs.add(pr)
   420             cs.add(pr)
   206     return cs
   421     return cs
   207 
   422 
       
   423 @eh.revset('obsancestors')
       
   424 @eh.revset('allprecursors')
   208 def revsetallprecursors(repo, subset, x):
   425 def revsetallprecursors(repo, subset, x):
   209     """``allprecursors(set)``
   426     """``allprecursors(set)``
   210     Transitive precursors of changesets in set.
   427     Transitive precursors of changesets in set.
   211     """
   428     """
   212     s = revset.getset(repo, range(len(repo)), x)
   429     s = revset.getset(repo, range(len(repo)), x)
   224                 sr = nm.get(sub)
   441                 sr = nm.get(sub)
   225                 if sr is not None:
   442                 if sr is not None:
   226                     cs.add(sr)
   443                     cs.add(sr)
   227     return cs
   444     return cs
   228 
   445 
       
   446 @eh.revset('obschildrend')
       
   447 @eh.revset('successors')
   229 def revsetsuccessors(repo, subset, x):
   448 def revsetsuccessors(repo, subset, x):
   230     """``successors(set)``
   449     """``successors(set)``
   231     Immediate successors of changesets in set.
   450     Immediate successors of changesets in set.
   232     """
   451     """
   233     s = revset.getset(repo, range(len(repo)), x)
   452     s = revset.getset(repo, range(len(repo)), x)
   252         sr = nm.get(s)
   471         sr = nm.get(s)
   253         if sr is not None:
   472         if sr is not None:
   254             cs.add(sr)
   473             cs.add(sr)
   255     return cs
   474     return cs
   256 
   475 
       
   476 @eh.revset('obsdescendants')
       
   477 @eh.revset('allsuccessors')
   257 def revsetallsuccessors(repo, subset, x):
   478 def revsetallsuccessors(repo, subset, x):
   258     """``allsuccessors(set)``
   479     """``allsuccessors(set)``
   259     Transitive successors of changesets in set.
   480     Transitive successors of changesets in set.
   260     """
   481     """
   261     s = revset.getset(repo, range(len(repo)), x)
   482     s = revset.getset(repo, range(len(repo)), x)
   276     if rev in repo._suspendedset:
   497     if rev in repo._suspendedset:
   277         return 'suspended'
   498         return 'suspended'
   278     if rev in repo._unstableset:
   499     if rev in repo._unstableset:
   279         return 'unstable'
   500         return 'unstable'
   280     return 'stable'
   501     return 'stable'
       
   502 
       
   503 @eh.extsetup
       
   504 def addkeyword(ui):
       
   505     templatekw.keywords['obsolete'] = obsoletekw
   281 
   506 
   282 ### Other Extension compat
   507 ### Other Extension compat
   283 ############################
   508 ############################
   284 
   509 
   285 
   510 
   360         return res
   585         return res
   361     finally:
   586     finally:
   362         delattr(repo, '_rebasestate')
   587         delattr(repo, '_rebasestate')
   363         delattr(repo, '_rebasetarget')
   588         delattr(repo, '_rebasetarget')
   364 
   589 
   365 
   590 @eh.extsetup
   366 def extsetup(ui):
   591 def _rebasewrapping(ui):
   367 
       
   368     revset.symbols["hidden"] = revsethidden
       
   369     revset.symbols["obsolete"] = revsetobsolete
       
   370     revset.symbols["unstable"] = revsetunstable
       
   371     revset.symbols["suspended"] = revsetsuspended
       
   372     revset.symbols["extinct"] = revsetextinct
       
   373     revset.symbols["latecomer"] = revsetlatecomer
       
   374     revset.symbols["conflicting"] = revsetconflicting
       
   375     revset.symbols["obsparents"] = revsetprecursors  # DEPR
       
   376     revset.symbols["precursors"] = revsetprecursors
       
   377     revset.symbols["obsancestors"] = revsetallprecursors  # DEPR
       
   378     revset.symbols["allprecursors"] = revsetallprecursors  # bad name
       
   379     revset.symbols["successors"] = revsetsuccessors
       
   380     revset.symbols["allsuccessors"] = revsetallsuccessors  # bad name
       
   381 
       
   382     templatekw.keywords['obsolete'] = obsoletekw
       
   383 
       
   384     # warning about more obsolete
   592     # warning about more obsolete
   385     for cmd in ['commit', 'push', 'pull', 'graft', 'phase', 'unbundle']:
       
   386         entry = extensions.wrapcommand(commands.table, cmd, warnobserrors)
       
   387     try:
   593     try:
   388         rebase = extensions.find('rebase')
   594         rebase = extensions.find('rebase')
   389         if rebase:
   595         if rebase:
   390             entry = extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors)
   596             entry = extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors)
   391             extensions.wrapfunction(rebase, 'buildstate', buildstate)
   597             extensions.wrapfunction(rebase, 'buildstate', buildstate)
   396         pass  # rebase not found
   602         pass  # rebase not found
   397 
   603 
   398 ### Discovery wrapping
   604 ### Discovery wrapping
   399 #############################
   605 #############################
   400 
   606 
   401 
   607 @eh.wrapfunction(discovery, 'checkheads')
   402 def wrapcheckheads(orig, repo, remote, outgoing, *args, **kwargs):
   608 def wrapcheckheads(orig, repo, remote, outgoing, *args, **kwargs):
   403     """wrap mercurial.discovery.checkheads
   609     """wrap mercurial.discovery.checkheads
   404 
   610 
   405     * prevent unstability to be pushed
   611     * prevent unstability to be pushed
   406     * patch remote to ignore obsolete heads on remote
   612     * patch remote to ignore obsolete heads on remote
   416         if ctx.conflicting():
   622         if ctx.conflicting():
   417             raise util.Abort(_("push includes a conflicting changeset: %s!")
   623             raise util.Abort(_("push includes a conflicting changeset: %s!")
   418                              % ctx)
   624                              % ctx)
   419     return orig(repo, remote, outgoing, *args, **kwargs)
   625     return orig(repo, remote, outgoing, *args, **kwargs)
   420 
   626 
       
   627 @eh.wrapfunction(phases, 'advanceboundary')
   421 def wrapclearcache(orig, repo, *args, **kwargs):
   628 def wrapclearcache(orig, repo, *args, **kwargs):
   422     try:
   629     try:
   423         return orig(repo, *args, **kwargs)
   630         return orig(repo, *args, **kwargs)
   424     finally:
   631     finally:
   425         repo._clearobsoletecache()
   632         repo._clearobsoletecache()
   429 #############################
   636 #############################
   430 
   637 
   431 cmdtable = {}
   638 cmdtable = {}
   432 command = cmdutil.command(cmdtable)
   639 command = cmdutil.command(cmdtable)
   433 
   640 
       
   641 
       
   642 
       
   643 @command('debugsuccessors', [], '')
       
   644 def cmddebugsuccessors(ui, repo):
       
   645     """dump obsolete changesets and their successors
       
   646 
       
   647     Each line matches an existing marker, the first identifier is the
       
   648     obsolete changeset identifier, followed by it successors.
       
   649     """
       
   650     lock = repo.lock()
       
   651     try:
       
   652         allsuccessors = repo.obsstore.precursors
       
   653         for old in sorted(allsuccessors):
       
   654             successors = [sorted(m[1]) for m in allsuccessors[old]]
       
   655             for i, group in enumerate(sorted(successors)):
       
   656                 ui.write('%s' % short(old))
       
   657                 for new in group:
       
   658                     ui.write(' %s' % short(new))
       
   659                 ui.write('\n')
       
   660     finally:
       
   661         lock.release()
       
   662 
       
   663 ### Altering existing command
       
   664 #############################
       
   665 
       
   666 @eh.wrapcommand("update")
       
   667 @eh.wrapcommand("pull")
       
   668 def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
       
   669     res = origfn(ui, repo, *args, **opts)
       
   670     if repo['.'].obsolete():
       
   671         ui.warn(_('Working directory parent is obsolete\n'))
       
   672     return res
       
   673 
       
   674 def warnobserrors(orig, ui, repo, *args, **kwargs):
       
   675     """display warning is the command resulted in more instable changeset"""
       
   676     priorunstables = len(repo.revs('unstable()'))
       
   677     priorlatecomers = len(repo.revs('latecomer()'))
       
   678     priorconflictings = len(repo.revs('conflicting()'))
       
   679     #print orig, priorunstables
       
   680     #print len(repo.revs('secret() - obsolete()'))
       
   681     try:
       
   682         return orig(ui, repo, *args, **kwargs)
       
   683     finally:
       
   684         newunstables = len(repo.revs('unstable()')) - priorunstables
       
   685         newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers
       
   686         newconflictings = len(repo.revs('conflicting()')) - priorconflictings
       
   687         #print orig, newunstables
       
   688         #print len(repo.revs('secret() - obsolete()'))
       
   689         if newunstables > 0:
       
   690             ui.warn(_('%i new unstables changesets\n') % newunstables)
       
   691         if newlatecomers > 0:
       
   692             ui.warn(_('%i new latecomers changesets\n') % newlatecomers)
       
   693         if newconflictings > 0:
       
   694             ui.warn(_('%i new conflictings changesets\n') % newconflictings)
       
   695 
       
   696 @eh.extsetup
       
   697 def _coreobserrorwrapping(ui):
       
   698     # warning about more obsolete
       
   699     for cmd in ['commit', 'push', 'pull', 'graft', 'phase', 'unbundle']:
       
   700         entry = extensions.wrapcommand(commands.table, cmd, warnobserrors)
       
   701 
       
   702 @eh.wrapfunction(cmdutil, 'amend')
       
   703 def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs):
       
   704     oldnode = old.node()
       
   705     new = orig(ui, repo, commitfunc, old, *args, **kwargs)
       
   706     if new != oldnode:
       
   707         lock = repo.lock()
       
   708         try:
       
   709             tr = repo.transaction('post-amend-obst')
       
   710             try:
       
   711                 meta = {
       
   712                     'date':  '%i %i' % util.makedate(),
       
   713                     'user': ui.username(),
       
   714                     }
       
   715                 repo.obsstore.create(tr, oldnode, [new], 0, meta)
       
   716                 tr.close()
       
   717                 repo._clearobsoletecache()
       
   718             finally:
       
   719                 tr.release()
       
   720         finally:
       
   721             lock.release()
       
   722     return new
       
   723 
       
   724 
       
   725 ### diagnostique tools
       
   726 #############################
       
   727 
       
   728 def unstables(repo):
       
   729     """Return all unstable changeset"""
       
   730     return scmutil.revrange(repo, ['obsolete():: and (not obsolete())'])
       
   731 
       
   732 def newerversion(repo, obs):
       
   733     """Return the newer version of an obsolete changeset"""
       
   734     toproceed = set([(obs,)])
       
   735     # XXX known optimization available
       
   736     newer = set()
       
   737     objectrels = repo.obsstore.precursors
       
   738     while toproceed:
       
   739         current = toproceed.pop()
       
   740         assert len(current) <= 1, 'splitting not handled yet. %r' % current
       
   741         current = [n for n in current if n != nullid]
       
   742         if current:
       
   743             n, = current
       
   744             if n in objectrels:
       
   745                 markers = objectrels[n]
       
   746                 for mark in markers:
       
   747                     toproceed.add(tuple(mark[1]))
       
   748             else:
       
   749                 newer.add(tuple(current))
       
   750         else:
       
   751             newer.add(())
       
   752     return sorted(newer)
       
   753 
       
   754 ### repo subclassing
       
   755 #############################
       
   756 
       
   757 @eh.reposetup
       
   758 def _reposetup(ui, repo):
       
   759     if not repo.local():
       
   760         return
       
   761 
       
   762     if not util.safehasattr(repo.opener, 'tryread'):
       
   763         raise util.Abort('Obsolete extension requires Mercurial 2.2 (or later)')
       
   764     opush = repo.push
       
   765     o_updatebranchcache = repo.updatebranchcache
       
   766 
       
   767     # /!\ api change in  Hg 2.2 (97efd26eb9576f39590812ea9) /!\
       
   768     if util.safehasattr(repo, '_journalfiles'): # Hg 2.2
       
   769         o_journalfiles = repo._journalfiles
       
   770     o_writejournal = repo._writejournal
       
   771     o_hook = repo.hook
       
   772 
       
   773 
       
   774     class obsoletingrepo(repo.__class__):
       
   775 
       
   776         # workaround
       
   777         def hook(self, name, throw=False, **args):
       
   778             if 'pushkey' in name:
       
   779                 args.pop('new')
       
   780                 args.pop('old')
       
   781             return o_hook(name, throw=False, **args)
       
   782 
       
   783         ### Public method
       
   784         # XXX Kill me
       
   785         def obsoletedby(self, node):
       
   786             """return the set of node that make <node> obsolete (obj)"""
       
   787             others = set()
       
   788             for marker in self.obsstore.precursors.get(node, []):
       
   789                 others.update(marker[1])
       
   790             return others
       
   791 
       
   792         # XXX Kill me
       
   793         def obsolete(self, node):
       
   794             """return the set of node that <node> make obsolete (sub)"""
       
   795             return set(marker[0] for marker in self.obsstore.successors.get(node, []))
       
   796 
       
   797         # XXX move me on obssotre
       
   798         @util.propertycache
       
   799         def _obsoleteset(self):
       
   800             """the set of obsolete revision"""
       
   801             obs = set()
       
   802             nm = self.changelog.nodemap
       
   803             for prec in self.obsstore.precursors:
       
   804                 rev = nm.get(prec)
       
   805                 if rev is not None:
       
   806                     obs.add(rev)
       
   807             return obs
       
   808 
       
   809         # XXX move me on obssotre
       
   810         @util.propertycache
       
   811         def _unstableset(self):
       
   812             """the set of non obsolete revision with obsolete parent"""
       
   813             return set(self.revs('(obsolete()::) - obsolete()'))
       
   814 
       
   815         # XXX move me on obssotre
       
   816         @util.propertycache
       
   817         def _suspendedset(self):
       
   818             """the set of obsolete parent with non obsolete descendant"""
       
   819             return set(self.revs('obsolete() and obsolete()::unstable()'))
       
   820 
       
   821         # XXX move me on obssotre
       
   822         @util.propertycache
       
   823         def _extinctset(self):
       
   824             """the set of obsolete parent without non obsolete descendant"""
       
   825             return set(self.revs('obsolete() - obsolete()::unstable()'))
       
   826 
       
   827         # XXX move me on obssotre
       
   828         @util.propertycache
       
   829         def _latecomerset(self):
       
   830             """the set of rev trying to obsolete public revision"""
       
   831             query = 'allsuccessors(public()) - obsolete() - public()'
       
   832             return set(self.revs(query))
       
   833 
       
   834         # XXX move me on obssotre
       
   835         @util.propertycache
       
   836         def _conflictingset(self):
       
   837             """the set of rev trying to obsolete public revision"""
       
   838             conflicting = set()
       
   839             obsstore = self.obsstore
       
   840             newermap = {}
       
   841             for ctx in self.set('(not public()) - obsolete()'):
       
   842                 prec = obsstore.successors.get(ctx.node(), ())
       
   843                 toprocess = set(prec)
       
   844                 while toprocess:
       
   845                     prec = toprocess.pop()[0]
       
   846                     if prec not in newermap:
       
   847                         newermap[prec] = newerversion(self, prec)
       
   848                     newer = [n for n in newermap[prec] if n] # filter kill
       
   849                     if len(newer) > 1:
       
   850                         conflicting.add(ctx.rev())
       
   851                         break
       
   852                 toprocess.update(obsstore.successors.get(prec, ()))
       
   853             return conflicting
       
   854 
       
   855         def _clearobsoletecache(self):
       
   856             if '_obsoleteset' in vars(self):
       
   857                 del self._obsoleteset
       
   858             self._clearunstablecache()
       
   859 
       
   860         def updatebranchcache(self):
       
   861             o_updatebranchcache()
       
   862             self._clearunstablecache()
       
   863 
       
   864         def _clearunstablecache(self):
       
   865             if '_unstableset' in vars(self):
       
   866                 del self._unstableset
       
   867             if '_suspendedset' in vars(self):
       
   868                 del self._suspendedset
       
   869             if '_extinctset' in vars(self):
       
   870                 del self._extinctset
       
   871             if '_latecomerset' in vars(self):
       
   872                 del self._latecomerset
       
   873             if '_conflictingset' in vars(self):
       
   874                 del self._conflictingset
       
   875 
       
   876         # XXX kill me
       
   877         def addobsolete(self, sub, obj):
       
   878             """Add a relation marking that node <sub> is a new version of <obj>"""
       
   879             assert sub != obj
       
   880             if not repo[obj].phase():
       
   881                 if sub is None:
       
   882                     self.ui.warn(
       
   883                         _("trying to kill immutable changeset %(obj)s\n")
       
   884                         % {'obj': short(obj)})
       
   885                 if sub is not None:
       
   886                     self.ui.warn(
       
   887                         _("%(sub)s try to obsolete immutable changeset %(obj)s\n")
       
   888                         % {'sub': short(sub), 'obj': short(obj)})
       
   889             lock = self.lock()
       
   890             try:
       
   891                 tr = self.transaction('add-obsolete')
       
   892                 try:
       
   893                     meta = {
       
   894                         'date':  '%i %i' % util.makedate(),
       
   895                         'user': ui.username(),
       
   896                         }
       
   897                     subs = (sub == nullid) and [] or [sub]
       
   898                     mid = self.obsstore.create(tr, obj, subs, 0, meta)
       
   899                     tr.close()
       
   900                     self._clearobsoletecache()
       
   901                     return mid
       
   902                 finally:
       
   903                     tr.release()
       
   904             finally:
       
   905                 lock.release()
       
   906 
       
   907         # XXX kill me
       
   908         def addcollapsedobsolete(self, oldnodes, newnode):
       
   909             """Mark oldnodes as collapsed into newnode."""
       
   910             # Assume oldnodes are all descendants of a single rev
       
   911             rootrevs = self.revs('roots(%ln)', oldnodes)
       
   912             assert len(rootrevs) == 1, rootrevs
       
   913             #rootnode = self[rootrevs[0]].node()
       
   914             for n in oldnodes:
       
   915                 self.addobsolete(newnode, n)
       
   916 
       
   917         ### pull // push support
       
   918 
       
   919         def push(self, remote, *args, **opts):
       
   920             """wrapper around pull that pull obsolete relation"""
       
   921             try:
       
   922                 result = opush(remote, *args, **opts)
       
   923             except util.Abort, ex:
       
   924                 hint = _("use 'hg stabilize' to get a stable history "
       
   925                          "or --force to ignore warnings")
       
   926                 if (len(ex.args) >= 1
       
   927                     and ex.args[0].startswith('push includes ')
       
   928                     and ex.hint is None):
       
   929                     ex.hint = hint
       
   930                 raise
       
   931             return result
       
   932 
       
   933 
       
   934     repo.__class__ = obsoletingrepo
       
   935 
       
   936 @eh.reposetup
       
   937 def _checkoldobsolete(ui, repo):
       
   938     if not repo.local():
       
   939         return
       
   940     for arg in sys.argv:
       
   941         if 'debugc' in arg:
       
   942             break
       
   943     else:
       
   944         data = repo.opener.tryread('obsolete-relations')
       
   945         if not data:
       
   946             data = repo.sopener.tryread('obsoletemarkers')
       
   947         if data:
       
   948             raise util.Abort('old format of obsolete marker detected!\n'
       
   949                              'run `hg debugconvertobsolete` once.')
       
   950 
       
   951 ### serialisation
       
   952 #############################
       
   953 
       
   954 def _obsdeserialise(flike):
       
   955     """read a file like object serialised with _obsserialise
       
   956 
       
   957     this desierialize into a {subject -> objects} mapping"""
       
   958     rels = {}
       
   959     for line in flike:
       
   960         subhex, objhex = line.split()
       
   961         subnode = bin(subhex)
       
   962         if subnode == nullid:
       
   963             subnode = None
       
   964         rels.setdefault( subnode, set()).add(bin(objhex))
       
   965     return rels
   434 
   966 
   435 @command('debugconvertobsolete', [], '')
   967 @command('debugconvertobsolete', [], '')
   436 def cmddebugconvertobsolete(ui, repo):
   968 def cmddebugconvertobsolete(ui, repo):
   437     """import markers from an .hg/obsolete-relations file"""
   969     """import markers from an .hg/obsolete-relations file"""
   438     cnt = 0
   970     cnt = 0
   517     if not some:
  1049     if not some:
   518             ui.warn('nothing to do\n')
  1050             ui.warn('nothing to do\n')
   519     ui.status('%i obsolete marker converted\n' % cnt)
  1051     ui.status('%i obsolete marker converted\n' % cnt)
   520     if err:
  1052     if err:
   521         ui.write_err('%i conversion failed. check you graph!\n' % err)
  1053         ui.write_err('%i conversion failed. check you graph!\n' % err)
   522 
       
   523 @command('debugsuccessors', [], '')
       
   524 def cmddebugsuccessors(ui, repo):
       
   525     """dump obsolete changesets and their successors
       
   526 
       
   527     Each line matches an existing marker, the first identifier is the
       
   528     obsolete changeset identifier, followed by it successors.
       
   529     """
       
   530     lock = repo.lock()
       
   531     try:
       
   532         allsuccessors = repo.obsstore.precursors
       
   533         for old in sorted(allsuccessors):
       
   534             successors = [sorted(m[1]) for m in allsuccessors[old]]
       
   535             for i, group in enumerate(sorted(successors)):
       
   536                 ui.write('%s' % short(old))
       
   537                 for new in group:
       
   538                     ui.write(' %s' % short(new))
       
   539                 ui.write('\n')
       
   540     finally:
       
   541         lock.release()
       
   542 
       
   543 ### Altering existing command
       
   544 #############################
       
   545 
       
   546 def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
       
   547     res = origfn(ui, repo, *args, **opts)
       
   548     if repo['.'].obsolete():
       
   549         ui.warn(_('Working directory parent is obsolete\n'))
       
   550     return res
       
   551 
       
   552 def warnobserrors(orig, ui, repo, *args, **kwargs):
       
   553     """display warning is the command resulted in more instable changeset"""
       
   554     priorunstables = len(repo.revs('unstable()'))
       
   555     priorlatecomers = len(repo.revs('latecomer()'))
       
   556     priorconflictings = len(repo.revs('conflicting()'))
       
   557     #print orig, priorunstables
       
   558     #print len(repo.revs('secret() - obsolete()'))
       
   559     try:
       
   560         return orig(ui, repo, *args, **kwargs)
       
   561     finally:
       
   562         newunstables = len(repo.revs('unstable()')) - priorunstables
       
   563         newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers
       
   564         newconflictings = len(repo.revs('conflicting()')) - priorconflictings
       
   565         #print orig, newunstables
       
   566         #print len(repo.revs('secret() - obsolete()'))
       
   567         if newunstables > 0:
       
   568             ui.warn(_('%i new unstables changesets\n') % newunstables)
       
   569         if newlatecomers > 0:
       
   570             ui.warn(_('%i new latecomers changesets\n') % newlatecomers)
       
   571         if newconflictings > 0:
       
   572             ui.warn(_('%i new conflictings changesets\n') % newconflictings)
       
   573 
       
   574 def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs):
       
   575     oldnode = old.node()
       
   576     new = orig(ui, repo, commitfunc, old, *args, **kwargs)
       
   577     if new != oldnode:
       
   578         lock = repo.lock()
       
   579         try:
       
   580             tr = repo.transaction('post-amend-obst')
       
   581             try:
       
   582                 meta = {
       
   583                     'date':  '%i %i' % util.makedate(),
       
   584                     'user': ui.username(),
       
   585                     }
       
   586                 repo.obsstore.create(tr, oldnode, [new], 0, meta)
       
   587                 tr.close()
       
   588                 repo._clearobsoletecache()
       
   589             finally:
       
   590                 tr.release()
       
   591         finally:
       
   592             lock.release()
       
   593     return new
       
   594 
       
   595 def uisetup(ui):
       
   596     extensions.wrapcommand(commands.table, "update", wrapmayobsoletewc)
       
   597     extensions.wrapcommand(commands.table, "pull", wrapmayobsoletewc)
       
   598     extensions.wrapfunction(cmdutil, 'amend', wrapcmdutilamend)
       
   599     extensions.wrapfunction(discovery, 'checkheads', wrapcheckheads)
       
   600     extensions.wrapfunction(phases, 'advanceboundary', wrapclearcache)
       
   601 
       
   602 ### serialisation
       
   603 #############################
       
   604 
       
   605 def _obsserialise(obssubrels, flike):
       
   606     """serialise an obsolete relation mapping in a plain text one
       
   607 
       
   608     this is for subject -> [objects] mapping
       
   609 
       
   610     format is::
       
   611 
       
   612         <subject-full-hex> <object-full-hex>\n"""
       
   613     for sub, objs in obssubrels.iteritems():
       
   614         for obj in objs:
       
   615             if sub is None:
       
   616                 sub = nullid
       
   617             flike.write('%s %s\n' % (hex(sub), hex(obj)))
       
   618 
       
   619 def _obsdeserialise(flike):
       
   620     """read a file like object serialised with _obsserialise
       
   621 
       
   622     this desierialize into a {subject -> objects} mapping"""
       
   623     rels = {}
       
   624     for line in flike:
       
   625         subhex, objhex = line.split()
       
   626         subnode = bin(subhex)
       
   627         if subnode == nullid:
       
   628             subnode = None
       
   629         rels.setdefault( subnode, set()).add(bin(objhex))
       
   630     return rels
       
   631 
       
   632 ### diagnostique tools
       
   633 #############################
       
   634 
       
   635 def unstables(repo):
       
   636     """Return all unstable changeset"""
       
   637     return scmutil.revrange(repo, ['obsolete():: and (not obsolete())'])
       
   638 
       
   639 def newerversion(repo, obs):
       
   640     """Return the newer version of an obsolete changeset"""
       
   641     toproceed = set([(obs,)])
       
   642     # XXX known optimization available
       
   643     newer = set()
       
   644     objectrels = repo.obsstore.precursors
       
   645     while toproceed:
       
   646         current = toproceed.pop()
       
   647         assert len(current) <= 1, 'splitting not handled yet. %r' % current
       
   648         current = [n for n in current if n != nullid]
       
   649         if current:
       
   650             n, = current
       
   651             if n in objectrels:
       
   652                 markers = objectrels[n]
       
   653                 for mark in markers:
       
   654                     toproceed.add(tuple(mark[1]))
       
   655             else:
       
   656                 newer.add(tuple(current))
       
   657         else:
       
   658             newer.add(())
       
   659     return sorted(newer)
       
   660 
       
   661 ### repo subclassing
       
   662 #############################
       
   663 
       
   664 def reposetup(ui, repo):
       
   665     if not repo.local():
       
   666         return
       
   667 
       
   668     if not util.safehasattr(repo.opener, 'tryread'):
       
   669         raise util.Abort('Obsolete extension requires Mercurial 2.2 (or later)')
       
   670     opush = repo.push
       
   671     o_updatebranchcache = repo.updatebranchcache
       
   672 
       
   673     # /!\ api change in  Hg 2.2 (97efd26eb9576f39590812ea9) /!\
       
   674     if util.safehasattr(repo, '_journalfiles'): # Hg 2.2
       
   675         o_journalfiles = repo._journalfiles
       
   676     o_writejournal = repo._writejournal
       
   677     o_hook = repo.hook
       
   678 
       
   679 
       
   680     class obsoletingrepo(repo.__class__):
       
   681 
       
   682         # workaround
       
   683         def hook(self, name, throw=False, **args):
       
   684             if 'pushkey' in name:
       
   685                 args.pop('new')
       
   686                 args.pop('old')
       
   687             return o_hook(name, throw=False, **args)
       
   688 
       
   689         ### Public method
       
   690         def obsoletedby(self, node):
       
   691             """return the set of node that make <node> obsolete (obj)"""
       
   692             others = set()
       
   693             for marker in self.obsstore.precursors.get(node, []):
       
   694                 others.update(marker[1])
       
   695             return others
       
   696 
       
   697         def obsolete(self, node):
       
   698             """return the set of node that <node> make obsolete (sub)"""
       
   699             return set(marker[0] for marker in self.obsstore.successors.get(node, []))
       
   700 
       
   701         @util.propertycache
       
   702         def _obsoleteset(self):
       
   703             """the set of obsolete revision"""
       
   704             obs = set()
       
   705             nm = self.changelog.nodemap
       
   706             for prec in self.obsstore.precursors:
       
   707                 rev = nm.get(prec)
       
   708                 if rev is not None:
       
   709                     obs.add(rev)
       
   710             return obs
       
   711 
       
   712         @util.propertycache
       
   713         def _unstableset(self):
       
   714             """the set of non obsolete revision with obsolete parent"""
       
   715             return set(self.revs('(obsolete()::) - obsolete()'))
       
   716 
       
   717         @util.propertycache
       
   718         def _suspendedset(self):
       
   719             """the set of obsolete parent with non obsolete descendant"""
       
   720             return set(self.revs('obsolete() and obsolete()::unstable()'))
       
   721 
       
   722         @util.propertycache
       
   723         def _extinctset(self):
       
   724             """the set of obsolete parent without non obsolete descendant"""
       
   725             return set(self.revs('obsolete() - obsolete()::unstable()'))
       
   726 
       
   727         @util.propertycache
       
   728         def _latecomerset(self):
       
   729             """the set of rev trying to obsolete public revision"""
       
   730             query = 'allsuccessors(public()) - obsolete() - public()'
       
   731             return set(self.revs(query))
       
   732 
       
   733         @util.propertycache
       
   734         def _conflictingset(self):
       
   735             """the set of rev trying to obsolete public revision"""
       
   736             conflicting = set()
       
   737             obsstore = self.obsstore
       
   738             newermap = {}
       
   739             for ctx in self.set('(not public()) - obsolete()'):
       
   740                 prec = obsstore.successors.get(ctx.node(), ())
       
   741                 toprocess = set(prec)
       
   742                 while toprocess:
       
   743                     prec = toprocess.pop()[0]
       
   744                     if prec not in newermap:
       
   745                         newermap[prec] = newerversion(self, prec)
       
   746                     newer = [n for n in newermap[prec] if n] # filter kill
       
   747                     if len(newer) > 1:
       
   748                         conflicting.add(ctx.rev())
       
   749                         break
       
   750                 toprocess.update(obsstore.successors.get(prec, ()))
       
   751             return conflicting
       
   752 
       
   753         def _clearobsoletecache(self):
       
   754             if '_obsoleteset' in vars(self):
       
   755                 del self._obsoleteset
       
   756             self._clearunstablecache()
       
   757 
       
   758         def updatebranchcache(self):
       
   759             o_updatebranchcache()
       
   760             self._clearunstablecache()
       
   761 
       
   762         def _clearunstablecache(self):
       
   763             if '_unstableset' in vars(self):
       
   764                 del self._unstableset
       
   765             if '_suspendedset' in vars(self):
       
   766                 del self._suspendedset
       
   767             if '_extinctset' in vars(self):
       
   768                 del self._extinctset
       
   769             if '_latecomerset' in vars(self):
       
   770                 del self._latecomerset
       
   771             if '_conflictingset' in vars(self):
       
   772                 del self._conflictingset
       
   773 
       
   774         def addobsolete(self, sub, obj):
       
   775             """Add a relation marking that node <sub> is a new version of <obj>"""
       
   776             assert sub != obj
       
   777             if not repo[obj].phase():
       
   778                 if sub is None:
       
   779                     self.ui.warn(
       
   780                         _("trying to kill immutable changeset %(obj)s\n")
       
   781                         % {'obj': short(obj)})
       
   782                 if sub is not None:
       
   783                     self.ui.warn(
       
   784                         _("%(sub)s try to obsolete immutable changeset %(obj)s\n")
       
   785                         % {'sub': short(sub), 'obj': short(obj)})
       
   786             lock = self.lock()
       
   787             try:
       
   788                 tr = self.transaction('add-obsolete')
       
   789                 try:
       
   790                     meta = {
       
   791                         'date':  '%i %i' % util.makedate(),
       
   792                         'user': ui.username(),
       
   793                         }
       
   794                     subs = (sub == nullid) and [] or [sub]
       
   795                     mid = self.obsstore.create(tr, obj, subs, 0, meta)
       
   796                     tr.close()
       
   797                     self._clearobsoletecache()
       
   798                     return mid
       
   799                 finally:
       
   800                     tr.release()
       
   801             finally:
       
   802                 lock.release()
       
   803 
       
   804         def addcollapsedobsolete(self, oldnodes, newnode):
       
   805             """Mark oldnodes as collapsed into newnode."""
       
   806             # Assume oldnodes are all descendants of a single rev
       
   807             rootrevs = self.revs('roots(%ln)', oldnodes)
       
   808             assert len(rootrevs) == 1, rootrevs
       
   809             #rootnode = self[rootrevs[0]].node()
       
   810             for n in oldnodes:
       
   811                 self.addobsolete(newnode, n)
       
   812 
       
   813         ### pull // push support
       
   814 
       
   815         def push(self, remote, *args, **opts):
       
   816             """wrapper around pull that pull obsolete relation"""
       
   817             try:
       
   818                 result = opush(remote, *args, **opts)
       
   819             except util.Abort, ex:
       
   820                 hint = _("use 'hg stabilize' to get a stable history "
       
   821                          "or --force to ignore warnings")
       
   822                 if (len(ex.args) >= 1
       
   823                     and ex.args[0].startswith('push includes ')
       
   824                     and ex.hint is None):
       
   825                     ex.hint = hint
       
   826                 raise
       
   827             return result
       
   828 
       
   829 
       
   830     repo.__class__ = obsoletingrepo
       
   831     for arg in sys.argv:
       
   832         if 'debugc' in arg:
       
   833             break
       
   834     else:
       
   835         data = repo.opener.tryread('obsolete-relations')
       
   836         if not data:
       
   837             data = repo.sopener.tryread('obsoletemarkers')
       
   838         if data:
       
   839             raise util.Abort('old format of obsolete marker detected!\n'
       
   840                              'run `hg debugconvertobsolete` once.')