Mercurial and Git clients can push and pull from this alias URL to interact with this repository. You can change to which repository an alias points by going to the Aliases link on the project page.
# pbranch.py - TortoiseHg's patch branch widget## Copyright 2010 Peer Sommerlund <peso@users.sourceforge.net>## This software may be used and distributed according to the terms of the# GNU General Public License version 2 or any later version.importosimporttimeimporterrnofrommercurialimportextensions,ui,errorfromtortoisehg.hgqt.i18nimport_fromtortoisehg.hgqtimportqtlib,cmdui,update,revdetailsfromtortoisehg.hgqt.qtlibimportgeticonfromtortoisehg.utilimporthglibfromPyQt4.QtCoreimport*fromPyQt4.QtGuiimport*PATCHCACHEPATH='thgpbcache'nullvariant=QVariant()classPatchBranchWidget(QWidget):''' A widget that show the patch graph and provide actions for the pbranch extension '''output=pyqtSignal(QString,QString)progress=pyqtSignal(QString,object,QString,QString,object)makeLogVisible=pyqtSignal(bool)def__init__(self,repo,parent=None,logwidget=None):QWidget.__init__(self,parent)# Set up variables and connect signalsself.repo=repoself.pbranch=extensions.find('pbranch')# Unfortunately global instead of repo-specificself.show_internal_branches=Falserepo.configChanged.connect(self.configChanged)repo.repositoryChanged.connect(self.repositoryChanged)repo.workingBranchChanged.connect(self.workingBranchChanged)# Build child widgetsdefBuildChildWidgets():vbox=QVBoxLayout()vbox.setContentsMargins(0,0,0,0)self.setLayout(vbox)vbox.addWidget(Toolbar(),1)vbox.addWidget(BelowToolbar(),1)defToolbar():tb=QToolBar(_("Patch Branch Toolbar"),self)tb.setEnabled(True)tb.setObjectName("toolBar_patchbranch")tb.setFloatable(False)self.actionPMerge=a=QWidgetAction(self)a.setIcon(geticon("hg-merge"))a.setToolTip(_('Merge all pending dependencies'))tb.addAction(self.actionPMerge)self.actionPMerge.triggered.connect(self.pmerge_clicked)self.actionBackport=a=QWidgetAction(self)a.setIcon(geticon("go-previous"))a.setToolTip(_('Backout current patch branch'))#tb.addAction(self.actionBackport)#self.actionBackport.triggered.connect(self.pbackout_clicked)self.actionReapply=a=QWidgetAction(self)a.setIcon(geticon("go-next"))a.setToolTip(_('Backport part of a changeset to a dependency'))#tb.addAction(self.actionReapply)#self.actionReapply.triggered.connect(self.reapply_clicked)self.actionPNew=a=QWidgetAction(self)a.setIcon(geticon("fileadd"))#STOCK_NEWa.setToolTip(_('Start a new patch branch'))tb.addAction(self.actionPNew)self.actionPNew.triggered.connect(self.pnew_clicked)self.actionEditPGraph=a=QWidgetAction(self)a.setIcon(geticon("edit-file"))#STOCK_EDITa.setToolTip(_('Edit patch dependency graph'))tb.addAction(self.actionEditPGraph)self.actionEditPGraph.triggered.connect(self.edit_pgraph_clicked)returntbdefBelowToolbar():w=QSplitter(self)w.addWidget(PatchList())w.addWidget(PatchDiff())returnwdefPatchList():self.patchlistmodel=PatchBranchModel(self.compute_model(),self.repo.changectx('.').branch(),self)self.patchlist=QTableView(self)self.patchlist.setModel(self.patchlistmodel)self.patchlist.setShowGrid(False)self.patchlist.verticalHeader().setDefaultSectionSize(20)self.patchlist.horizontalHeader().setHighlightSections(False)self.patchlist.setSelectionBehavior(QAbstractItemView.SelectRows)self.patchlist.clicked.connect(self.patchBranchSelected)returnself.patchlistdefPatchDiff():# pdiff view to the right of pgraphself.patchDiffStack=QStackedWidget()self.patchDiffStack.addWidget(PatchDiffMessage())self.patchDiffStack.addWidget(PatchDiffDetails())returnself.patchDiffStackdefPatchDiffMessage():# message if no patch is selectedself.patchDiffMessage=QLabel()self.patchDiffMessage.setAlignment(Qt.AlignCenter)returnself.patchDiffMessage def PatchDiffDetails():
# pdiff view of selected patc
- self.patchdiff = revdetails.RevDetailsWidget(self.repo)
+ self.patchdiff = revdetails.RevDetailsWidget(self.repo, self)
return self.patchdiff
BuildChildWidgets()
# Command outputself.runner=cmdui.Runner(False,self)self.runner.output.connect(self.output)self.runner.progress.connect(self.progress)self.runner.makeLogVisible.connect(self.makeLogVisible)self.runner.commandFinished.connect(self.commandFinished)defreload(self):'User has requested a reload'self.repo.thginvalidate()self.refresh()defrefresh(self):""" Refresh the list of patches. This operation will try to keep selection state. """ifnotself.pbranch:return# store selected patch nameselname=Nonepatchnamecol=PatchBranchModel._columns.index('Name')# Column used to store patch nameselinxs=self.patchlist.selectedIndexes()iflen(selinxs)>0:selrow=selinxs[0].row()patchnameinx=self.patchlist.model().index(selrow,patchnamecol)selname=self.patchlist.model().data(patchnameinx)# compute model dataself.patchlistmodel.setModel(self.compute_model(),self.repo.changectx('.').branch())# restore patch selectionifselname:selinxs=self.patchlistmodel.match(self.patchlistmodel.index(0,patchnamecol),Qt.DisplayRole,selname,flags=Qt.MatchExactly)iflen(selinxs)>0:self.patchlist.setCurrentIndex(selinxs[0])# update UI sensitivesself.update_sensitivity()## Data functions#defcompute_model(self):""" Compute content of table, including patch graph and other columns """# compute model datamodel=[]# Generate patch branch graph from all heads (option --tips)opts={'tips':True}mgr=self.pbranch.patchmanager(self.repo.ui,self.repo,opts)graph=mgr.graphforopts(opts)ifnotself.show_internal_branches:graph=mgr.patchonlygraph(graph)names=Nonepatch_list=graph.topolist(names)in_lines=[]ifpatch_list:dep_list=[patch_list[0]]cur_branch=self.repo['.'].branch()patch_status={}fornameinpatch_list:patch_status[name]=self.pstatus(name)fornameinpatch_list:parents=graph.deps(name)# Node propertiesifnameindep_list:node_column=dep_list.index(name)else:node_column=len(dep_list)node_color=patch_status[name]and'#ff0000'or0node_status=(name==cur_branch)and4or0node=PatchGraphNodeAttributes(node_column,node_color,node_status)# Find next dependency listmy_deps=[]forpinparents:ifpnotindep_list:my_deps.append(p)next_dep_list=dep_list[:]next_dep_list[node_column:node_column+1]=my_deps# Dependency linesshift=len(parents)-1out_lines=[]forpinparents:dep_column=next_dep_list.index(p)color=0# blackifpatch_status[p]:color='#ff0000'# redstyle=0# solid linesout_lines.append(GraphLine(node_column,dep_column,color,style))forlineinin_lines:ifline.end_column==node_column:# Deps to current patch end herepasselse:# Find line continuationsdep=dep_list[line.end_column]dep_column=next_dep_list.index(dep)out_lines.append(GraphLine(line.end_column,dep_column,line.color,line.style))stat=patch_status[name]and'M'or'C'# patch statuspatchname=namemsg=self.pmessage(name)# summaryifmsg:title=msg.split('\n')[0]else:title=Nonemodel.append(PatchGraphNode(node,in_lines,out_lines,patchname,stat,title,msg))# Loopin_lines=out_linesdep_list=next_dep_listreturnmodel## pbranch extension functions#defpgraph(self):""" [pbranch] Execute 'pgraph' command. :returns: A list of patches and dependencies """ifself.pbranchisNone:returnNoneopts={}mgr=self.pbranch.patchmanager(self.repo.ui,self.repo,opts)returnmgr.graphforopts(opts)defpstatus(self,patch_name):""" [pbranch] Execute 'pstatus' command. :param patch_name: Name of patch-branch :retv: list of status messages. If empty there is no pending merges """ifself.pbranchisNone:returnNonestatus=[]opts={}mgr=self.pbranch.patchmanager(self.repo.ui,self.repo,opts)graph=mgr.graphforopts(opts)heads=self.repo.branchheads(patch_name)iflen(heads)>1:status.append(_('needs merge of %i heads\n')%len(heads))fordep,throughingraph.pendingmerges(patch_name):ifthrough:status.append(_('needs merge with %s (through %s)\n')%(dep,", ".join(through)))else:status.append(_('needs merge with %s\n')%dep)fordepingraph.pendingrebases(patch_name):status.append(_('needs update of diff base to tip of %s\n')%dep)returnstatusdefpmessage(self,patch_name):""" Get patch message :param patch_name: Name of patch-branch :retv: Full patch message. If you extract the first line you will get the patch title. If the repo does not contain message or patch, the function returns None """opts={}mgr=self.pbranch.patchmanager(self.repo.ui,self.repo,opts)try:returnmgr.patchdesc(patch_name)except:returnNonedefpdiff(self,patch_name):""" [pbranch] Execute 'pdiff --tips' command. :param patch_name: Name of patch-branch :retv: list of lines of generated patch """opts={}mgr=self.pbranch.patchmanager(self.repo.ui,self.repo,opts)graph=mgr.graphattips()returngraph.diff(patch_name,None,opts)defpnew_ui(self):""" Create new patch. Propmt user for new patch name. Patch is created on current branch. """parent=Nonetitle=_('TortoiseHg Prompt')label=_('New Patch Name')new_name,ok=QInputDialog.getText(self,title,label)ifnotok:returnFalseself.pnew(hglib.fromunicode(new_name))returnTruedefpnew(self,patch_name):""" [pbranch] Execute 'pnew' command. :param patch_name: Name of new patch-branch """ifself.pbranchisNone:returnFalseself.repo.incrementBusyCount()self.pbranch.cmdnew(self.repo.ui,self.repo,patch_name)self.repo.decrementBusyCount()returnTruedefpmerge(self,patch_name=None):""" [pbranch] Execute 'pmerge' command. :param patch_name: Merge to this patch-branch """ifnotself.has_patch():returncmd=['pmerge','--cwd',self.repo.root]ifpatch_name:cmd+=[patch_name]else:cmd+=['--all']self.repo.incrementBusyCount()self.runner.run(cmd)defhas_pbranch(self):""" return True if pbranch extension can be used """returnself.pbranchisnotNonedefhas_patch(self):""" return True if pbranch extension is in use on repo """returnself.has_pbranch()andself.pgraph()!=[]defis_patch(self,branch_name):""" return True if branch is a patch. This excludes root branches and internal diff base branches (for patches with multiple dependencies. """returnself.has_pbranch()andself.pgraph().ispatch(branch_name)defcur_branch(self):""" Return branch that workdir belongs to. """returnself.repo.dirstate.branch()### internal functions ###defpatchFromIndex(self,index):ifnotindex.isValid():returnmodel=self.patchlistmodelcol=model._columns.index('Name')patchIndex=model.createIndex(index.row(),col)returnstr(model.data(patchIndex).toString())defupdatePatchCache(self,patchname):# TODO: Parameters should include rev, as one patch may have several heads# rev should be appended to filename and used by pdiffassert(len(patchname)>0)cachepath=self.repo.join(PATCHCACHEPATH)# TODO: Fix this - it looks uglytry:os.mkdir(cachepath)exceptOSError,err:iferr.errno!=errno.EEXIST:raise# TODO: Convert filename if any funny characters are presentpatchfile=os.path.join(cachepath,patchname)dirstate=self.repo.join('dirstate')try:patch_age=os.path.getmtime(patchfile)-os.path.getmtime(dirstate)except:patch_age=-1ifpatch_age<0:pf=open(patchfile,'wb')try:pf.writelines(self.pdiff(patchname))# except (util.Abort, error.RepoError), e:# # Do something with str(e)finally:pf.close()returnpatchfiledefupdate_sensitivity(self):""" Update the sensitivity of entire UI """in_pbranch=True#TODOis_merge=len(self.repo.parents())>1self.actionPMerge.setEnabled(in_pbranch)self.actionBackport.setEnabled(in_pbranch)self.actionReapply.setEnabled(True)self.actionPNew.setEnabled(notis_merge)self.actionEditPGraph.setEnabled(True)defselected_patch(self):C_NAME=PatchBranchModel._columns.index('Name')indexes=self.patchlist.selectedIndexes()iflen(indexes)==0:returnNoneindex=indexes[0]returnstr(index.sibling(index.row(),C_NAME).data().toString())defshow_patch_cmenu(self,pos):"""Context menu for selected patch"""patchname=self.selected_patch()ifnotpatchname:returnmenu=QMenu(self)defappend(label,handler):menu.addAction(label).triggered.connect(handler)has_pbranch=self.has_pbranch()is_current=self.has_patch()andself.cur_branch()==patchnameis_patch=self.is_patch(patchname)is_internal=self.pbranch.isinternal(patchname)is_merge=len(self.repo.branchheads(patchname))>1#if has_pbranch and not is_merge and not is_internal:# append(_('&New'), self.pnew_activated)ifnotis_current:append(_('&Goto (update workdir)'),self.goto_activated)#if is_patch:# append(_('&Edit message'), self.edit_message_activated)# append(_('&Rename'), self.rename_activated)# append(_('&Delete'), self.delete_activated)# append(_('&Finish'), self.finish_activated)iflen(menu.actions())>0:menu.exec_(pos)# Signal handlersdefpatchBranchSelected(self,index):patchname=self.patchFromIndex(index)ifself.is_patch(patchname):patchfile=self.updatePatchCache(patchname)self.patchdiff.onRevisionSelected(patchfile)self.patchDiffStack.setCurrentWidget(self.patchdiff)else:self.patchDiffMessage.setText(_('No patch branch selected'))self.patchDiffStack.setCurrentWidget(self.patchDiffMessage)defcloseEvent(self,event):self.repo.configChanged.disconnect(self.configChanged)self.repo.repositoryChanged.disconnect(self.repositoryChanged)self.repo.workingBranchChanged.disconnect(self.workingBranchChanged)super(PatchBranchWidget,self).closeEvent(event)defcontextMenuEvent(self,event):ifself.patchlist.geometry().contains(event.pos()):self.show_patch_cmenu(event.globalPos())defcommandFinished(self,ret):self.repo.decrementBusyCount()self.refresh()defconfigChanged(self):passdefrepositoryChanged(self):self.refresh()defworkingBranchChanged(self):self.refresh()defpmerge_clicked(self):self.pmerge()defpnew_clicked(self,toolbutton):self.pnew_ui()defedit_pgraph_clicked(self):opts={}# TODO: How to find user IDmgr=self.pbranch.patchmanager(self.repo.ui,self.repo,opts)oldtext=mgr.graphdesc()# run editor in the repository rootolddir=os.getcwd()os.chdir(self.repo.root)try:newtext=Nonenewtext=self.repo.ui.edit(oldtext,opts.get('user'))excepterror.Abort:no_editor_configured=(os.environ.get("HGEDITOR")orself.repo.ui.config("ui","editor")oros.environ.get("VISUAL")oros.environ.get("EDITOR","editor-not-configured")=="editor-not-configured")ifno_editor_configured:qtlib.ErrorMsgBox(_('No editor found'),_('Mercurial was unable to find an editor. Please configure Mercurial to use an editor installed on your system.'))else:raiseos.chdir(olddir)ifnewtextisnotNone:mgr.updategraphdesc(newtext)### context menu signal handlers ###defpnew_activated(self):"""Insert new patch after this row"""assertFalsedefedit_message_activated(self):assertFalsedefgoto_activated(self):branch=self.selected_patch()# TODO: Fetch list of heads of branch# - use a list of revs if more than one founddlg=update.UpdateDialog(self.repo,branch,self)dlg.output.connect(self.output)dlg.makeLogVisible.connect(self.makeLogVisible)dlg.progress.connect(self.progress)dlg.finished.connect(dlg.deleteLater)dlg.exec_()defdelete_activated(self):assertFalsedefrename_activated(self):assertFalsedeffinish_activated(self):assertFalseclassPatchGraphNode(object):""" Simple class to encapsulate a node in the patch branch graph. Does nothing but declaring attributes. """def__init__(self,node,in_lines,out_lines,patchname,stat,title,msg):""" :node: attributes related to the node :in_lines: List of lines above node :out_lines: List of lines below node :patchname: Patch branch name :stat: Status of node - does it need updating or not :title: First line of patch message :msg: Full patch message """self.node=nodeself.toplines=in_linesself.bottomlines=out_lines# Find rightmost column usedself.cols=max([max(line.start_column,line.end_column)forlineinin_lines+out_lines])self.patchname=patchnameself.status=statself.title=titleself.message=msgself.msg_esc=msg# u''.join(msg) # escaped summary (utf-8)classPatchGraphNodeAttributes(object):""" Simple class to encapsulate attributes about a node in the patch branch graph. Does nothing but declaring attributes. """def__init__(self,column,color,status):self.column=columnself.color=colorself.status=statusclassGraphLine(object):""" Simple class to encapsulate attributes about a line in the patch branch graph. Does nothing but declaring attributes. """def__init__(self,start_column,end_column,color,style):self.start_column=start_columnself.end_column=end_columnself.color=colorself.style=styleclassPatchBranchContext(object):""" Similar to patchctx in thgrepo, this class simulates a changeset for a particular patch branch- """classPatchBranchModel(QAbstractTableModel):""" Model used to list patch branches TODO: Should be extended to list all branches """_columns=['Graph','Name','Status','Title','Message',]def__init__(self,model,wd_branch="",parent=None):QAbstractTableModel.__init__(self,parent)self.rowcount=0self._columnmap={'Graph':lambdactx,gnode:"",'Name':lambdactx,gnode:gnode.patchname,'Status':lambdactx,gnode:gnode.status,'Title':lambdactx,gnode:gnode.title,'Message':lambdactx,gnode:gnode.message}self.model=modelself.wd_branch=wd_branchself.dotradius=8self.rowheight=20# virtual functions required to subclass QAbstractTableModeldefrowCount(self,parent=None):returnlen(self.model)defcolumnCount(self,parent=None):returnlen(self._columns)defdata(self,index,role=Qt.DisplayRole):ifnotindex.isValid():returnnullvariantrow=index.row()column=self._columns[index.column()]gnode=self.model[row]ctx=None#ctx = self.repo.changectx(gnode.rev)ifrole==Qt.DisplayRole:text=self._columnmap[column](ctx,gnode)ifnotisinstance(text,(QString,unicode)):text=hglib.tounicode(text)returnQVariant(text)elifrole==Qt.ForegroundRole:returngnode.node.colorifctx.thgpbunappliedpatch():returnQColor(UNAPPLIED_PATCH_COLOR)ifcolumn=='Name':returnQVariant(QColor(self.namedbranch_color(ctx.branch())))elifrole==Qt.DecorationRole:ifcolumn=='Graph':returnself.graphctx(ctx,gnode)returnnullvariantdefheaderData(self,section,orientation,role):iforientation==Qt.Horizontal:ifrole==Qt.DisplayRole:returnQVariant(self._columns[section])ifrole==Qt.TextAlignmentRole:returnQVariant(Qt.AlignLeft)returnnullvariant# end of functions required to subclass QAbstractTableModeldefsetModel(self,model,wd_branch):self.beginResetModel()self.model=modelself.wd_branch=wd_branchself.endResetModel()defcol2x(self,col):return2*self.dotradius*col+self.dotradius/2+8defgraphctx(self,ctx,gnode):""" Return a QPixmap for the patch graph for the current row :ctx: Data for current row = branch :gnode: Node in patch branch graph :returns: QPixmap of pgraph for ctx """w=self.col2x(gnode.cols)+10h=self.rowheightdot_y=h/2# Prepare painting: Target pixmap, blue and black penpix=QPixmap(w,h)pix.fill(QColor(0,0,0,0))painter=QPainter(pix)painter.setRenderHint(QPainter.Antialiasing)pen=QPen(Qt.blue)pen.setWidth(2)painter.setPen(pen)lpen=QPen(pen)lpen.setColor(Qt.black)painter.setPen(lpen)# Draw linesfory1,y4,linesin((dot_y,dot_y+h,gnode.bottomlines),(dot_y-h,dot_y,gnode.toplines)):y2=y1+1*(y4-y1)/4ymid=(y1+y4)/2y3=y1+3*(y4-y1)/4forlineinlines:start=line.start_columnend=line.end_columncolor=line.colorlpen=QPen(pen)lpen.setColor(QColor(color))lpen.setWidth(2)painter.setPen(lpen)x1=self.col2x(start)x2=self.col2x(end)path=QPainterPath()path.moveTo(x1,y1)path.cubicTo(x1,y2,x1,y2,(x1+x2)/2,ymid)path.cubicTo(x2,y3,x2,y3,x2,y4)painter.drawPath(path)# Draw nodedot_color=QColor(gnode.node.color)dotcolor=dot_color.lighter()pencolor=dot_color.darker()white=QColor("white")fillcolor=dotcolor#gnode.rev is None and white or dotcolorpen=QPen(pencolor)pen.setWidthF(1.5)painter.setPen(pen)radius=self.dotradiuscentre_x=self.col2x(gnode.node.column)centre_y=h/2defcircle(r):rect=QRectF(centre_x-r,centre_y-r,2*r,2*r)painter.drawEllipse(rect)defdiamond(r):poly=QPolygonF([QPointF(centre_x-r,centre_y),QPointF(centre_x,centre_y-r),QPointF(centre_x+r,centre_y),QPointF(centre_x,centre_y+r),QPointF(centre_x-r,centre_y),])painter.drawPolygon(poly)ifFalseandctx.thg_patchbranch():# diamonds for patchesifctx.thg_wdbranch():painter.setBrush(white)diamond(2*0.9*radius/1.5)painter.setBrush(fillcolor)diamond(radius/1.5)else:# circles for normal branchesifgnode.patchname==self.wd_branch:painter.setBrush(white)circle(0.9*radius)painter.setBrush(fillcolor)circle(0.5*radius)painter.end()returnQVariant(pix)
Attach a Trello Card
Add a tag
Your session has expired
You are no longer logged in. Please log in and try your request again.
Filter RSS Feed
This RSS feed URL allows you to see the contents of your current filter using any feed reader.
This link includes a special authentication token. If you share the URL with anyone else, they can see this RSS feed's activity. You can disable these tokens when needed.
Your current filter is unsaved; changing it won't affect this RSS feed.