# stack.py - code related to stack workflow## This software may be used and distributed according to the terms of the# GNU General Public License version 2 or any later version.frommercurial.i18nimport_frommercurialimport(destutil,error,node,phases,pycompat,obsolete,util,)from.evolvebitsimport(_singlesuccessor,MultipleSuccessorsError,builddependencies,)short=node.shortdefparseusername(user):"""parses the ctx user and returns the username without email ID if possible, otherwise returns the mail address from that"""username=Noneifuser:# user is of form "abc <abc@xyz.com>"username=user.split(b'<')[0]ifnotusername:# assuming user is of form "<abc@xyz.com>"iflen(user)>1:username=user[1:-1]else:username=userusername=username.strip()returnusernamedef_stackcandidates(repo):"""build the smaller set of revs that might be part of a stack. The intend is to build something more efficient than what revsets do in this area. """phasesets=repo._phasecache._phasesetsifnotphasesetsorNoneinphasesets[phases.draft:]:returnrepo.revs(b'(not public()) - obsolete()')result=set.union(*phasesets[phases.draft:])result-=obsolete.getrevs(repo,b'obsolete')returnresultclassstack(object):"""object represent a stack and common logic associated to it."""def__init__(self,repo,branch=None,topic=None):self._repo=repoself.branch=branchself.topic=topicself.behinderror=Nonesubset=_stackcandidates(repo)iftopicisnotNoneandbranchisnotNone:raiseerror.ProgrammingError(b'both branch and topic specified (not defined yet)')eliftopicisnotNone:trevs=repo.revs(b"%ld and topic(%s)",subset,topic)elifbranchisnotNone:trevs=repo.revs(b"%ld and branch(%s) - topic()",subset,branch)else:raiseerror.ProgrammingError(b'neither branch and topic specified (not defined yet)')self._revs=trevsdef__iter__(self):returniter(self.revs)def__getitem__(self,index):returnself.revs[index]def__nonzero__(self):returnbool(self._revs)__bool__=__nonzero__defindex(self,item):returnself.revs.index(item)@util.propertycachedef_dependencies(self):deps,rdeps=builddependencies(self._repo,self._revs)repo=self._reposrcpfunc=repo.changelog.parentrevs### post process to skip over possible gaps in the stack## For example in the following situation, we need to detect that "t3"# indirectly depends on t2.## o t3# |# o other# |# o t2# |# o t1pmap={}defpfuncrev(repo,rev):"""a special "parent func" that also consider successors"""parents=pmap.get(rev)ifparentsisNone:parents=[repo[_singlesuccessor(repo,repo[p])].rev()forpinsrcpfunc(rev)if0<=p]pmap[rev]=parentsreturnparentsrevs=self._revsstackrevs=set(self._revs)forrootin[rforrinrevsifnotdeps[r]]:seen=set()stack=[root]whilestack:current=stack.pop()forpinpfuncrev(repo,current):ifpinseen:continueseen.add(p)ifpinstackrevs:rdeps[p].add(root)deps[root].add(p)elifphases.public<repo[p].phase():# traverse only if we did not found a proper candidatestack.append(p)returndeps,rdeps@util.propertycachedefrevs(self):# some duplication/change from _orderrevs because we use a post# processed dependency graph.# Step 1: compute relation of revision with each otherorigdeps,rdependencies=self._dependenciesdependencies={}# Making a deep copy of origdeps because we modify contents of values# later on. Checking for list here only because right now# builddependencies in evolvebits.py can return a list of _succs()# objects. When that will be dealt with, this deep copy code can be# simplified a lot.fork,vinorigdeps.items():ifisinstance(v,list):dependencies[k]=[i.copy()foriinv]else:dependencies[k]=v.copy()# Step 2: Build the ordering# Remove the revisions with no dependency(A) and add them to the ordering.# Removing these revisions leads to new revisions with no dependency (the# one depending on A) that we can remove from the dependency graph and add# to the ordering. We progress in a similar fashion until the ordering is# builtsolvablerevs=[rforrinsorted(dependencies.keys())ifnotdependencies[r]]revs=[]whilesolvablerevs:rev=solvablerevs.pop()fordependentinrdependencies[rev]:dependencies[dependent].remove(rev)ifnotdependencies[dependent]:solvablerevs.append(dependent)deldependencies[rev]revs.append(rev)revs.extend(sorted(dependencies))# step 3: add t0ifrevs:pt1=self._repo[revs[0]].p1()else:pt1=self._repo[b'.']ifpt1.obsolete():pt1=self._repo[_singlesuccessor(self._repo,pt1)]revs.insert(0,pt1.rev())returnrevs@util.propertycachedefchangesetcount(self):returnlen(self._revs)@util.propertycachedefunstablecount(self):returnlen([rforrinself._revsifself._repo[r].isunstable()])@util.propertycachedefheads(self):revs=self.revs[1:]deps,rdeps=self._dependenciesreturn[rforrinrevsifnotrdeps[r]]@util.propertycachedefbehindcount(self):revs=self.revs[1:]deps,rdeps=self._dependenciesifrevs:minroot=[min(rforrinrevsifnotdeps[r])]try:dest=destutil.destmerge(self._repo,action=b'rebase',sourceset=minroot,onheadcheck=False)returnlen(self._repo.revs(b"only(%d, %ld)",dest,minroot))excepterror.NoMergeDestAbort:return0excepterror.ManyMergeDestAbortasexc:# XXX we should make it easier for upstream to provide the informationself.behinderror=pycompat.bytestr(exc).split(b'-',1)[0].rstrip()return-1return0@util.propertycachedefbranches(self):branches=sorted(set(self._repo[r].branch()forrinself._revs))ifnotbranches:branches=set([self._repo[None].branch()])returnbranchesdeflabelsgen(prefix,parts):fmt=prefix+b'.%s'returnprefix+b' '+b' '.join(fmt%p.replace(b' ',b'-')forpinparts)defshowstack(ui,repo,branch=None,topic=None,opts=None):ifoptsisNone:opts={}iftopicisnotNoneandbranchisnotNone:msg=b'both branch and topic specified [%s]{%s}(not defined yet)'msg%=(branch,topic)raiseerror.ProgrammingError(msg)eliftopicisnotNone:prefix=b's'iftopicnotinrepo.topics:raiseerror.Abort(_(b'cannot resolve "%s": no such topic found')%topic)elifbranchisnotNone:prefix=b's'else:raiseerror.ProgrammingError(b'neither branch and topic specified (not defined yet)')fm=ui.formatter(b'topicstack',opts)prev=Noneentries=[]idxmap={}label=b'topic'iftopic==repo.currenttopic:label=b'topic.active'st=stack(repo,branch,topic)iftopicisnotNone:fm.plain(_(b'### topic: %s')%ui.label(topic,label),label=b'stack.summary.topic')if1<len(st.heads):fm.plain(b' (')fm.plain(b'%d heads'%len(st.heads),label=b'stack.summary.headcount.multiple')fm.plain(b')')fm.plain(b'\n')fm.plain(_(b'### target: %s (branch)')%b'+'.join(st.branches),# XXX handle multi brancheslabel=b'stack.summary.branches')iftopicisNone:if1<len(st.heads):fm.plain(b' (')fm.plain(b'%d heads'%len(st.heads),label=b'stack.summary.headcount.multiple')fm.plain(b')')else:ifst.behindcount==-1:fm.plain(b', ')fm.plain(b'ambiguous rebase destination - %s'%st.behinderror,label=b'stack.summary.behinderror')elifst.behindcount:fm.plain(b', ')fm.plain(b'%d behind'%st.behindcount,label=b'stack.summary.behindcount')fm.plain(b'\n')ifnotst:fm.plain(_(b"(stack is empty)\n"))st=stack(repo,branch=branch,topic=topic)foridx,rinenumerate(st,0):ctx=repo[r]# special case for t0, b0 as it's hard to plugin into rest of the logicifidx==0:# t0, b0 can be Noneifr==-1:continueentries.append((idx,False,ctx))prev=ctx.rev()continuep1=ctx.p1()p2=ctx.p2()ifp1.obsolete():try:p1=repo[_singlesuccessor(repo,p1)]exceptMultipleSuccessorsErrorase:successors=e.successorssetsiflen(successors)>1:# case of divergence which we don't handle yetraisep1=repo[successors[0][-1]]ifp2.node()!=node.nullid:entries.append((idxmap.get(p1.rev()),False,p1))entries.append((idxmap.get(p2.rev()),False,p2))elifp1.rev()!=prevandp1.node()!=node.nullid:entries.append((idxmap.get(p1.rev()),False,p1))entries.append((idx,True,ctx))idxmap[ctx.rev()]=idxprev=r# super crude initial versionforidx,isentry,ctxinentries[::-1]:symbol=Nonestates=[]ifopts.get(b'children'):expr=b'children(%d) and merge() - %ld'revisions=repo.revs(expr,ctx.rev(),st._revs)iflen(revisions)>0:states.append(b'external-children')ifctx.orphan():symbol=b'$'states.append(b'orphan')ifctx.contentdivergent():symbol=b'$'states.append(b'content divergent')ifctx.phasedivergent():symbol=b'$'states.append(b'phase divergent')iscurrentrevision=repo.revs(b'%d and parents()',ctx.rev())ifiscurrentrevision:symbol=b'@'states.append(b'current')ifnotisentry:symbol=b'^'# "base" is kind of a "ghost" entrystates.append(b'base')# none of the above if statments get executedifnotsymbol:symbol=b':'ifnotstates:states.append(b'clean')states.sort()fm.startitem()fm.context(ctx=ctx)fm.data(isentry=isentry)ifidxisNone:spacewidth=0ifui.verbose:# parentheses plus short node hashspacewidth=2+12ifui.debugflag:# parentheses plus full node hashspacewidth=2+40# s# alias widthspacewidth+=2fm.plain(b' '*spacewidth)else:fm.write(b'stack_index',b'%s%%d'%prefix,idx,label=labelsgen(b'stack.index',states))ifui.verbose:fm.write(b'node',b'(%s)',fm.hexfunc(ctx.node()),label=labelsgen(b'stack.shortnode',states))else:fm.data(node=fm.hexfunc(ctx.node()))fm.write(b'symbol',b'%s',symbol,label=labelsgen(b'stack.state',states))fm.plain(b' ')fm.write(b'desc',b'%s',ctx.description().splitlines()[0],label=labelsgen(b'stack.desc',states))fm.condwrite(states!=[b'clean']andidxisnotNone,b'state',b' (%s)',fm.formatlist(states,b'stack.state'),label=labelsgen(b'stack.state',states))fm.plain(b'\n')fm.end()