obshashrange: warm the cache at the end of each transaction
This will help having warmed cache for read only client.
The warming is still imperfect in case of markers that trigger a reset, but we
are in a better place than what we used to be.
# __init__.py - topic extension## This software may be used and distributed according to the terms of the# GNU General Public License version 2 or any later version."""support for topic branchesTopic branches are lightweight branches which disappear when changes arefinalized (move to the public phase).Compared to bookmark, topic is reference carried by each changesets of theseries instead of just the single head revision. Topic are quite similar tothe way named branch work, except they eventualy fade away when the changesetbecomes part of the immutable history. Changeset can below to both a topic anda named branch, but as long as it is mutable, its topic identity will prevail.As a result, default destination for 'update', 'merge', etc... will take topicinto account. When a topic is active these operations will only consider otherchangesets on that topic (and, in some occurence, bare changeset on samebranch). When no topic is active, changeset with topic will be ignored andonly bare one on the same branch will be taken in account.There is currently two commands to be used with that extension: 'topics' and'stack'.The 'hg topics' command is used to set the current topic and list existing one.'hg topics --verbose' will list various information related to each topic.The 'stack' will show you in formation about the stack of commit belonging toyour current topic.Topic is offering you aliases reference to changeset in your current topicstack as 't#'. For example, 't1' refers to the root of your stack, 't2' to thesecond commits, etc. The 'hg stack' command show these number.Push behavior will change a bit with topic. When pushing to a publishingrepository the changesets will turn public and the topic data on them will fadeaway. The logic regarding pushing new heads will behave has before, ignore anytopic related data. When pushing to a non-publishing repository (supportingtopic), the head checking will be done taking topic data into account.Push will complain about multiple heads on a branch if you push multiple headswith no topic information on them (or multiple public heads). But pushing a newtopic will not requires any specific flag. However, pushing multiple heads on atopic will be met with the usual warning.The 'evolve' extension takes 'topic' into account. 'hg evolve --all'will evolve all changesets in the active topic. In addition, by default. 'hgnext' and 'hg prev' will stick to the current topic.Be aware that this extension is still an experiment, commands and other featuresare likely to be change/adjusted/dropped over time as we refine the concept."""from__future__importabsolute_importimportrefrommercurial.i18nimport_frommercurialimport(branchmap,cmdutil,commands,context,error,extensions,localrepo,lock,merge,namespaces,node,obsolete,patch,phases,util,)from.import(constants,revsetastopicrevset,destination,stack,topicmap,discovery,)cmdtable={}command=cmdutil.command(cmdtable)colortable={'topic.active':'green','topic.list.troubledcount':'red','topic.list.headcount.multiple':'yellow','topic.list.behindcount':'cyan','topic.list.behinderror':'red','topic.stack.index':'yellow','topic.stack.index.base':'none dim','topic.stack.desc.base':'none dim','topic.stack.state.base':'dim','topic.stack.state.clean':'green','topic.stack.index.current':'cyan',# random pick'topic.stack.state.current':'cyan bold',# random pick'topic.stack.desc.current':'cyan',# random pick'topic.stack.state.unstable':'red','topic.stack.summary.behindcount':'cyan','topic.stack.summary.behinderror':'red','topic.stack.summary.headcount.multiple':'yellow',# default color to help log output and thg# (first pick I could think off, update as needed'log.topic':'green_background','topic.active':'green',}testedwith='3.9'def_contexttopic(self):returnself.extra().get(constants.extrakey,'')context.basectx.topic=_contexttopictopicrev=re.compile(r'^t\d+$')def_namemap(repo,name):iftopicrev.match(name):idx=int(name[1:])topic=repo.currenttopicifnottopic:raiseerror.Abort(_('cannot resolve "%s": no active topic')%name)revs=list(stack.getstack(repo,topic))try:r=revs[idx-1]exceptIndexError:msg=_('cannot resolve "%s": topic "%s" has only %d changesets')raiseerror.Abort(msg%(name,topic,len(revs)))return[repo[r].node()]ifnamenotinrepo.topics:return[]return[ctx.node()forctxinrepo.set('not public() and extra(topic, %s)',name)]def_nodemap(repo,node):ctx=repo[node]t=ctx.topic()iftandctx.phase()>phases.public:return[t]return[]defuisetup(ui):destination.modsetup(ui)topicrevset.modsetup(ui)discovery.modsetup(ui)topicmap.modsetup(ui)setupimportexport(ui)extensions.afterloaded('rebase',_fixrebase)entry=extensions.wrapcommand(commands.table,'commit',commitwrap)entry[1].append(('t','topic','',_("use specified topic"),_('TOPIC')))extensions.wrapfunction(cmdutil,'buildcommittext',committextwrap)extensions.wrapfunction(merge,'update',mergeupdatewrap)cmdutil.summaryhooks.add('topic',summaryhook)defreposetup(ui,repo):ifnotisinstance(repo,localrepo.localrepository):return# this can be a peer in the ssh case (puzzling)ifrepo.ui.config('experimental','thg.displaynames',None)isNone:repo.ui.setconfig('experimental','thg.displaynames','topics',source='topic-extension')classtopicrepo(repo.__class__):def_restrictcapabilities(self,caps):caps=super(topicrepo,self)._restrictcapabilities(caps)caps.add('topics')returncapsdefcommit(self,*args,**kwargs):backup=self.ui.backupconfig('ui','allowemptycommit')try:ifrepo.currenttopic!=repo['.'].topic():# bypass the core "nothing changed" logicself.ui.setconfig('ui','allowemptycommit',True)returnsuper(topicrepo,self).commit(*args,**kwargs)finally:self.ui.restoreconfig(backup)defcommitctx(self,ctx,error=None):ifisinstance(ctx,context.workingcommitctx):current=self.currenttopicifcurrent:ctx.extra()[constants.extrakey]=currentif(isinstance(ctx,context.memctx)andctx.extra().get('amend_source')andctx.topic()andnotself.currenttopic):# we are amending and need to remove a topicdelctx.extra()[constants.extrakey]withtopicmap.usetopicmap(self):returnsuper(topicrepo,self).commitctx(ctx,error=error)@propertydeftopics(self):ifself._topicsisnotNone:returnself._topicstopics=set(['',self.currenttopic])forcinself.set('not public()'):topics.add(c.topic())topics.remove('')self._topics=topicsreturntopics@propertydefcurrenttopic(self):returnself.vfs.tryread('topic')defbranchmap(self,topic=True):ifnottopic:super(topicrepo,self).branchmap()withtopicmap.usetopicmap(self):branchmap.updatecache(self)returnself._topiccaches[self.filtername]defdestroyed(self,*args,**kwargs):withtopicmap.usetopicmap(self):returnsuper(topicrepo,self).destroyed(*args,**kwargs)definvalidatevolatilesets(self):# XXX we might be able to move this to something invalidated less oftensuper(topicrepo,self).invalidatevolatilesets()self._topics=Noneif'_topiccaches'invars(self.unfiltered()):self.unfiltered()._topiccaches.clear()defpeer(self):peer=super(topicrepo,self).peer()ifgetattr(peer,'_repo',None)isnotNone:# localpeerclasstopicpeer(peer.__class__):defbranchmap(self):usetopic=notself._repo.publishing()returnself._repo.branchmap(topic=usetopic)peer.__class__=topicpeerreturnpeerrepo.__class__=topicreporepo._topics=Noneifutil.safehasattr(repo,'names'):repo.names.addnamespace(namespaces.namespace('topics','topic',namemap=_namemap,nodemap=_nodemap,listnames=lambdarepo:repo.topics))@command('topics [TOPIC]',[('','clear',False,'clear active topic if any'),('','change','','revset of existing revisions to change topic'),('l','list',False,'show the stack of changeset in the topic'),]+commands.formatteropts)deftopics(ui,repo,topic='',clear=False,change=None,list=False,**opts):"""View current topic, set current topic, or see all topics. The --verbose version of this command display various information on the state of each topic."""iflist:ifclearorchange:raiseerror.Abort(_("cannot use --clear or --change with --list"))ifnottopic:topic=repo.currenttopicifnottopic:raiseerror.Abort(_('no active topic to list'))returnstack.showstack(ui,repo,topic,opts)ifchange:ifnotobsolete.isenabled(repo,obsolete.createmarkersopt):raiseerror.Abort(_('must have obsolete enabled to use --change'))ifnottopicandnotclear:raiseerror.Abort('changing topic requires a topic name or --clear')ifany(notc.mutable()forcinrepo.set('%r and public()',change)):raiseerror.Abort("can't change topic of a public change")rewrote=0needevolve=Falsel=repo.lock()txn=repo.transaction('rewrite-topics')try:forcinrepo.set('%r',change):deffilectxfn(repo,ctx,path):try:returnc[path]excepterror.ManifestLookupError:returnNonefixedextra=dict(c.extra())ui.debug('old node id is %s\n'%node.hex(c.node()))ui.debug('origextra: %r\n'%fixedextra)newtopic=Noneifclearelsetopicoldtopic=fixedextra.get(constants.extrakey,None)ifoldtopic==newtopic:continueifclear:delfixedextra[constants.extrakey]else:fixedextra[constants.extrakey]=topicif'amend_source'infixedextra:# TODO: right now the commitctx wrapper in# topicrepo overwrites the topic in extra if# amend_source is set to support 'hg commit# --amend'. Support for amend should be adjusted# to not be so invasive.delfixedextra['amend_source']ui.debug('changing topic of %s from %s to %s\n'%(c,oldtopic,newtopic))ui.debug('fixedextra: %r\n'%fixedextra)mc=context.memctx(repo,(c.p1().node(),c.p2().node()),c.description(),c.files(),filectxfn,user=c.user(),date=c.date(),extra=fixedextra)newnode=repo.commitctx(mc)ui.debug('new node id is %s\n'%node.hex(newnode))needevolve=needevolveor(len(c.children())>0)obsolete.createmarkers(repo,[(c,(repo[newnode],))])rewrote+=1txn.close()except:try:txn.abort()finally:repo.invalidate()raisefinally:lock.release(txn,l)ui.status('changed topic on %d changes\n'%rewrote)ifneedevolve:evolvetarget='topic(%s)'%topiciftopicelse'not topic()'ui.status('please run hg evolve --rev "%s" now\n'%evolvetarget)ifclear:ifrepo.vfs.exists('topic'):repo.vfs.unlink('topic')returniftopic:withrepo.wlock():withrepo.vfs.open('topic','w')asf:f.write(topic)return_listtopics(ui,repo,opts)@command('stack [TOPIC]',[]+commands.formatteropts)defcmdstack(ui,repo,topic='',**opts):"""list all changesets in a topic and other information List the current topic by default."""ifnottopic:topic=repo.currenttopicifnottopic:raiseerror.Abort(_('no active topic to list'))returnstack.showstack(ui,repo,topic,opts)def_listtopics(ui,repo,opts):fm=ui.formatter('bookmarks',opts)activetopic=repo.currenttopicnamemask='%s'ifrepo.topicsandui.verbose:maxwidth=max(len(t)fortinrepo.topics)namemask='%%-%is'%maxwidthfortopicinsorted(repo.topics):fm.startitem()marker=' 'label='topic'active=(topic==activetopic)ifactive:marker='*'label='topic.active'ifnotui.quiet:# registering the active data is made explicitly laterfm.plain(' %s '%marker,label=label)fm.write('topic',namemask,topic,label=label)fm.data(active=active)ifui.verbose:# XXX we should include the data even when not verbosedata=stack.stackdata(repo,topic)fm.plain(' (')fm.write('branches+','on branch: %s','+'.join(data['branches']),# XXX use list directly after 4.0 is releasedlabel='topic.list.branches')fm.plain(', ')fm.write('changesetcount','%d changesets',data['changesetcount'],label='topic.list.changesetcount')ifdata['troubledcount']:fm.plain(', ')fm.write('troubledcount','%d troubled',data['troubledcount'],label='topic.list.troubledcount')if1<data['headcount']:fm.plain(', ')fm.write('headcount','%d heads',data['headcount'],label='topic.list.headcount.multiple')if0<data['behindcount']:fm.plain(', ')fm.write('behindcount','%d behind',data['behindcount'],label='topic.list.behindcount')elif-1==data['behindcount']:fm.plain(', ')fm.write('behinderror','%s',_('ambiguous destination'),label='topic.list.behinderror')fm.plain(')')fm.plain('\n')fm.end()defsummaryhook(ui,repo):t=repo.currenttopicifnott:return# i18n: column positioning for "hg summary"ui.write(_("topic: %s\n")%ui.label(t,'topic.active'))defcommitwrap(orig,ui,repo,*args,**opts):withrepo.wlock():ifopts.get('topic'):t=opts['topic']withrepo.vfs.open('topic','w')asf:f.write(t)returnorig(ui,repo,*args,**opts)defcommittextwrap(orig,repo,ctx,subs,extramsg):ret=orig(repo,ctx,subs,extramsg)t=repo.currenttopicift:ret=ret.replace("\nHG: branch","\nHG: topic '%s'\nHG: branch"%t)returnretdefmergeupdatewrap(orig,repo,node,branchmerge,force,*args,**kwargs):matcher=kwargs.get('matcher')partial=not(matcherisNoneormatcher.always())wlock=repo.wlock()try:ret=orig(repo,node,branchmerge,force,*args,**kwargs)ifnotpartialandnotbranchmerge:ot=repo.currenttopict=''pctx=repo[node]ifpctx.phase()>phases.public:t=pctx.topic()withrepo.vfs.open('topic','w')asf:f.write(t)iftandt!=ot:repo.ui.status(_("switching to topic %s\n")%t)returnretfinally:wlock.release()def_fixrebase(loaded):ifnotloaded:returndefsavetopic(ctx,extra):ifctx.topic():extra[constants.extrakey]=ctx.topic()defnewmakeextrafn(orig,copiers):returnorig(copiers+[savetopic])try:rebase=extensions.find("rebase")extensions.wrapfunction(rebase,'_makeextrafn',newmakeextrafn)exceptKeyError:pass## preserve topic during import/exportdef_exporttopic(seq,ctx):topic=ctx.topic()iftopic:return'EXP-Topic %s'%topicreturnNonedef_importtopic(repo,patchdata,extra,opts):if'topic'inpatchdata:extra['topic']=patchdata['topic']defsetupimportexport(ui):"""run at ui setup time to install import/export logic"""cmdutil.extraexport.append('topic')cmdutil.extraexportmap['topic']=_exporttopiccmdutil.extrapreimport.append('topic')cmdutil.extrapreimportmap['topic']=_importtopicpatch.patchheadermap.append(('EXP-Topic','topic'))