evolve: add ordering of the revisions for evolve --rev
When running evolve --rev we want to process the revisions in an optimal fashion
to solve the maximum amount of trouble in the minimum number of steps.
This patch adds a step to evolve --rev to order the revision before solving
the troubles. A simple test is added to cover a basic case.
"""reduce the changesets evolution feature scope for early and noob friendly uithe full scale changeset evolution have some massive bleeding edge and it isvery easy for people not very intimate with the concept to end up in intricatesituation. in order to get some of the benefit sooner, this extension isdisabling some of the less polished aspect of evolution. it should graduallyget thinner and thinner as changeset evolution will get more polished. thisextension is only recommended for large scale organisations. individual usershould probably stick on using evolution in its current state, understand itsconcept and provide feedbackThis extension provides the ability to "inhibit" obsolescence markers. obsolete revision can be cheaply brought back to life that way. However as the inhibitor are not fitting in an append only model, this is incompatible with sharing mutable history."""frommercurialimportlocalrepofrommercurialimportobsoletefrommercurialimportextensionsfrommercurialimportcmdutilfrommercurialimporterrorfrommercurialimportscmutilfrommercurialimportcommandsfrommercurialimportlockaslockmodfrommercurialimportbookmarksfrommercurial.i18nimport_cmdtable={}command=cmdutil.command(cmdtable)defreposetup(ui,repo):classobsinhibitedrepo(repo.__class__):@localrepo.storecache('obsinhibit')def_obsinhibit(self):# XXX we should make sure it is invalidated by transaction failureobsinhibit=set()raw=self.sopener.tryread('obsinhibit')foriinxrange(0,len(raw),20):obsinhibit.add(raw[i:i+20])returnobsinhibitdefcommit(self,*args,**kwargs):newnode=super(obsinhibitedrepo,self).commit(*args,**kwargs)ifnewnodeisnotNone:_inhibitmarkers(repo,[newnode])returnnewnoderepo.__class__=obsinhibitedrepo# Wrapping this to inhibit obsolete revs resulting from a transactionextensions.wrapfunction(localrepo.localrepository,'transaction',transactioncallback)def_update(orig,ui,repo,*args,**kwargs):""" When moving to a commit we want to inhibit any obsolete commit affecting the changeset we are updating to. In other words we don't want any visible commit to be obsolete. """wlock=Nonetry:# Evolve is running a hook on lock release to display a warning message # if the workind dir's parent is obsolete.# We take the lock here to make sure that we inhibit the parent before# that hook get a chance to run.wlock=repo.wlock()res=orig(ui,repo,*args,**kwargs)newhead=repo['.'].node()_inhibitmarkers(repo,[newhead])returnresfinally:lockmod.release(wlock)def_bookmarkchanged(orig,bkmstoreinst,*args,**kwargs):""" Add inhibition markers to every obsolete bookmarks """repo=bkmstoreinst._repobkmstorenodes=[repo[v].node()forvinbkmstoreinst.values()]_inhibitmarkers(repo,bkmstorenodes)returnorig(bkmstoreinst,*args,**kwargs)def_bookmark(orig,ui,repo,*bookmarks,**opts):""" Add a -D option to the bookmark command, map it to prune -B """haspruneopt=opts.get('prune',False)ifnothaspruneopt:returnorig(ui,repo,*bookmarks,**opts)# Call prune -Bevolve=extensions.find('evolve')optsdict={'new':[],'succ':[],'rev':[],'bookmark':bookmarks[0],'keep':None,'biject':False,}evolve.cmdprune(ui,repo,**optsdict)# obsolescence inhibitor########################def_schedulewrite(tr,obsinhibit):"""Make sure on disk content will be updated on transaction commit"""defwriter(fp):"""Serialize the inhibited list to disk. """raw=''.join(obsinhibit)fp.write(raw)tr.addfilegenerator('obsinhibit',('obsinhibit',),writer)tr.hookargs['obs_inbihited']='1'def_filterpublic(repo,nodes):"""filter out inhibitor on public changeset Public changesets are already immune to obsolescence"""getrev=repo.changelog.nodemap.getgetphase=repo._phasecache.phasereturn(nforninrepo._obsinhibitifgetrev(n)isnotNoneandgetphase(repo,getrev(n)))def_inhibitmarkers(repo,nodes):"""add marker inhibitor for all obsolete revision under <nodes> Content of <nodes> and all mutable ancestors are considered. Marker for obsolete revision only are created. """newinhibit=repo.set('::%ln and obsolete()',nodes)ifnewinhibit:lock=tr=Nonetry:lock=repo.lock()tr=repo.transaction('obsinhibit')repo._obsinhibit.update(c.node()forcinnewinhibit)_schedulewrite(tr,_filterpublic(repo,repo._obsinhibit))repo.invalidatevolatilesets()tr.close()finally:lockmod.release(tr,lock)def_deinhibitmarkers(repo,nodes):"""lift obsolescence inhibition on a set of nodes This will be triggered when inhibited nodes received new obsolescence markers. Otherwise the new obsolescence markers would also be inhibited. """deinhibited=repo._obsinhibit&set(nodes)ifdeinhibited:tr=repo.transaction('obsinhibit')try:repo._obsinhibit-=deinhibited_schedulewrite(tr,_filterpublic(repo,repo._obsinhibit))repo.invalidatevolatilesets()tr.close()finally:tr.release()def_createmarkers(orig,repo,relations,flag=0,date=None,metadata=None):"""wrap markers create to make sure we de-inhibit target nodes"""# wrapping transactio to unify the one in each functiontr=repo.transaction('add-obsolescence-marker')try:orig(repo,relations,flag,date,metadata)precs=(r[0].node()forrinrelations)_deinhibitmarkers(repo,precs)tr.close()finally:tr.release()deftransactioncallback(orig,repo,*args,**kwargs):""" Wrap localrepo.transaction to inhibit new obsolete changes """definhibitposttransaction(transaction):# At the end of the transaction we catch all the new visible and# obsolete commit to inhibit themvisibleobsolete=repo.revs('(not hidden()) and obsolete()')ignoreset=set(getattr(repo,'_rebaseset',[]))visibleobsolete=list(rforrinvisibleobsoleteifrnotinignoreset)ifvisibleobsolete:_inhibitmarkers(repo,[repo[r].node()forrinvisibleobsolete])transaction=orig(repo,*args,**kwargs)transaction.addpostclose('inhibitposttransaction',inhibitposttransaction)returntransactiondefextsetup(ui):# lets wrap the computation of the obsolete set# We apply inhibition thereobsfunc=obsolete.cachefuncs['obsolete']def_computeobsoleteset(repo):"""remove any inhibited nodes from the obsolete set This will trickle down to other part of mercurial (hidden, log, etc)"""obs=obsfunc(repo)getrev=repo.changelog.nodemap.getforninrepo._obsinhibit:obs.discard(getrev(n))returnobstry:extensions.find('directaccess')exceptKeyError:errormsg=_('Cannot use inhibit without the direct access extension')raiseerror.Abort(errormsg)obsolete.cachefuncs['obsolete']=_computeobsoleteset# wrap create marker to make it able to lift the inhibitionextensions.wrapfunction(obsolete,'createmarkers',_createmarkers)# drop divergence computation since it is incompatible with "light revive"obsolete.cachefuncs['divergent']=lambdarepo:set()# drop bumped computation since it is incompatible with "light revive"obsolete.cachefuncs['bumped']=lambdarepo:set()# wrap update to make sure that no obsolete commit is visible after an# updateextensions.wrapcommand(commands.table,'update',_update)# There are two ways to save bookmark changes during a transation, we# wrap both to add inhibition markers.extensions.wrapfunction(bookmarks.bmstore,'recordchange',_bookmarkchanged)extensions.wrapfunction(bookmarks.bmstore,'write',_bookmarkchanged)# Add bookmark -D optionentry=extensions.wrapcommand(commands.table,'bookmark',_bookmark)entry[1].append(('D','prune',None,_('delete the bookmark and prune the commits underneath')))@command('debugobsinhibit',[],'')defcmddebugobsinhibit(ui,repo,*revs):"""inhibit obsolescence markers effect on a set of revs"""nodes=(repo[r].node()forrinscmutil.revrange(repo,revs))_inhibitmarkers(repo,nodes)