hgext/obsolete.py
changeset 437 48ac58354b7b
parent 436 1e042eeb2d1a
child 438 b1b7b5ef506a
equal deleted inserted replaced
436:1e042eeb2d1a 437:48ac58354b7b
    77 # - Older format compat
    77 # - Older format compat
    78 
    78 
    79 
    79 
    80 
    80 
    81 #####################################################################
    81 #####################################################################
    82 ### Extension helper                                             ####
    82 ### Extension helper                                              ###
    83 #####################################################################
    83 #####################################################################
    84 
    84 
    85 class exthelper(object):
    85 class exthelper(object):
    86     """Helper for modular extension setup
    86     """Helper for modular extension setup
    87 
    87 
   305 eh = exthelper()
   305 eh = exthelper()
   306 uisetup = eh.final_uisetup
   306 uisetup = eh.final_uisetup
   307 extsetup = eh.final_extsetup
   307 extsetup = eh.final_extsetup
   308 reposetup = eh.final_reposetup
   308 reposetup = eh.final_reposetup
   309 
   309 
   310 
   310 #####################################################################
   311 ### Patch changectx
   311 ### Obsolescence Caching Logic                                    ###
   312 #############################
   312 #####################################################################
   313 
   313 
   314 @eh.addattr(context.changectx, 'unstable')
   314 @eh.addattr(context.changectx, 'unstable')
   315 def unstable(ctx):
   315 def unstable(ctx):
   316     """is the changeset unstable (have obsolete ancestor)"""
   316     """is the changeset unstable (have obsolete ancestor)"""
   317     if ctx.node() is None:
   317     if ctx.node() is None:
   324     """is the changeset extinct by other"""
   324     """is the changeset extinct by other"""
   325     if ctx.node() is None:
   325     if ctx.node() is None:
   326         return False
   326         return False
   327     return ctx.rev() in ctx._repo._extinctset
   327     return ctx.rev() in ctx._repo._extinctset
   328 
   328 
       
   329 @eh.revset('obsolete')
       
   330 def revsetobsolete(repo, subset, x):
       
   331     """``obsolete()``
       
   332     Changeset is obsolete.
       
   333     """
       
   334     args = revset.getargs(x, 0, 0, 'obsolete takes no argument')
       
   335     return [r for r in subset if r in repo._obsoleteset and repo._phasecache.phase(repo, r) > 0]
       
   336 
       
   337 @eh.revset('unstable')
       
   338 def revsetunstable(repo, subset, x):
       
   339     """``unstable()``
       
   340     Unstable changesets are non-obsolete with obsolete ancestors.
       
   341     """
       
   342     args = revset.getargs(x, 0, 0, 'unstable takes no arguments')
       
   343     return [r for r in subset if r in repo._unstableset]
       
   344 
       
   345 @eh.revset('extinct')
       
   346 def revsetextinct(repo, subset, x):
       
   347     """``extinct()``
       
   348     Obsolete changesets with obsolete descendants only.
       
   349     """
       
   350     args = revset.getargs(x, 0, 0, 'extinct takes no arguments')
       
   351     return [r for r in subset if r in repo._extinctset]
       
   352 
       
   353 
       
   354 @eh.wrapfunction(phases, 'advanceboundary')
       
   355 def wrapclearcache(orig, repo, *args, **kwargs):
       
   356     try:
       
   357         return orig(repo, *args, **kwargs)
       
   358     finally:
       
   359         repo._clearobsoletecache()
       
   360 
       
   361 @eh.reposetup
       
   362 def _repocachesetup(ui, repo):
       
   363     if not repo.local():
       
   364         return
       
   365 
       
   366     o_updatebranchcache = repo.updatebranchcache
       
   367     class cachedobsolescencegrepo(repo.__class__):
       
   368 
       
   369         # XXX move me on obssotre
       
   370         @util.propertycache
       
   371         def _obsoleteset(self):
       
   372             """the set of obsolete revision"""
       
   373             obs = set()
       
   374             nm = self.changelog.nodemap
       
   375             for prec in self.obsstore.precursors:
       
   376                 rev = nm.get(prec)
       
   377                 if rev is not None:
       
   378                     obs.add(rev)
       
   379             return obs
       
   380 
       
   381         # XXX move me on obssotre
       
   382         @util.propertycache
       
   383         def _unstableset(self):
       
   384             """the set of non obsolete revision with obsolete parent"""
       
   385             return set(self.revs('(obsolete()::) - obsolete()'))
       
   386 
       
   387         # XXX move me on obssotre
       
   388         @util.propertycache
       
   389         def _suspendedset(self):
       
   390             """the set of obsolete parent with non obsolete descendant"""
       
   391             return set(self.revs('obsolete() and obsolete()::unstable()'))
       
   392 
       
   393         # XXX move me on obssotre
       
   394         @util.propertycache
       
   395         def _extinctset(self):
       
   396             """the set of obsolete parent without non obsolete descendant"""
       
   397             return set(self.revs('obsolete() - obsolete()::unstable()'))
       
   398 
       
   399         # XXX move me on obssotre
       
   400         @util.propertycache
       
   401         def _latecomerset(self):
       
   402             """the set of rev trying to obsolete public revision"""
       
   403             query = 'allsuccessors(public()) - obsolete() - public()'
       
   404             return set(self.revs(query))
       
   405 
       
   406         # XXX move me on obssotre
       
   407         @util.propertycache
       
   408         def _conflictingset(self):
       
   409             """the set of rev trying to obsolete public revision"""
       
   410             conflicting = set()
       
   411             obsstore = self.obsstore
       
   412             newermap = {}
       
   413             for ctx in self.set('(not public()) - obsolete()'):
       
   414                 prec = obsstore.successors.get(ctx.node(), ())
       
   415                 toprocess = set(prec)
       
   416                 while toprocess:
       
   417                     prec = toprocess.pop()[0]
       
   418                     if prec not in newermap:
       
   419                         newermap[prec] = newerversion(self, prec)
       
   420                     newer = [n for n in newermap[prec] if n] # filter kill
       
   421                     if len(newer) > 1:
       
   422                         conflicting.add(ctx.rev())
       
   423                         break
       
   424                 toprocess.update(obsstore.successors.get(prec, ()))
       
   425             return conflicting
       
   426 
       
   427         def _clearobsoletecache(self):
       
   428             if '_obsoleteset' in vars(self):
       
   429                 del self._obsoleteset
       
   430             self._clearunstablecache()
       
   431 
       
   432         def updatebranchcache(self):
       
   433             o_updatebranchcache()
       
   434             self._clearunstablecache()
       
   435 
       
   436         def _clearunstablecache(self):
       
   437             if '_unstableset' in vars(self):
       
   438                 del self._unstableset
       
   439             if '_suspendedset' in vars(self):
       
   440                 del self._suspendedset
       
   441             if '_extinctset' in vars(self):
       
   442                 del self._extinctset
       
   443             if '_latecomerset' in vars(self):
       
   444                 del self._latecomerset
       
   445             if '_conflictingset' in vars(self):
       
   446                 del self._conflictingset
       
   447 
       
   448     repo.__class__ = cachedobsolescencegrepo
       
   449 
       
   450 #####################################################################
       
   451 ### Complete troubles computation logic                           ###
       
   452 #####################################################################
       
   453 
   329 @eh.addattr(context.changectx, 'latecomer')
   454 @eh.addattr(context.changectx, 'latecomer')
   330 def latecomer(ctx):
   455 def latecomer(ctx):
   331     """is the changeset latecomer (Try to succeed to public change)"""
   456     """is the changeset latecomer (Try to succeed to public change)"""
   332     if ctx.node() is None:
   457     if ctx.node() is None:
   333         return False
   458         return False
   339     if ctx.node() is None:
   464     if ctx.node() is None:
   340         return False
   465         return False
   341     return ctx.rev() in ctx._repo._conflictingset
   466     return ctx.rev() in ctx._repo._conflictingset
   342 
   467 
   343 
   468 
   344 ### revset
   469 #####################################################################
   345 #############################
   470 ### Additional Utilities functions                                ###
   346 
   471 #####################################################################
   347 @eh.revset('hidden')
       
   348 def revsethidden(repo, subset, x):
       
   349     """``hidden()``
       
   350     Changeset is hidden.
       
   351     """
       
   352     args = revset.getargs(x, 0, 0, 'hidden takes no argument')
       
   353     return [r for r in subset if r in repo.hiddenrevs]
       
   354 
       
   355 @eh.revset('obsolete')
       
   356 def revsetobsolete(repo, subset, x):
       
   357     """``obsolete()``
       
   358     Changeset is obsolete.
       
   359     """
       
   360     args = revset.getargs(x, 0, 0, 'obsolete takes no argument')
       
   361     return [r for r in subset if r in repo._obsoleteset and repo._phasecache.phase(repo, r) > 0]
       
   362 
       
   363 @eh.revset('unstable')
       
   364 def revsetunstable(repo, subset, x):
       
   365     """``unstable()``
       
   366     Unstable changesets are non-obsolete with obsolete ancestors.
       
   367     """
       
   368     args = revset.getargs(x, 0, 0, 'unstable takes no arguments')
       
   369     return [r for r in subset if r in repo._unstableset]
       
   370 
       
   371 @eh.revset('suspended')
       
   372 def revsetsuspended(repo, subset, x):
       
   373     """``suspended()``
       
   374     Obsolete changesets with non-obsolete descendants.
       
   375     """
       
   376     args = revset.getargs(x, 0, 0, 'suspended takes no arguments')
       
   377     return [r for r in subset if r in repo._suspendedset]
       
   378 
       
   379 @eh.revset('extinct')
       
   380 def revsetextinct(repo, subset, x):
       
   381     """``extinct()``
       
   382     Obsolete changesets with obsolete descendants only.
       
   383     """
       
   384     args = revset.getargs(x, 0, 0, 'extinct takes no arguments')
       
   385     return [r for r in subset if r in repo._extinctset]
       
   386 
       
   387 @eh.revset('latecomer')
       
   388 def revsetlatecomer(repo, subset, x):
       
   389     """``latecomer()``
       
   390     Changesets marked as successors of public changesets.
       
   391     """
       
   392     args = revset.getargs(x, 0, 0, 'latecomer takes no arguments')
       
   393     return [r for r in subset if r in repo._latecomerset]
       
   394 
       
   395 @eh.revset('conflicting')
       
   396 def revsetconflicting(repo, subset, x):
       
   397     """``conflicting()``
       
   398     Changesets marked as successors of a same changeset.
       
   399     """
       
   400     args = revset.getargs(x, 0, 0, 'conflicting takes no arguments')
       
   401     return [r for r in subset if r in repo._conflictingset]
       
   402 
   472 
   403 def _precursors(repo, s):
   473 def _precursors(repo, s):
   404     """Precursor of a changeset"""
   474     """Precursor of a changeset"""
   405     cs = set()
   475     cs = set()
   406     nm = repo.changelog.nodemap
   476     nm = repo.changelog.nodemap
   409         for p in markerbysubj.get(repo[r].node(), ()):
   479         for p in markerbysubj.get(repo[r].node(), ()):
   410             pr = nm.get(p[0])
   480             pr = nm.get(p[0])
   411             if pr is not None:
   481             if pr is not None:
   412                 cs.add(pr)
   482                 cs.add(pr)
   413     return cs
   483     return cs
   414 
       
   415 @eh.revset('obsparents')
       
   416 @eh.revset('precursors')
       
   417 def revsetprecursors(repo, subset, x):
       
   418     """``precursors(set)``
       
   419     Immediate precursors of changesets in set.
       
   420     """
       
   421     s = revset.getset(repo, range(len(repo)), x)
       
   422     cs = _precursors(repo, s)
       
   423     return [r for r in subset if r in cs]
       
   424 
   484 
   425 def _allprecursors(repo, s):  # XXX we need a better naming
   485 def _allprecursors(repo, s):  # XXX we need a better naming
   426     """transitive precursors of a subset"""
   486     """transitive precursors of a subset"""
   427     toproceed = [repo[r].node() for r in s]
   487     toproceed = [repo[r].node() for r in s]
   428     seen = set()
   488     seen = set()
   440         pr = nm.get(p)
   500         pr = nm.get(p)
   441         if pr is not None:
   501         if pr is not None:
   442             cs.add(pr)
   502             cs.add(pr)
   443     return cs
   503     return cs
   444 
   504 
   445 @eh.revset('obsancestors')
       
   446 @eh.revset('allprecursors')
       
   447 def revsetallprecursors(repo, subset, x):
       
   448     """``allprecursors(set)``
       
   449     Transitive precursors of changesets in set.
       
   450     """
       
   451     s = revset.getset(repo, range(len(repo)), x)
       
   452     cs = _allprecursors(repo, s)
       
   453     return [r for r in subset if r in cs]
       
   454 
       
   455 def _successors(repo, s):
   505 def _successors(repo, s):
   456     """Successors of a changeset"""
   506     """Successors of a changeset"""
   457     cs = set()
   507     cs = set()
   458     nm = repo.changelog.nodemap
   508     nm = repo.changelog.nodemap
   459     markerbyobj = repo.obsstore.precursors
   509     markerbyobj = repo.obsstore.precursors
   462             for sub in p[1]:
   512             for sub in p[1]:
   463                 sr = nm.get(sub)
   513                 sr = nm.get(sub)
   464                 if sr is not None:
   514                 if sr is not None:
   465                     cs.add(sr)
   515                     cs.add(sr)
   466     return cs
   516     return cs
   467 
       
   468 @eh.revset('obschildrend')
       
   469 @eh.revset('successors')
       
   470 def revsetsuccessors(repo, subset, x):
       
   471     """``successors(set)``
       
   472     Immediate successors of changesets in set.
       
   473     """
       
   474     s = revset.getset(repo, range(len(repo)), x)
       
   475     cs = _successors(repo, s)
       
   476     return [r for r in subset if r in cs]
       
   477 
   517 
   478 def _allsuccessors(repo, s):  # XXX we need a better naming
   518 def _allsuccessors(repo, s):  # XXX we need a better naming
   479     """transitive successors of a subset"""
   519     """transitive successors of a subset"""
   480     toproceed = [repo[r].node() for r in s]
   520     toproceed = [repo[r].node() for r in s]
   481     seen = set()
   521     seen = set()
   493         sr = nm.get(s)
   533         sr = nm.get(s)
   494         if sr is not None:
   534         if sr is not None:
   495             cs.add(sr)
   535             cs.add(sr)
   496     return cs
   536     return cs
   497 
   537 
       
   538 
       
   539 ### diagnostique tools
       
   540 
       
   541 def unstables(repo):
       
   542     """Return all unstable changeset"""
       
   543     return scmutil.revrange(repo, ['obsolete():: and (not obsolete())'])
       
   544 
       
   545 def newerversion(repo, obs):
       
   546     """Return the newer version of an obsolete changeset"""
       
   547     toproceed = set([(obs,)])
       
   548     # XXX known optimization available
       
   549     newer = set()
       
   550     objectrels = repo.obsstore.precursors
       
   551     while toproceed:
       
   552         current = toproceed.pop()
       
   553         assert len(current) <= 1, 'splitting not handled yet. %r' % current
       
   554         current = [n for n in current if n != nullid]
       
   555         if current:
       
   556             n, = current
       
   557             if n in objectrels:
       
   558                 markers = objectrels[n]
       
   559                 for mark in markers:
       
   560                     toproceed.add(tuple(mark[1]))
       
   561             else:
       
   562                 newer.add(tuple(current))
       
   563         else:
       
   564             newer.add(())
       
   565     return sorted(newer)
       
   566 
       
   567 cmdtable = {}
       
   568 command = cmdutil.command(cmdtable)
       
   569 @command('debugsuccessors', [], '')
       
   570 def cmddebugsuccessors(ui, repo):
       
   571     """dump obsolete changesets and their successors
       
   572 
       
   573     Each line matches an existing marker, the first identifier is the
       
   574     obsolete changeset identifier, followed by it successors.
       
   575     """
       
   576     lock = repo.lock()
       
   577     try:
       
   578         allsuccessors = repo.obsstore.precursors
       
   579         for old in sorted(allsuccessors):
       
   580             successors = [sorted(m[1]) for m in allsuccessors[old]]
       
   581             for i, group in enumerate(sorted(successors)):
       
   582                 ui.write('%s' % short(old))
       
   583                 for new in group:
       
   584                     ui.write(' %s' % short(new))
       
   585                 ui.write('\n')
       
   586     finally:
       
   587         lock.release()
       
   588 
       
   589 
       
   590 @eh.reposetup
       
   591 def _repoobsutilsetup(ui, repo):
       
   592     if not repo.local():
       
   593         return
       
   594 
       
   595     class obsoletingrepo(repo.__class__):
       
   596 
       
   597         # XXX kill me
       
   598         def addobsolete(self, sub, obj):
       
   599             """Add a relation marking that node <sub> is a new version of <obj>"""
       
   600             assert sub != obj
       
   601             if not repo[obj].phase():
       
   602                 if sub is None:
       
   603                     self.ui.warn(
       
   604                         _("trying to kill immutable changeset %(obj)s\n")
       
   605                         % {'obj': short(obj)})
       
   606                 if sub is not None:
       
   607                     self.ui.warn(
       
   608                         _("%(sub)s try to obsolete immutable changeset %(obj)s\n")
       
   609                         % {'sub': short(sub), 'obj': short(obj)})
       
   610             lock = self.lock()
       
   611             try:
       
   612                 tr = self.transaction('add-obsolete')
       
   613                 try:
       
   614                     meta = {
       
   615                         'date':  '%i %i' % util.makedate(),
       
   616                         'user': ui.username(),
       
   617                         }
       
   618                     subs = (sub == nullid) and [] or [sub]
       
   619                     mid = self.obsstore.create(tr, obj, subs, 0, meta)
       
   620                     tr.close()
       
   621                     self._clearobsoletecache()
       
   622                     return mid
       
   623                 finally:
       
   624                     tr.release()
       
   625             finally:
       
   626                 lock.release()
       
   627 
       
   628         # XXX kill me
       
   629         def addcollapsedobsolete(self, oldnodes, newnode):
       
   630             """Mark oldnodes as collapsed into newnode."""
       
   631             # Assume oldnodes are all descendants of a single rev
       
   632             rootrevs = self.revs('roots(%ln)', oldnodes)
       
   633             assert len(rootrevs) == 1, rootrevs
       
   634             #rootnode = self[rootrevs[0]].node()
       
   635             for n in oldnodes:
       
   636                 self.addobsolete(newnode, n)
       
   637     repo.__class__ = obsoletingrepo
       
   638 
       
   639 #####################################################################
       
   640 ### Extending revset and template                                 ###
       
   641 #####################################################################
       
   642 
       
   643 @eh.revset('hidden')
       
   644 def revsethidden(repo, subset, x):
       
   645     """``hidden()``
       
   646     Changeset is hidden.
       
   647     """
       
   648     args = revset.getargs(x, 0, 0, 'hidden takes no argument')
       
   649     return [r for r in subset if r in repo.hiddenrevs]
       
   650 
       
   651 ## troubles
       
   652 
       
   653 @eh.revset('suspended')
       
   654 def revsetsuspended(repo, subset, x):
       
   655     """``suspended()``
       
   656     Obsolete changesets with non-obsolete descendants.
       
   657     """
       
   658     args = revset.getargs(x, 0, 0, 'suspended takes no arguments')
       
   659     return [r for r in subset if r in repo._suspendedset]
       
   660 
       
   661 @eh.revset('latecomer')
       
   662 def revsetlatecomer(repo, subset, x):
       
   663     """``latecomer()``
       
   664     Changesets marked as successors of public changesets.
       
   665     """
       
   666     args = revset.getargs(x, 0, 0, 'latecomer takes no arguments')
       
   667     return [r for r in subset if r in repo._latecomerset]
       
   668 
       
   669 @eh.revset('conflicting')
       
   670 def revsetconflicting(repo, subset, x):
       
   671     """``conflicting()``
       
   672     Changesets marked as successors of a same changeset.
       
   673     """
       
   674     args = revset.getargs(x, 0, 0, 'conflicting takes no arguments')
       
   675     return [r for r in subset if r in repo._conflictingset]
       
   676 
       
   677 
       
   678 @eh.revset('obsparents')
       
   679 @eh.revset('precursors')
       
   680 def revsetprecursors(repo, subset, x):
       
   681     """``precursors(set)``
       
   682     Immediate precursors of changesets in set.
       
   683     """
       
   684     s = revset.getset(repo, range(len(repo)), x)
       
   685     cs = _precursors(repo, s)
       
   686     return [r for r in subset if r in cs]
       
   687 
       
   688 
       
   689 @eh.revset('obsancestors')
       
   690 @eh.revset('allprecursors')
       
   691 def revsetallprecursors(repo, subset, x):
       
   692     """``allprecursors(set)``
       
   693     Transitive precursors of changesets in set.
       
   694     """
       
   695     s = revset.getset(repo, range(len(repo)), x)
       
   696     cs = _allprecursors(repo, s)
       
   697     return [r for r in subset if r in cs]
       
   698 
       
   699 
       
   700 @eh.revset('obschildrend')
       
   701 @eh.revset('successors')
       
   702 def revsetsuccessors(repo, subset, x):
       
   703     """``successors(set)``
       
   704     Immediate successors of changesets in set.
       
   705     """
       
   706     s = revset.getset(repo, range(len(repo)), x)
       
   707     cs = _successors(repo, s)
       
   708     return [r for r in subset if r in cs]
       
   709 
   498 @eh.revset('obsdescendants')
   710 @eh.revset('obsdescendants')
   499 @eh.revset('allsuccessors')
   711 @eh.revset('allsuccessors')
   500 def revsetallsuccessors(repo, subset, x):
   712 def revsetallsuccessors(repo, subset, x):
   501     """``allsuccessors(set)``
   713     """``allsuccessors(set)``
   502     Transitive successors of changesets in set.
   714     Transitive successors of changesets in set.
   503     """
   715     """
   504     s = revset.getset(repo, range(len(repo)), x)
   716     s = revset.getset(repo, range(len(repo)), x)
   505     cs = _allsuccessors(repo, s)
   717     cs = _allsuccessors(repo, s)
   506     return [r for r in subset if r in cs]
   718     return [r for r in subset if r in cs]
   507 
   719 
   508 
       
   509 ### template keywords
   720 ### template keywords
   510 #####################
       
   511 
   721 
   512 @eh.templatekw('obsolete')
   722 @eh.templatekw('obsolete')
   513 def obsoletekw(repo, ctx, templ, **args):
   723 def obsoletekw(repo, ctx, templ, **args):
   514     """:obsolete: String. The obsolescence level of the node, could be
   724     """:obsolete: String. The obsolescence level of the node, could be
   515     ``stable``, ``unstable``, ``suspended`` or ``extinct``.
   725     ``stable``, ``unstable``, ``suspended`` or ``extinct``.
   521         return 'suspended'
   731         return 'suspended'
   522     if rev in repo._unstableset:
   732     if rev in repo._unstableset:
   523         return 'unstable'
   733         return 'unstable'
   524     return 'stable'
   734     return 'stable'
   525 
   735 
   526 ### Other Extension compat
   736 #####################################################################
   527 ############################
   737 ### Various trouble warning                                       ###
   528 
   738 #####################################################################
       
   739 
       
   740 
       
   741 ### Discovery wrapping
       
   742 
       
   743 @eh.wrapfunction(discovery, 'checkheads')
       
   744 def wrapcheckheads(orig, repo, remote, outgoing, *args, **kwargs):
       
   745     """wrap mercurial.discovery.checkheads
       
   746 
       
   747     * prevent unstability to be pushed
       
   748     * patch remote to ignore obsolete heads on remote
       
   749     """
       
   750     # do not push instability
       
   751     for h in outgoing.missingheads:
       
   752         # Checking heads is enough, obsolete descendants are either
       
   753         # obsolete or unstable.
       
   754         ctx = repo[h]
       
   755         if ctx.latecomer():
       
   756             raise util.Abort(_("push includes a latecomer changeset: %s!")
       
   757                              % ctx)
       
   758         if ctx.conflicting():
       
   759             raise util.Abort(_("push includes a conflicting changeset: %s!")
       
   760                              % ctx)
       
   761     return orig(repo, remote, outgoing, *args, **kwargs)
       
   762 
       
   763 @eh.wrapcommand("update")
       
   764 @eh.wrapcommand("pull")
       
   765 def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
       
   766     res = origfn(ui, repo, *args, **opts)
       
   767     if repo['.'].obsolete():
       
   768         ui.warn(_('Working directory parent is obsolete\n'))
       
   769     return res
       
   770 
       
   771 @eh.wrapcommand("commit")
       
   772 @eh.wrapcommand("push")
       
   773 @eh.wrapcommand("pull")
       
   774 @eh.wrapcommand("graft")
       
   775 @eh.wrapcommand("phase")
       
   776 @eh.wrapcommand("unbundle")
       
   777 def warnobserrors(orig, ui, repo, *args, **kwargs):
       
   778     """display warning is the command resulted in more instable changeset"""
       
   779     priorunstables = len(repo.revs('unstable()'))
       
   780     priorlatecomers = len(repo.revs('latecomer()'))
       
   781     priorconflictings = len(repo.revs('conflicting()'))
       
   782     #print orig, priorunstables
       
   783     #print len(repo.revs('secret() - obsolete()'))
       
   784     try:
       
   785         return orig(ui, repo, *args, **kwargs)
       
   786     finally:
       
   787         newunstables = len(repo.revs('unstable()')) - priorunstables
       
   788         newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers
       
   789         newconflictings = len(repo.revs('conflicting()')) - priorconflictings
       
   790         #print orig, newunstables
       
   791         #print len(repo.revs('secret() - obsolete()'))
       
   792         if newunstables > 0:
       
   793             ui.warn(_('%i new unstables changesets\n') % newunstables)
       
   794         if newlatecomers > 0:
       
   795             ui.warn(_('%i new latecomers changesets\n') % newlatecomers)
       
   796         if newconflictings > 0:
       
   797             ui.warn(_('%i new conflictings changesets\n') % newconflictings)
       
   798 
       
   799 @eh.reposetup
       
   800 def _repostabilizesetup(ui, repo):
       
   801     if not repo.local():
       
   802         return
       
   803 
       
   804     opush = repo.push
       
   805 
       
   806     class stabilizerrepo(repo.__class__):
       
   807         def push(self, remote, *args, **opts):
       
   808             """wrapper around pull that pull obsolete relation"""
       
   809             try:
       
   810                 result = opush(remote, *args, **opts)
       
   811             except util.Abort, ex:
       
   812                 hint = _("use 'hg stabilize' to get a stable history "
       
   813                          "or --force to ignore warnings")
       
   814                 if (len(ex.args) >= 1
       
   815                     and ex.args[0].startswith('push includes ')
       
   816                     and ex.hint is None):
       
   817                     ex.hint = hint
       
   818                 raise
       
   819             return result
       
   820     repo.__class__ = stabilizerrepo
       
   821 
       
   822 #####################################################################
       
   823 ### Other extension compat                                        ###
       
   824 #####################################################################
       
   825 
       
   826 ### commit --amend
       
   827 
       
   828 @eh.wrapfunction(cmdutil, 'amend')
       
   829 def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs):
       
   830     oldnode = old.node()
       
   831     new = orig(ui, repo, commitfunc, old, *args, **kwargs)
       
   832     if new != oldnode:
       
   833         lock = repo.lock()
       
   834         try:
       
   835             tr = repo.transaction('post-amend-obst')
       
   836             try:
       
   837                 meta = {
       
   838                     'date':  '%i %i' % util.makedate(),
       
   839                     'user': ui.username(),
       
   840                     }
       
   841                 repo.obsstore.create(tr, oldnode, [new], 0, meta)
       
   842                 tr.close()
       
   843                 repo._clearobsoletecache()
       
   844             finally:
       
   845                 tr.release()
       
   846         finally:
       
   847             lock.release()
       
   848     return new
       
   849 
       
   850 ### rebase
   529 
   851 
   530 def buildstate(orig, repo, dest, rebaseset, *ags, **kws):
   852 def buildstate(orig, repo, dest, rebaseset, *ags, **kws):
   531     """wrapper for rebase 's buildstate that exclude obsolete changeset"""
   853     """wrapper for rebase 's buildstate that exclude obsolete changeset"""
   532     rebaseset = repo.revs('%ld - extinct()', rebaseset)
   854     rebaseset = repo.revs('%ld - extinct()', rebaseset)
   533     return orig(repo, dest, rebaseset, *ags, **kws)
   855     return orig(repo, dest, rebaseset, *ags, **kws)
   617             extensions.wrapfunction(rebase, 'defineparents', defineparents)
   939             extensions.wrapfunction(rebase, 'defineparents', defineparents)
   618             extensions.wrapfunction(rebase, 'concludenode', concludenode)
   940             extensions.wrapfunction(rebase, 'concludenode', concludenode)
   619             extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase)
   941             extensions.wrapcommand(rebase.cmdtable, "rebase", cmdrebase)
   620     except KeyError:
   942     except KeyError:
   621         pass  # rebase not found
   943         pass  # rebase not found
   622 
       
   623 ### Discovery wrapping
       
   624 #############################
       
   625 
       
   626 @eh.wrapfunction(discovery, 'checkheads')
       
   627 def wrapcheckheads(orig, repo, remote, outgoing, *args, **kwargs):
       
   628     """wrap mercurial.discovery.checkheads
       
   629 
       
   630     * prevent unstability to be pushed
       
   631     * patch remote to ignore obsolete heads on remote
       
   632     """
       
   633     # do not push instability
       
   634     for h in outgoing.missingheads:
       
   635         # Checking heads is enough, obsolete descendants are either
       
   636         # obsolete or unstable.
       
   637         ctx = repo[h]
       
   638         if ctx.latecomer():
       
   639             raise util.Abort(_("push includes a latecomer changeset: %s!")
       
   640                              % ctx)
       
   641         if ctx.conflicting():
       
   642             raise util.Abort(_("push includes a conflicting changeset: %s!")
       
   643                              % ctx)
       
   644     return orig(repo, remote, outgoing, *args, **kwargs)
       
   645 
       
   646 @eh.wrapfunction(phases, 'advanceboundary')
       
   647 def wrapclearcache(orig, repo, *args, **kwargs):
       
   648     try:
       
   649         return orig(repo, *args, **kwargs)
       
   650     finally:
       
   651         repo._clearobsoletecache()
       
   652 
       
   653 
       
   654 ### New commands
       
   655 #############################
       
   656 
       
   657 cmdtable = {}
       
   658 command = cmdutil.command(cmdtable)
       
   659 
       
   660 
       
   661 
       
   662 @command('debugsuccessors', [], '')
       
   663 def cmddebugsuccessors(ui, repo):
       
   664     """dump obsolete changesets and their successors
       
   665 
       
   666     Each line matches an existing marker, the first identifier is the
       
   667     obsolete changeset identifier, followed by it successors.
       
   668     """
       
   669     lock = repo.lock()
       
   670     try:
       
   671         allsuccessors = repo.obsstore.precursors
       
   672         for old in sorted(allsuccessors):
       
   673             successors = [sorted(m[1]) for m in allsuccessors[old]]
       
   674             for i, group in enumerate(sorted(successors)):
       
   675                 ui.write('%s' % short(old))
       
   676                 for new in group:
       
   677                     ui.write(' %s' % short(new))
       
   678                 ui.write('\n')
       
   679     finally:
       
   680         lock.release()
       
   681 
       
   682 ### Altering existing command
       
   683 #############################
       
   684 
       
   685 @eh.wrapcommand("update")
       
   686 @eh.wrapcommand("pull")
       
   687 def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
       
   688     res = origfn(ui, repo, *args, **opts)
       
   689     if repo['.'].obsolete():
       
   690         ui.warn(_('Working directory parent is obsolete\n'))
       
   691     return res
       
   692 
       
   693 def warnobserrors(orig, ui, repo, *args, **kwargs):
       
   694     """display warning is the command resulted in more instable changeset"""
       
   695     priorunstables = len(repo.revs('unstable()'))
       
   696     priorlatecomers = len(repo.revs('latecomer()'))
       
   697     priorconflictings = len(repo.revs('conflicting()'))
       
   698     #print orig, priorunstables
       
   699     #print len(repo.revs('secret() - obsolete()'))
       
   700     try:
       
   701         return orig(ui, repo, *args, **kwargs)
       
   702     finally:
       
   703         newunstables = len(repo.revs('unstable()')) - priorunstables
       
   704         newlatecomers = len(repo.revs('latecomer()')) - priorlatecomers
       
   705         newconflictings = len(repo.revs('conflicting()')) - priorconflictings
       
   706         #print orig, newunstables
       
   707         #print len(repo.revs('secret() - obsolete()'))
       
   708         if newunstables > 0:
       
   709             ui.warn(_('%i new unstables changesets\n') % newunstables)
       
   710         if newlatecomers > 0:
       
   711             ui.warn(_('%i new latecomers changesets\n') % newlatecomers)
       
   712         if newconflictings > 0:
       
   713             ui.warn(_('%i new conflictings changesets\n') % newconflictings)
       
   714 
       
   715 @eh.extsetup
       
   716 def _coreobserrorwrapping(ui):
       
   717     # warning about more obsolete
       
   718     for cmd in ['commit', 'push', 'pull', 'graft', 'phase', 'unbundle']:
       
   719         entry = extensions.wrapcommand(commands.table, cmd, warnobserrors)
       
   720 
       
   721 @eh.wrapfunction(cmdutil, 'amend')
       
   722 def wrapcmdutilamend(orig, ui, repo, commitfunc, old, *args, **kwargs):
       
   723     oldnode = old.node()
       
   724     new = orig(ui, repo, commitfunc, old, *args, **kwargs)
       
   725     if new != oldnode:
       
   726         lock = repo.lock()
       
   727         try:
       
   728             tr = repo.transaction('post-amend-obst')
       
   729             try:
       
   730                 meta = {
       
   731                     'date':  '%i %i' % util.makedate(),
       
   732                     'user': ui.username(),
       
   733                     }
       
   734                 repo.obsstore.create(tr, oldnode, [new], 0, meta)
       
   735                 tr.close()
       
   736                 repo._clearobsoletecache()
       
   737             finally:
       
   738                 tr.release()
       
   739         finally:
       
   740             lock.release()
       
   741     return new
       
   742 
       
   743 
       
   744 ### diagnostique tools
       
   745 #############################
       
   746 
       
   747 def unstables(repo):
       
   748     """Return all unstable changeset"""
       
   749     return scmutil.revrange(repo, ['obsolete():: and (not obsolete())'])
       
   750 
       
   751 def newerversion(repo, obs):
       
   752     """Return the newer version of an obsolete changeset"""
       
   753     toproceed = set([(obs,)])
       
   754     # XXX known optimization available
       
   755     newer = set()
       
   756     objectrels = repo.obsstore.precursors
       
   757     while toproceed:
       
   758         current = toproceed.pop()
       
   759         assert len(current) <= 1, 'splitting not handled yet. %r' % current
       
   760         current = [n for n in current if n != nullid]
       
   761         if current:
       
   762             n, = current
       
   763             if n in objectrels:
       
   764                 markers = objectrels[n]
       
   765                 for mark in markers:
       
   766                     toproceed.add(tuple(mark[1]))
       
   767             else:
       
   768                 newer.add(tuple(current))
       
   769         else:
       
   770             newer.add(())
       
   771     return sorted(newer)
       
   772 
       
   773 ### repo subclassing
       
   774 #############################
       
   775 
       
   776 @eh.reposetup
       
   777 def _reposetup(ui, repo):
       
   778     if not repo.local():
       
   779         return
       
   780 
       
   781     opush = repo.push
       
   782     o_updatebranchcache = repo.updatebranchcache
       
   783 
       
   784     o_hook = repo.hook
       
   785 
       
   786 
       
   787     class obsoletingrepo(repo.__class__):
       
   788 
       
   789         # workaround
       
   790         def hook(self, name, throw=False, **args):
       
   791             if 'pushkey' in name:
       
   792                 args.pop('new')
       
   793                 args.pop('old')
       
   794             return o_hook(name, throw=False, **args)
       
   795 
       
   796         # XXX move me on obssotre
       
   797         @util.propertycache
       
   798         def _obsoleteset(self):
       
   799             """the set of obsolete revision"""
       
   800             obs = set()
       
   801             nm = self.changelog.nodemap
       
   802             for prec in self.obsstore.precursors:
       
   803                 rev = nm.get(prec)
       
   804                 if rev is not None:
       
   805                     obs.add(rev)
       
   806             return obs
       
   807 
       
   808         # XXX move me on obssotre
       
   809         @util.propertycache
       
   810         def _unstableset(self):
       
   811             """the set of non obsolete revision with obsolete parent"""
       
   812             return set(self.revs('(obsolete()::) - obsolete()'))
       
   813 
       
   814         # XXX move me on obssotre
       
   815         @util.propertycache
       
   816         def _suspendedset(self):
       
   817             """the set of obsolete parent with non obsolete descendant"""
       
   818             return set(self.revs('obsolete() and obsolete()::unstable()'))
       
   819 
       
   820         # XXX move me on obssotre
       
   821         @util.propertycache
       
   822         def _extinctset(self):
       
   823             """the set of obsolete parent without non obsolete descendant"""
       
   824             return set(self.revs('obsolete() - obsolete()::unstable()'))
       
   825 
       
   826         # XXX move me on obssotre
       
   827         @util.propertycache
       
   828         def _latecomerset(self):
       
   829             """the set of rev trying to obsolete public revision"""
       
   830             query = 'allsuccessors(public()) - obsolete() - public()'
       
   831             return set(self.revs(query))
       
   832 
       
   833         # XXX move me on obssotre
       
   834         @util.propertycache
       
   835         def _conflictingset(self):
       
   836             """the set of rev trying to obsolete public revision"""
       
   837             conflicting = set()
       
   838             obsstore = self.obsstore
       
   839             newermap = {}
       
   840             for ctx in self.set('(not public()) - obsolete()'):
       
   841                 prec = obsstore.successors.get(ctx.node(), ())
       
   842                 toprocess = set(prec)
       
   843                 while toprocess:
       
   844                     prec = toprocess.pop()[0]
       
   845                     if prec not in newermap:
       
   846                         newermap[prec] = newerversion(self, prec)
       
   847                     newer = [n for n in newermap[prec] if n] # filter kill
       
   848                     if len(newer) > 1:
       
   849                         conflicting.add(ctx.rev())
       
   850                         break
       
   851                 toprocess.update(obsstore.successors.get(prec, ()))
       
   852             return conflicting
       
   853 
       
   854         def _clearobsoletecache(self):
       
   855             if '_obsoleteset' in vars(self):
       
   856                 del self._obsoleteset
       
   857             self._clearunstablecache()
       
   858 
       
   859         def updatebranchcache(self):
       
   860             o_updatebranchcache()
       
   861             self._clearunstablecache()
       
   862 
       
   863         def _clearunstablecache(self):
       
   864             if '_unstableset' in vars(self):
       
   865                 del self._unstableset
       
   866             if '_suspendedset' in vars(self):
       
   867                 del self._suspendedset
       
   868             if '_extinctset' in vars(self):
       
   869                 del self._extinctset
       
   870             if '_latecomerset' in vars(self):
       
   871                 del self._latecomerset
       
   872             if '_conflictingset' in vars(self):
       
   873                 del self._conflictingset
       
   874 
       
   875         # XXX kill me
       
   876         def addobsolete(self, sub, obj):
       
   877             """Add a relation marking that node <sub> is a new version of <obj>"""
       
   878             assert sub != obj
       
   879             if not repo[obj].phase():
       
   880                 if sub is None:
       
   881                     self.ui.warn(
       
   882                         _("trying to kill immutable changeset %(obj)s\n")
       
   883                         % {'obj': short(obj)})
       
   884                 if sub is not None:
       
   885                     self.ui.warn(
       
   886                         _("%(sub)s try to obsolete immutable changeset %(obj)s\n")
       
   887                         % {'sub': short(sub), 'obj': short(obj)})
       
   888             lock = self.lock()
       
   889             try:
       
   890                 tr = self.transaction('add-obsolete')
       
   891                 try:
       
   892                     meta = {
       
   893                         'date':  '%i %i' % util.makedate(),
       
   894                         'user': ui.username(),
       
   895                         }
       
   896                     subs = (sub == nullid) and [] or [sub]
       
   897                     mid = self.obsstore.create(tr, obj, subs, 0, meta)
       
   898                     tr.close()
       
   899                     self._clearobsoletecache()
       
   900                     return mid
       
   901                 finally:
       
   902                     tr.release()
       
   903             finally:
       
   904                 lock.release()
       
   905 
       
   906         # XXX kill me
       
   907         def addcollapsedobsolete(self, oldnodes, newnode):
       
   908             """Mark oldnodes as collapsed into newnode."""
       
   909             # Assume oldnodes are all descendants of a single rev
       
   910             rootrevs = self.revs('roots(%ln)', oldnodes)
       
   911             assert len(rootrevs) == 1, rootrevs
       
   912             #rootnode = self[rootrevs[0]].node()
       
   913             for n in oldnodes:
       
   914                 self.addobsolete(newnode, n)
       
   915 
       
   916         ### pull // push support
       
   917 
       
   918         def push(self, remote, *args, **opts):
       
   919             """wrapper around pull that pull obsolete relation"""
       
   920             try:
       
   921                 result = opush(remote, *args, **opts)
       
   922             except util.Abort, ex:
       
   923                 hint = _("use 'hg stabilize' to get a stable history "
       
   924                          "or --force to ignore warnings")
       
   925                 if (len(ex.args) >= 1
       
   926                     and ex.args[0].startswith('push includes ')
       
   927                     and ex.hint is None):
       
   928                     ex.hint = hint
       
   929                 raise
       
   930             return result
       
   931     repo.__class__ = obsoletingrepo
       
   932 
   944 
   933 
   945 
   934 #####################################################################
   946 #####################################################################
   935 ### Older format management                                       ###
   947 ### Older format management                                       ###
   936 #####################################################################
   948 #####################################################################